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

visgl / deck.gl / 13977134572

20 Mar 2025 06:52PM UTC coverage: 91.627% (+0.09%) from 91.538%
13977134572

Pull #9523

github

web-flow
Merge 486ad0976 into 21dcaee23
Pull Request #9523: chore(yarn): Add package.json#packageManager field

6704 of 7352 branches covered (91.19%)

Branch coverage included in aggregate %.

55029 of 60022 relevant lines covered (91.68%)

14625.86 hits per line

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

91.84
/modules/carto/src/api/parse-map.ts
1
// deck.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {ColorParameters} from '@luma.gl/core';
1✔
6
import {Layer, log} from '@deck.gl/core';
1✔
7
import {
1✔
8
  AGGREGATION,
1✔
9
  getLayer,
1✔
10
  getColorAccessor,
1✔
11
  getColorValueAccessor,
1✔
12
  getSizeAccessor,
1✔
13
  getTextAccessor,
1✔
14
  OPACITY_MAP,
1✔
15
  opacityToAlpha,
1✔
16
  getIconUrlAccessor,
1✔
17
  negateAccessor,
1✔
18
  getMaxMarkerSize
1✔
19
} from './layer-map';
1✔
20
import {assert} from '../utils';
1✔
21
import {KeplerMapConfig, MapDataset, MapLayerConfig, VisualChannels} from './types';
1✔
22

1✔
23
export type ParseMapResult = {
1✔
24
  /** Map id. */
1✔
25
  id: string;
1✔
26

1✔
27
  /** Title of map. */
1✔
28
  title: string;
1✔
29

1✔
30
  /** Description of map. */
1✔
31
  description?: string;
1✔
32
  createdAt: string;
1✔
33
  updatedAt: string;
1✔
34
  initialViewState: any;
1✔
35

1✔
36
  /** @deprecated Use `basemap`. */
1✔
37
  mapStyle: any;
1✔
38
  token: string;
1✔
39

1✔
40
  layers: Layer[];
1✔
41
};
1✔
42

1✔
43
export function parseMap(json) {
1✔
44
  const {keplerMapConfig, datasets, token} = json;
18✔
45
  assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
18✔
46
  const {mapState, mapStyle} = keplerMapConfig.config as KeplerMapConfig;
18✔
47
  const {layers, layerBlending, interactionConfig} = keplerMapConfig.config.visState;
18✔
48

18✔
49
  return {
18✔
50
    id: json.id,
18✔
51
    title: json.title,
18✔
52
    description: json.description,
18✔
53
    createdAt: json.createdAt,
18✔
54
    updatedAt: json.updatedAt,
18✔
55
    initialViewState: mapState,
18✔
56
    /** @deprecated Use `basemap`. */
18✔
57
    mapStyle,
18✔
58
    token,
18✔
59
    layers: layers.reverse().map(({id, type, config, visualChannels}) => {
18✔
60
      try {
18✔
61
        const {dataId} = config;
18✔
62
        const dataset: MapDataset | null = datasets.find(d => d.id === dataId);
18✔
63
        assert(dataset, `No dataset matching dataId: ${dataId}`);
18✔
64
        const {data} = dataset;
18✔
65
        assert(data, `No data loaded for dataId: ${dataId}`);
18✔
66
        const {Layer, propMap, defaultProps} = getLayer(type, config, dataset);
18✔
67
        const styleProps = createStyleProps(config, propMap);
18✔
68
        return new Layer({
18✔
69
          id,
18✔
70
          data,
18✔
71
          ...defaultProps,
18✔
72
          ...createInteractionProps(interactionConfig),
18✔
73
          ...styleProps,
18✔
74
          ...createChannelProps(id, type, config, visualChannels, data), // Must come after style
18✔
75
          ...createParametersProp(layerBlending, styleProps.parameters || {}), // Must come after style
18✔
76
          ...createLoadOptions(token)
18✔
77
        });
18✔
78
      } catch (e: any) {
18✔
79
        log.error(e.message)();
1✔
80
        return undefined;
1✔
81
      }
1✔
82
    })
18✔
83
  };
18✔
84
}
18✔
85

1✔
86
function createParametersProp(layerBlending: string, parameters: ColorParameters) {
17✔
87
  if (layerBlending === 'additive') {
17✔
88
    parameters.blendColorSrcFactor = parameters.blendAlphaSrcFactor = 'src-alpha';
4✔
89
    parameters.blendColorDstFactor = parameters.blendAlphaDstFactor = 'dst-alpha';
4✔
90
    parameters.blendColorOperation = parameters.blendAlphaOperation = 'add';
4✔
91
  } else if (layerBlending === 'subtractive') {
17✔
92
    parameters.blendColorSrcFactor = 'one';
6✔
93
    parameters.blendColorDstFactor = 'one-minus-dst-color';
6✔
94
    parameters.blendAlphaSrcFactor = 'src-alpha';
6✔
95
    parameters.blendAlphaDstFactor = 'dst-alpha';
6✔
96
    parameters.blendColorOperation = 'subtract';
6✔
97
    parameters.blendAlphaOperation = 'add';
6✔
98
  }
6✔
99

17✔
100
  return Object.keys(parameters).length ? {parameters} : {};
17✔
101
}
17✔
102

1✔
103
function createInteractionProps(interactionConfig) {
17✔
104
  const pickable = interactionConfig && interactionConfig.tooltip.enabled;
17✔
105
  return {
17✔
106
    autoHighlight: pickable,
17✔
107
    pickable
17✔
108
  };
17✔
109
}
17✔
110

1✔
111
function mapProps(source, target, mapping) {
62✔
112
  for (const sourceKey in mapping) {
62✔
113
    const sourceValue = source[sourceKey];
335✔
114
    const targetKey = mapping[sourceKey];
335✔
115
    if (sourceValue === undefined) {
335✔
116
      // eslint-disable-next-line no-continue
111✔
117
      continue;
111✔
118
    }
111✔
119
    if (typeof targetKey === 'string') {
335✔
120
      target[targetKey] = sourceValue;
165✔
121
    } else if (typeof targetKey === 'function') {
335✔
122
      const [key, value] = Object.entries(targetKey(sourceValue))[0];
14✔
123
      target[key] = value;
14✔
124
    } else if (typeof targetKey === 'object') {
59✔
125
      // Nested definition, recurse down one level (also handles arrays)
45✔
126
      mapProps(sourceValue, target, targetKey);
45✔
127
    }
45✔
128
  }
335✔
129
}
62✔
130

1✔
131
function createStyleProps(config: MapLayerConfig, mapping) {
17✔
132
  const result: Record<string, any> = {};
17✔
133
  mapProps(config, result, mapping);
17✔
134

17✔
135
  // Kepler format sometimes omits strokeColor. TODO: remove once we can rely on
17✔
136
  // `strokeColor` always being set when `stroke: true`.
17✔
137
  if (result.stroked && !result.getLineColor) {
17!
138
    result.getLineColor = result.getFillColor;
×
139
  }
×
140

17✔
141
  for (const colorAccessor in OPACITY_MAP) {
17✔
142
    if (Array.isArray(result[colorAccessor])) {
51✔
143
      const color = [...result[colorAccessor]];
24✔
144
      const opacityKey = OPACITY_MAP[colorAccessor];
24✔
145
      const opacity = config.visConfig[opacityKey];
24✔
146
      color[3] = opacityToAlpha(opacity);
24✔
147
      result[colorAccessor] = color;
24✔
148
    }
24✔
149
  }
51✔
150

17✔
151
  result.highlightColor = config.visConfig.enable3d ? [255, 255, 255, 60] : [252, 242, 26, 255];
17✔
152
  return result;
17✔
153
}
17✔
154

1✔
155
/* eslint-disable complexity, max-statements */
1✔
156
function createChannelProps(
17✔
157
  id: string,
17✔
158
  type: string,
17✔
159
  config: MapLayerConfig,
17✔
160
  visualChannels: VisualChannels,
17✔
161
  data
17✔
162
) {
17✔
163
  const {
17✔
164
    colorField,
17✔
165
    colorScale,
17✔
166
    radiusField,
17✔
167
    radiusScale,
17✔
168
    sizeField,
17✔
169
    sizeScale,
17✔
170
    strokeColorField,
17✔
171
    strokeColorScale,
17✔
172
    weightField
17✔
173
  } = visualChannels;
17✔
174
  let {heightField, heightScale} = visualChannels;
17✔
175
  if (type === 'hexagonId') {
17✔
176
    heightField = sizeField;
1✔
177
    heightScale = sizeScale;
1✔
178
  }
1✔
179
  const {textLabel, visConfig} = config;
17✔
180
  const result: Record<string, any> = {};
17✔
181

17✔
182
  if (type === 'grid' || type === 'hexagon') {
17✔
183
    result.colorScaleType = colorScale;
2✔
184
    if (colorField) {
2!
185
      const {colorAggregation} = config.visConfig;
×
186
      if (!AGGREGATION[colorAggregation]) {
×
187
        result.getColorValue = getColorValueAccessor(colorField, colorAggregation, data);
×
188
      } else {
×
189
        result.getColorWeight = d => d[colorField.name];
×
190
      }
×
191
    }
×
192
  } else if (colorField) {
17✔
193
    const {colorAggregation: aggregation, colorRange: range} = visConfig;
5✔
194
    result.getFillColor = getColorAccessor(
5✔
195
      colorField,
5✔
196
      // @ts-ignore
5✔
197
      colorScale,
5✔
198
      {aggregation, range},
5✔
199
      visConfig.opacity,
5✔
200
      data
5✔
201
    );
5✔
202
  }
5✔
203

17✔
204
  if (type === 'point') {
17✔
205
    const altitude = config.columns?.altitude;
5✔
206
    if (altitude) {
5✔
207
      result.dataTransform = data => {
1✔
208
        data.features.forEach(({geometry, properties}) => {
×
209
          const {type, coordinates} = geometry;
×
210
          if (type === 'Point') {
×
211
            coordinates[2] = properties[altitude];
×
212
          }
×
213
        });
×
214
        return data;
×
215
      };
×
216
    }
1✔
217
  }
5✔
218

17✔
219
  if (radiusField || sizeField) {
17✔
220
    result.getPointRadius = getSizeAccessor(
1✔
221
      // @ts-ignore
1✔
222
      radiusField || sizeField,
1✔
223
      // @ts-ignore
1✔
224
      radiusScale || sizeScale,
1✔
225
      visConfig.sizeAggregation,
1✔
226
      visConfig.radiusRange || visConfig.sizeRange,
1✔
227
      data
1✔
228
    );
1✔
229
  }
1✔
230

17✔
231
  if (strokeColorField) {
17✔
232
    const fallbackOpacity = type === 'point' ? visConfig.opacity : 1;
4!
233
    const opacity =
4✔
234
      visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : fallbackOpacity;
4!
235
    const {strokeColorAggregation: aggregation, strokeColorRange: range} = visConfig;
4✔
236
    result.getLineColor = getColorAccessor(
4✔
237
      strokeColorField,
4✔
238
      // @ts-ignore
4✔
239
      strokeColorScale,
4✔
240
      // @ts-ignore
4✔
241
      {aggregation, range},
4✔
242
      opacity,
4✔
243
      data
4✔
244
    );
4✔
245
  }
4✔
246
  if (heightField && visConfig.enable3d) {
17✔
247
    result.getElevation = getSizeAccessor(
1✔
248
      heightField,
1✔
249
      // @ts-ignore
1✔
250
      heightScale,
1✔
251
      visConfig.heightAggregation,
1✔
252
      visConfig.heightRange || visConfig.sizeRange,
1✔
253
      data
1✔
254
    );
1✔
255
  }
1✔
256

17✔
257
  if (weightField) {
17✔
258
    result.getWeight = getSizeAccessor(
1✔
259
      weightField,
1✔
260
      undefined,
1✔
261
      visConfig.weightAggregation,
1✔
262
      undefined,
1✔
263
      data
1✔
264
    );
1✔
265
  }
1✔
266

17✔
267
  if (visConfig.customMarkers) {
17✔
268
    const maxIconSize = getMaxMarkerSize(visConfig, visualChannels);
2✔
269
    const {getPointRadius, getFillColor} = result;
2✔
270
    const {customMarkersUrl, customMarkersRange, filled: useMaskedIcons} = visConfig;
2✔
271

2✔
272
    result.pointType = 'icon';
2✔
273
    result.getIcon = getIconUrlAccessor(
2✔
274
      visualChannels.customMarkersField,
2✔
275
      customMarkersRange,
2✔
276
      {fallbackUrl: customMarkersUrl, maxIconSize, useMaskedIcons},
2✔
277
      data
2✔
278
    );
2✔
279
    result._subLayerProps = {
2✔
280
      'points-icon': {
2✔
281
        loadOptions: {
2✔
282
          image: {
2✔
283
            type: 'imagebitmap'
2✔
284
          },
2✔
285
          imagebitmap: {
2✔
286
            resizeWidth: maxIconSize,
2✔
287
            resizeHeight: maxIconSize,
2✔
288
            resizeQuality: 'high'
2✔
289
          }
2✔
290
        }
2✔
291
      }
2✔
292
    };
2✔
293

2✔
294
    if (getFillColor && useMaskedIcons) {
2!
295
      result.getIconColor = getFillColor;
×
296
    }
×
297

2✔
298
    if (getPointRadius) {
2!
299
      result.getIconSize = getPointRadius;
×
300
    }
×
301

2✔
302
    if (visualChannels.rotationField) {
2!
303
      result.getIconAngle = negateAccessor(
×
304
        getSizeAccessor(visualChannels.rotationField, undefined, null, undefined, data)
×
305
      );
×
306
    }
×
307
  } else if (type === 'point' || type === 'tileset') {
17✔
308
    result.pointType = 'circle';
5✔
309
  }
5✔
310

17✔
311
  if (textLabel && textLabel.length && textLabel[0].field) {
17✔
312
    const [mainLabel, secondaryLabel] = textLabel;
2✔
313
    const collisionGroup = id;
2✔
314

2✔
315
    ({
2✔
316
      alignment: result.getTextAlignmentBaseline,
2✔
317
      anchor: result.getTextAnchor,
2✔
318
      color: result.getTextColor,
2✔
319
      outlineColor: result.textOutlineColor,
2✔
320
      size: result.textSizeScale
2✔
321
    } = mainLabel);
2✔
322
    const {
2✔
323
      color: getSecondaryColor,
2✔
324
      field: secondaryField,
2✔
325
      outlineColor: secondaryOutlineColor,
2✔
326
      size: secondarySizeScale
2✔
327
    } = secondaryLabel || {};
2✔
328

2✔
329
    result.getText = mainLabel.field && getTextAccessor(mainLabel.field, data);
2✔
330
    const getSecondaryText = secondaryField && getTextAccessor(secondaryField, data);
2✔
331

2✔
332
    result.pointType = `${result.pointType}+text`;
2✔
333
    result.textCharacterSet = 'auto';
2✔
334
    result.textFontFamily = 'Inter, sans';
2✔
335
    result.textFontSettings = {sdf: true};
2✔
336
    result.textFontWeight = 600;
2✔
337
    result.textOutlineWidth = 3;
2✔
338

2✔
339
    result._subLayerProps = {
2✔
340
      ...result._subLayerProps,
2✔
341
      'points-text': {
2✔
342
        // The following props are injected by default by VectorTileLayer:
2✔
343
        // type: PointLabelLayer,
2✔
344
        // extensions: [new CollisionFilterExtension()],
2✔
345
        collisionEnabled: true,
2✔
346
        collisionGroup,
2✔
347

2✔
348
        // getPointRadius already has radiusScale baked in, so only pass one or the other
2✔
349
        ...(result.getPointRadius
2!
350
          ? {getRadius: result.getPointRadius}
×
351
          : {radiusScale: visConfig.radius}),
2✔
352

2✔
353
        ...(secondaryField && {
2✔
354
          getSecondaryText,
1✔
355
          getSecondaryColor,
1✔
356
          secondarySizeScale,
1✔
357
          secondaryOutlineColor
1✔
358
        })
1✔
359
      }
2✔
360
    };
2✔
361
  }
2✔
362

17✔
363
  return result;
17✔
364
}
17✔
365

1✔
366
function createLoadOptions(accessToken: string) {
17✔
367
  return {
17✔
368
    loadOptions: {fetch: {headers: {Authorization: `Bearer ${accessToken}`}}}
17✔
369
  };
17✔
370
}
17✔
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