• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 23959483528

03 Apr 2026 07:36PM UTC coverage: 59.888% (-1.8%) from 61.699%
23959483528

Pull #3271

github

web-flow
Merge a5b540ae8 into bc59e880b
Pull Request #3271: chore: deck.gl 9.2 upgrade & loaders.gl, luma.gl upgrades

6517 of 12973 branches covered (50.24%)

Branch coverage included in aggregate %.

324 of 1027 new or added lines in 58 files covered. (31.55%)

123 existing lines in 18 files now uncovered.

13313 of 20139 relevant lines covered (66.11%)

78.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.11
/src/utils/src/export-utils.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import {Blob, URL, atob, Uint8Array, ArrayBuffer, document} from 'global/window';
5
import get from 'lodash/get';
6

7
import {
8
  EXPORT_IMG_RESOLUTION_OPTIONS,
9
  EXPORT_IMG_RATIO_OPTIONS,
10
  EXPORT_IMG_RATIOS,
11
  FourByThreeRatioOption,
12
  OneXResolutionOption,
13
  type ExportResolutionOption
14
} from '@kepler.gl/constants';
15
import {ExportImage} from '@kepler.gl/types';
16
import {generateHashId} from '@kepler.gl/common-utils';
17
import domtoimage from './dom-to-image';
18
import {set} from './utils';
19
import {exportMapToHTML} from './export-map-html';
20
import {getApplicationConfig} from './application-config';
21

22
const defaultResolution = OneXResolutionOption;
15✔
23

24
const defaultRatio = FourByThreeRatioOption;
15✔
25

26
export function isMSEdge(window: Window): boolean {
27
  // @ts-ignore msSaveOrOpenBlob was a proprietary addition to the Navigator object, added by Microsoft for Internet Explorer.
28
  return Boolean(window.navigator && window.navigator.msSaveOrOpenBlob);
3✔
29
}
30

31
export function getScaleFromImageSize(imageW = 0, imageH = 0, mapW = 0, mapH = 0) {
1!
32
  if ([imageW, imageH, mapW, mapH].some(d => d <= 0)) {
16✔
33
    return 1;
3✔
34
  }
35

36
  const base = imageW / imageH > 1 ? imageW : imageH;
1!
37
  const mapBase = imageW / imageH > 1 ? mapW : mapH;
1!
38
  return base / mapBase;
1✔
39
}
40

41
export function calculateExportImageSize({
42
  mapW,
43
  mapH,
44
  ratio,
45
  resolution
46
}: {
47
  mapW: number;
48
  mapH: number;
49
  ratio: keyof typeof EXPORT_IMG_RATIOS;
50
  resolution: ExportResolutionOption;
51
}) {
52
  if (mapW <= 0 || mapH <= 0) {
11✔
53
    return null;
4✔
54
  }
55

56
  const ratioItem = EXPORT_IMG_RATIO_OPTIONS.find(op => op.id === ratio) || defaultRatio;
13✔
57

58
  const resolutionItem =
59
    EXPORT_IMG_RESOLUTION_OPTIONS.find(op => op.id === resolution) || defaultResolution;
25✔
60

61
  const {width: scaledWidth, height: scaledHeight} = resolutionItem.getSize(mapW, mapH);
7✔
62

63
  const {width: imageW, height: imageH} = ratioItem.getSize(scaledWidth, scaledHeight);
7✔
64

65
  const {scale} = ratioItem.id === EXPORT_IMG_RATIOS.CUSTOM ? {scale: undefined} : resolutionItem;
7✔
66

67
  return {
7✔
68
    scale,
69
    imageW,
70
    imageH
71
  };
72
}
73

74
export function convertToPng(sourceElem: HTMLElement, options) {
UNCOV
75
  return domtoimage.toPng(sourceElem, options);
×
76
}
77

78
export function dataURItoBlob(dataURI: string): Blob {
79
  const binary = atob(dataURI.split(',')[1]);
×
80

81
  // separate out the mime component
82
  const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
×
83

84
  // write the bytes of the string to an ArrayBuffer
85
  const ab = new ArrayBuffer(binary.length);
×
86

87
  // create a view into the buffer
88
  const ia = new Uint8Array(ab);
×
89

90
  for (let i = 0; i < binary.length; i++) {
×
91
    ia[i] = binary.charCodeAt(i);
×
92
  }
93

94
  return new Blob([ab], {type: mimeString});
×
95
}
96

97
export function downloadFile(fileBlob: Blob, fileName: string) {
98
  if (isMSEdge(window)) {
×
99
    (window.navigator as any).msSaveOrOpenBlob(fileBlob, fileName);
×
100
  } else {
101
    const url = URL.createObjectURL(fileBlob);
×
102

103
    const link = document.createElement('a');
×
104
    link.setAttribute('href', url);
×
105
    link.setAttribute('download', fileName);
×
106

107
    document.body.appendChild(link);
×
108
    // in some cases where maps are embedded, e.g. need to
109
    // create and dispatch an event so that the browser downloads
110
    // the file instead of navigating to the url
111
    const evt = new MouseEvent('click', {
×
112
      view: window,
113
      bubbles: false,
114
      cancelable: true
115
    });
116
    link.dispatchEvent(evt);
×
117
    document.body.removeChild(link);
×
118
    URL.revokeObjectURL(url);
×
119
  }
120
}
121

122
/**
123
 * Whether color is rgb
124
 * @returns
125
 */
126
export function exportImage(
127
  uiStateExportImage: ExportImage,
128
  filename = getApplicationConfig().defaultImageName
×
129
) {
130
  const {imageDataUri} = uiStateExportImage;
×
131
  if (imageDataUri) {
×
132
    const file = dataURItoBlob(imageDataUri);
×
133
    downloadFile(file, filename);
×
134
  }
135
}
136

137
export function exportToJsonString(data) {
138
  try {
2✔
139
    return JSON.stringify(data);
2✔
140
  } catch (e) {
141
    if (e instanceof TypeError) return e.message;
×
142
    // Non-Standard Error Object Property
143
    return (e as any).description;
×
144
  }
145
}
146

147
export function getMapJSON(state, options = getApplicationConfig().defaultExportJsonSettings) {
1✔
148
  const {hasData} = options;
1✔
149
  const schema = state.visState.schema;
1✔
150

151
  if (!hasData) {
1!
152
    return schema.getConfigToSave(state);
×
153
  }
154

155
  let mapToSave = schema.save(state);
1✔
156
  // add file name if title is not provided
157
  const title = get(mapToSave, ['info', 'title']);
1✔
158
  if (!title || !title.length) {
1!
159
    mapToSave = set(['info', 'title'], `keplergl_${generateHashId(6)}`, mapToSave);
1✔
160
  }
161
  return mapToSave;
1✔
162
}
163

164
export function exportJson(state, options: any = {}) {
×
165
  const map = getMapJSON(state, options);
×
166
  map.info.source = 'kepler.gl';
×
167
  const fileBlob = new Blob([exportToJsonString(map)], {type: 'application/json'});
×
168
  const fileName = state.appName ? `${state.appName}.json` : getApplicationConfig().defaultJsonName;
×
169
  downloadFile(fileBlob, fileName);
×
170
}
171

172
export function exportHtml(state, options) {
173
  const {userMapboxToken, exportMapboxAccessToken, mode} = options;
×
174

175
  const data = {
×
176
    ...getMapJSON(state),
177
    mapboxApiAccessToken:
178
      (userMapboxToken || '') !== '' ? userMapboxToken : exportMapboxAccessToken,
×
179
    mode
180
  };
181

182
  const fileBlob = new Blob([exportMapToHTML(data)], {type: 'text/html'});
×
183
  downloadFile(
×
184
    fileBlob,
185
    state.appName ? `${state.appName}.html` : getApplicationConfig().defaultHtmlName
×
186
  );
187
}
188

189
export function exportMap(state, options = getApplicationConfig().defaultExportJsonSettings) {
×
190
  const {imageDataUri} = state.uiState.exportImage;
×
191
  const thumbnail: Blob | null = imageDataUri ? dataURItoBlob(imageDataUri) : null;
×
192
  const mapToSave = getMapJSON(state, options);
×
193

194
  return {
×
195
    map: mapToSave,
196
    thumbnail
197
  };
198
}
199

200
const exporters = {
15✔
201
  exportImage,
202
  exportJson,
203
  exportHtml
204
};
205

206
export default exporters;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc