@deck.gl/carto 8.9.4 → 8.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/dist.dev.js +420 -222
  2. package/dist/es5/api/layer-map.js +44 -44
  3. package/dist/es5/api/layer-map.js.map +1 -1
  4. package/dist/es5/api/maps-v3-client.js +22 -4
  5. package/dist/es5/api/maps-v3-client.js.map +1 -1
  6. package/dist/es5/api/parseMap.js +64 -48
  7. package/dist/es5/api/parseMap.js.map +1 -1
  8. package/dist/es5/index.js +8 -0
  9. package/dist/es5/index.js.map +1 -1
  10. package/dist/es5/layers/point-label-layer.js +268 -0
  11. package/dist/es5/layers/point-label-layer.js.map +1 -0
  12. package/dist/es5/layers/schema/carto-spatial-tile-loader.js +1 -9
  13. package/dist/es5/layers/schema/carto-spatial-tile-loader.js.map +1 -1
  14. package/dist/es5/layers/schema/carto-spatial-tile.js +5 -31
  15. package/dist/es5/layers/schema/carto-spatial-tile.js.map +1 -1
  16. package/dist/es5/layers/schema/carto-tile.js +12 -6
  17. package/dist/es5/layers/schema/carto-tile.js.map +1 -1
  18. package/dist/es5/layers/schema/carto-vector-tile-loader.js +1 -21
  19. package/dist/es5/layers/schema/carto-vector-tile-loader.js.map +1 -1
  20. package/dist/es5/layers/schema/fast-pbf.js +22 -0
  21. package/dist/es5/layers/schema/fast-pbf.js.map +1 -0
  22. package/dist/es5/layers/schema/tile-loader-utils.js +0 -17
  23. package/dist/es5/layers/schema/tile-loader-utils.js.map +1 -1
  24. package/dist/es5/layers/spatial-index-tile-layer.js +19 -1
  25. package/dist/es5/layers/spatial-index-tile-layer.js.map +1 -1
  26. package/dist/esm/api/layer-map.js +29 -21
  27. package/dist/esm/api/layer-map.js.map +1 -1
  28. package/dist/esm/api/maps-v3-client.js +12 -1
  29. package/dist/esm/api/maps-v3-client.js.map +1 -1
  30. package/dist/esm/api/parseMap.js +59 -44
  31. package/dist/esm/api/parseMap.js.map +1 -1
  32. package/dist/esm/index.js +1 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/layers/point-label-layer.js +198 -0
  35. package/dist/esm/layers/point-label-layer.js.map +1 -0
  36. package/dist/esm/layers/schema/carto-spatial-tile-loader.js +2 -4
  37. package/dist/esm/layers/schema/carto-spatial-tile-loader.js.map +1 -1
  38. package/dist/esm/layers/schema/carto-spatial-tile.js +3 -28
  39. package/dist/esm/layers/schema/carto-spatial-tile.js.map +1 -1
  40. package/dist/esm/layers/schema/carto-tile.js +12 -6
  41. package/dist/esm/layers/schema/carto-tile.js.map +1 -1
  42. package/dist/esm/layers/schema/carto-vector-tile-loader.js +2 -18
  43. package/dist/esm/layers/schema/carto-vector-tile-loader.js.map +1 -1
  44. package/dist/esm/layers/schema/fast-pbf.js +13 -0
  45. package/dist/esm/layers/schema/fast-pbf.js.map +1 -0
  46. package/dist/esm/layers/schema/tile-loader-utils.js +0 -16
  47. package/dist/esm/layers/schema/tile-loader-utils.js.map +1 -1
  48. package/dist/esm/layers/spatial-index-tile-layer.js +21 -1
  49. package/dist/esm/layers/spatial-index-tile-layer.js.map +1 -1
  50. package/dist.min.js +24 -23
  51. package/package.json +2 -2
  52. package/src/api/layer-map.ts +21 -25
  53. package/src/api/maps-v3-client.ts +19 -4
  54. package/src/api/parseMap.ts +67 -44
  55. package/src/api/types.ts +1 -0
  56. package/src/index.ts +1 -0
  57. package/src/layers/point-label-layer.ts +266 -0
  58. package/src/layers/schema/carto-spatial-tile-loader.ts +2 -5
  59. package/src/layers/schema/carto-spatial-tile.ts +5 -30
  60. package/src/layers/schema/carto-tile.ts +11 -12
  61. package/src/layers/schema/carto-vector-tile-loader.ts +2 -9
  62. package/src/layers/schema/fast-pbf.ts +13 -0
  63. package/src/layers/schema/tile-loader-utils.ts +0 -15
  64. package/src/layers/spatial-index-tile-layer.ts +22 -1
  65. package/typed/api/layer-map.d.ts +1 -2
  66. package/typed/api/layer-map.d.ts.map +1 -1
  67. package/typed/api/maps-v3-client.d.ts +4 -1
  68. package/typed/api/maps-v3-client.d.ts.map +1 -1
  69. package/typed/api/parseMap.d.ts +1 -1
  70. package/typed/api/parseMap.d.ts.map +1 -1
  71. package/typed/api/types.d.ts +1 -0
  72. package/typed/api/types.d.ts.map +1 -1
  73. package/typed/index.d.ts +1 -0
  74. package/typed/index.d.ts.map +1 -1
  75. package/typed/layers/point-label-layer.d.ts +56 -0
  76. package/typed/layers/point-label-layer.d.ts.map +1 -0
  77. package/typed/layers/schema/carto-spatial-tile-loader.d.ts.map +1 -1
  78. package/typed/layers/schema/carto-spatial-tile.d.ts +2 -2
  79. package/typed/layers/schema/carto-spatial-tile.d.ts.map +1 -1
  80. package/typed/layers/schema/carto-tile.d.ts +2 -9
  81. package/typed/layers/schema/carto-tile.d.ts.map +1 -1
  82. package/typed/layers/schema/carto-vector-tile-loader.d.ts.map +1 -1
  83. package/typed/layers/schema/fast-pbf.d.ts +3 -0
  84. package/typed/layers/schema/fast-pbf.d.ts.map +1 -0
  85. package/typed/layers/schema/tile-loader-utils.d.ts +0 -3
  86. package/typed/layers/schema/tile-loader-utils.d.ts.map +1 -1
  87. package/typed/layers/spatial-index-tile-layer.d.ts +1 -0
  88. package/typed/layers/spatial-index-tile-layer.d.ts.map +1 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@deck.gl/carto",
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
- "version": "8.9.4",
5
+ "version": "8.9.6",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -62,5 +62,5 @@
62
62
  "@deck.gl/layers": "^8.0.0",
63
63
  "@loaders.gl/core": "^3.0.0"
64
64
  },
65
- "gitHead": "b5a41f34a66d7ebf089fc703b805157d176cf41b"
65
+ "gitHead": "539cc9960aa47bbe22f607a5de49e943f6765d61"
66
66
  }
@@ -27,7 +27,6 @@ import {
27
27
  CustomMarkersRange,
28
28
  MapDataset,
29
29
  MapTextSubLayerConfig,
30
- TextLabel,
31
30
  VisConfig,
32
31
  VisualChannelField,
33
32
  VisualChannels
@@ -140,7 +139,7 @@ export function getLayer(
140
139
  ): {Layer: ConstructorOf<Layer>; propMap: any; defaultProps: any} {
141
140
  let basePropMap: any = sharedPropMap;
142
141
 
143
- if (config.visConfig?.customMarkers && !config.textLabel) {
142
+ if (config.visConfig?.customMarkers) {
144
143
  basePropMap = mergePropMaps(sharedPropMap, customMarkersPropsMap);
145
144
  }
146
145
  if (type === 'mvt' || type === 'tileset' || type === 'h3' || type === 'quadbin') {
@@ -296,8 +295,26 @@ function calculateDomain(data, name, scaleType, scaleLength?) {
296
295
 
297
296
  function normalizeAccessor(accessor, data) {
298
297
  if (data.features || data.tilestats) {
299
- return ({properties}) => {
300
- return accessor(properties);
298
+ return (object, info) => {
299
+ if (object) {
300
+ return accessor(object.properties || object.__source.object.properties);
301
+ }
302
+
303
+ const {data, index} = info;
304
+ const {properties, numericProps} = data;
305
+ const proxy = new Proxy(properties[index] || {}, {
306
+ get(target, property, receiver) {
307
+ if (property in numericProps) {
308
+ return numericProps[property as string].value[index];
309
+ }
310
+ return target[property as string];
311
+ },
312
+
313
+ has(target, property) {
314
+ return property in numericProps || property in target;
315
+ }
316
+ });
317
+ return accessor(proxy);
301
318
  };
302
319
  }
303
320
  return accessor;
@@ -468,25 +485,4 @@ export function getTextAccessor({name, type}: VisualChannelField, data) {
468
485
  return normalizeAccessor(accessor, data);
469
486
  }
470
487
 
471
- export function getTextPixelOffsetAccessor(
472
- {alignment, anchor, size}: TextLabel,
473
- radius
474
- ): Accessor<unknown, [number, number]> {
475
- const padding = 20;
476
- const signX = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1;
477
- const signY = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1;
478
- const sizeOffset = alignment === 'center' ? 0 : size;
479
-
480
- const calculateOffset = (r: number): [number, number] => [
481
- signX * (r + padding),
482
- signY * (r + padding + sizeOffset)
483
- ];
484
-
485
- return typeof radius === 'function'
486
- ? d => {
487
- return calculateOffset(radius(d));
488
- }
489
- : calculateOffset(radius);
490
- }
491
-
492
488
  export {domainFromValues as _domainFromValues};
@@ -534,6 +534,7 @@ async function fillInTileStats(
534
534
  return await Promise.all(promises);
535
535
  }
536
536
 
537
+ /* eslint-disable max-statements */
537
538
  export async function fetchMap({
538
539
  cartoMapId,
539
540
  clientId,
@@ -613,8 +614,22 @@ export async function fetchMap({
613
614
 
614
615
  // Mutates attributes in visualChannels to contain tile stats
615
616
  await fillInTileStats(map, localCreds);
616
- return {
617
- ...parseMap(map),
618
- ...{stopAutoRefresh}
619
- };
617
+ const out = {...parseMap(map), ...{stopAutoRefresh}};
618
+
619
+ const textLayers = out.layers.filter(layer => {
620
+ const pointType = layer.props.pointType || '';
621
+ return pointType.includes('text');
622
+ });
623
+
624
+ /* global FontFace, window, document */
625
+ if (textLayers.length && window.FontFace && !document.fonts.check('12px Inter')) {
626
+ // Fetch font needed for labels
627
+ const font = new FontFace(
628
+ 'Inter',
629
+ 'url(https://fonts.gstatic.com/s/inter/v12/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2)'
630
+ );
631
+ await font.load().then(f => document.fonts.add(f));
632
+ }
633
+
634
+ return out;
620
635
  }
@@ -6,16 +6,19 @@ import {
6
6
  getColorValueAccessor,
7
7
  getSizeAccessor,
8
8
  getTextAccessor,
9
- getTextPixelOffsetAccessor,
10
9
  OPACITY_MAP,
11
10
  opacityToAlpha,
12
11
  getIconUrlAccessor,
13
12
  negateAccessor,
14
13
  getMaxMarkerSize
15
14
  } from './layer-map';
16
- import {_flatten as flatten, log} from '@deck.gl/core';
15
+ import PointLabelLayer from '../layers/point-label-layer';
16
+ import {log} from '@deck.gl/core';
17
+ import {CollisionFilterExtension} from '@deck.gl/extensions';
17
18
  import {assert} from '../utils';
18
- import {MapDataset, MapTextSubLayerConfig, VisualChannels} from './types';
19
+ import {MapDataset, MapLayerConfig, VisualChannels} from './types';
20
+
21
+ const collisionFilterExtension = new CollisionFilterExtension();
19
22
 
20
23
  export function parseMap(json) {
21
24
  const {keplerMapConfig, datasets, token} = json;
@@ -32,7 +35,7 @@ export function parseMap(json) {
32
35
  initialViewState: mapState,
33
36
  mapStyle,
34
37
  token,
35
- layers: extractTextLayers(layers.reverse()).map(({id, type, config, visualChannels}) => {
38
+ layers: layers.reverse().map(({id, type, config, visualChannels}) => {
36
39
  try {
37
40
  const {dataId} = config;
38
41
  const dataset: MapDataset | null = datasets.find(d => d.id === dataId);
@@ -45,9 +48,9 @@ export function parseMap(json) {
45
48
  id,
46
49
  data,
47
50
  ...defaultProps,
48
- ...(!config.textLabel && createInteractionProps(interactionConfig)),
51
+ ...createInteractionProps(interactionConfig),
49
52
  ...styleProps,
50
- ...createChannelProps(visualChannels, type, config, data), // Must come after style
53
+ ...createChannelProps(id, type, config, visualChannels, data), // Must come after style
51
54
  ...createParametersProp(layerBlending, styleProps.parameters || {}), // Must come after style
52
55
  ...createLoadOptions(token)
53
56
  });
@@ -59,34 +62,6 @@ export function parseMap(json) {
59
62
  };
60
63
  }
61
64
 
62
- function extractTextLayers(layers) {
63
- return flatten(
64
- layers.map(({id, config, ...rest}) => {
65
- const {textLabel, ...configRest} = config;
66
- return [
67
- // Original layer without textLabel
68
- {id, config: configRest, ...rest},
69
-
70
- // One layer per valid text label, with full opacity
71
- ...textLabel
72
- .filter(t => t.field)
73
- .map(t => {
74
- return {
75
- id: `${id}-label-${t.field.name}`,
76
- config: {
77
- textLabel: t,
78
- ...configRest,
79
- label: `${config.label}-label-${t.field.name}`,
80
- visConfig: {...configRest.visConfig, opacity: 1}
81
- },
82
- ...rest
83
- };
84
- })
85
- ];
86
- })
87
- );
88
- }
89
-
90
65
  function createParametersProp(layerBlending, parameters: Record<string, any>) {
91
66
  if (layerBlending === 'additive') {
92
67
  parameters.blendFunc = [GL.SRC_ALPHA, GL.DST_ALPHA];
@@ -127,7 +102,7 @@ function mapProps(source, target, mapping) {
127
102
  }
128
103
  }
129
104
 
130
- function createStyleProps(config: MapTextSubLayerConfig, mapping) {
105
+ function createStyleProps(config: MapLayerConfig, mapping) {
131
106
  const result: Record<string, any> = {};
132
107
  mapProps(config, result, mapping);
133
108
 
@@ -153,9 +128,10 @@ function createStyleProps(config: MapTextSubLayerConfig, mapping) {
153
128
 
154
129
  /* eslint-disable complexity, max-statements */
155
130
  function createChannelProps(
156
- visualChannels: VisualChannels,
131
+ id: string,
157
132
  type: string,
158
- config: MapTextSubLayerConfig,
133
+ config: MapLayerConfig,
134
+ visualChannels: VisualChannels,
159
135
  data
160
136
  ) {
161
137
  const {
@@ -175,7 +151,6 @@ function createChannelProps(
175
151
  }
176
152
  const {textLabel, visConfig} = config;
177
153
  const result: Record<string, any> = {};
178
- const textLabelField = textLabel && textLabel.field;
179
154
 
180
155
  if (type === 'grid' || type === 'hexagon') {
181
156
  result.colorScaleType = colorScale;
@@ -253,12 +228,7 @@ function createChannelProps(
253
228
  );
254
229
  }
255
230
 
256
- if (textLabelField) {
257
- result.getText = getTextAccessor(textLabelField, data);
258
- result.pointType = 'text';
259
- const radius = result.getPointRadius || visConfig.radius;
260
- result.getTextPixelOffset = getTextPixelOffsetAccessor(textLabel, radius);
261
- } else if (visConfig.customMarkers) {
231
+ if (visConfig.customMarkers) {
262
232
  const maxIconSize = getMaxMarkerSize(visConfig, visualChannels);
263
233
  const {getPointRadius, getFillColor} = result;
264
234
  const {customMarkersUrl, customMarkersRange, filled: useMaskedIcons} = visConfig;
@@ -298,6 +268,59 @@ function createChannelProps(
298
268
  getSizeAccessor(visualChannels.rotationField, undefined, null, undefined, data)
299
269
  );
300
270
  }
271
+ } else if (type === 'point' || type === 'tileset') {
272
+ result.pointType = 'circle';
273
+ }
274
+
275
+ if (textLabel && textLabel.length && textLabel[0].field) {
276
+ const [mainLabel, secondaryLabel] = textLabel;
277
+ const collisionGroup = id;
278
+
279
+ ({
280
+ alignment: result.getTextAlignmentBaseline,
281
+ anchor: result.getTextAnchor,
282
+ color: result.getTextColor,
283
+ outlineColor: result.textOutlineColor,
284
+ size: result.textSizeScale
285
+ } = mainLabel);
286
+ const {
287
+ color: getSecondaryColor,
288
+ field: secondaryField,
289
+ outlineColor: secondaryOutlineColor,
290
+ size: secondarySizeScale
291
+ } = secondaryLabel || {};
292
+
293
+ result.getText = mainLabel.field && getTextAccessor(mainLabel.field, data);
294
+ const getSecondaryText = secondaryField && getTextAccessor(secondaryField, data);
295
+
296
+ result.pointType = `${result.pointType}+text`;
297
+ result.textCharacterSet = 'auto';
298
+ result.textFontFamily = 'Inter, sans';
299
+ result.textFontSettings = {sdf: true};
300
+ result.textFontWeight = 600;
301
+ result.textOutlineWidth = 3;
302
+
303
+ result._subLayerProps = {
304
+ ...result._subLayerProps,
305
+ 'points-text': {
306
+ type: PointLabelLayer,
307
+ extensions: [collisionFilterExtension],
308
+ collisionEnabled: true,
309
+ collisionGroup,
310
+
311
+ // getPointRadius already has radiusScale baked in, so only pass one or the other
312
+ ...(result.getPointRadius
313
+ ? {getRadius: result.getPointRadius}
314
+ : {radiusScale: visConfig.radius}),
315
+
316
+ ...(secondaryField && {
317
+ getSecondaryText,
318
+ getSecondaryColor,
319
+ secondarySizeScale,
320
+ secondaryOutlineColor
321
+ })
322
+ }
323
+ };
301
324
  }
302
325
 
303
326
  return result;
package/src/api/types.ts CHANGED
@@ -77,6 +77,7 @@ export type TextLabel = {
77
77
  size: number;
78
78
  color?: number[];
79
79
  offset?: [number, number];
80
+ outlineColor?: number[];
80
81
  };
81
82
 
82
83
  export type MapLayerConfig = {
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export {getDefaultCredentials, setDefaultCredentials} from './config';
2
2
  export {default as CartoLayer} from './layers/carto-layer';
3
3
  export {default as _CartoTileLayer} from './layers/carto-tile-layer';
4
4
  export {default as _H3TileLayer} from './layers/h3-tile-layer';
5
+ export {default as _PointLabelLayer} from './layers/point-label-layer';
5
6
  export {default as _QuadbinTileLayer} from './layers/quadbin-tile-layer';
6
7
  export {default as BASEMAP} from './basemap';
7
8
  export {default as colorBins} from './style/color-bins-style';
@@ -0,0 +1,266 @@
1
+ import {
2
+ Accessor,
3
+ Color,
4
+ CompositeLayer,
5
+ CompositeLayerProps,
6
+ DefaultProps,
7
+ Layer,
8
+ LayersList
9
+ } from '@deck.gl/core';
10
+ import {
11
+ TextLayer,
12
+ TextLayerProps,
13
+ _TextBackgroundLayer as TextBackgroundLayer
14
+ } from '@deck.gl/layers';
15
+
16
+ const [LEFT, TOP, RIGHT, BOTTOM] = [0, 1, 2, 3];
17
+
18
+ class EnhancedTextBackgroundLayer extends TextBackgroundLayer {
19
+ static layerName = 'EnhancedTextBackgroundLayer';
20
+
21
+ getShaders() {
22
+ const shaders = super.getShaders();
23
+ let vs = shaders.vs;
24
+
25
+ // Modify shader so that the padding is offset by the pixel offset to ensure the padding
26
+ // always captures the anchor point. As padding is uniform we cannot pass it a per-label value
27
+ vs = vs.replaceAll('padding.', '_padding.');
28
+ vs = vs.replace(
29
+ 'void main(void) {',
30
+ 'void main(void) {\n vec4 _padding = padding + instancePixelOffsets.xyxy * vec4(1.0, 1.0, -1.0, -1.0);'
31
+ );
32
+
33
+ return {...shaders, vs};
34
+ }
35
+ }
36
+
37
+ // TextLayer which includes modified text-background-layer-vertex shader and only renders the
38
+ // primary background layer in the collision pass
39
+ class EnhancedTextLayer extends TextLayer {
40
+ static layerName = 'EnhancedTextLayer';
41
+
42
+ filterSubLayer({layer, renderPass}) {
43
+ const background = layer.id.includes('primary-background');
44
+ if (renderPass === 'collision') {
45
+ return background; // Only draw primary background layer in collision pass
46
+ }
47
+
48
+ return !background; // Do not draw background layer in other passes
49
+ }
50
+ }
51
+
52
+ const defaultProps: DefaultProps<PointLabelLayerProps> = {
53
+ ...TextLayer.defaultProps,
54
+ getRadius: {type: 'accessor', value: 1},
55
+ radiusScale: {type: 'number', min: 0, value: 1}
56
+ };
57
+
58
+ /** All properties supported by PointLabelLayer. */
59
+ export type PointLabelLayerProps<DataT = any> = _PointLabelLayerProps<DataT> &
60
+ TextLayerProps &
61
+ CompositeLayerProps;
62
+
63
+ /** Properties added by PointLabelLayer. */
64
+ type _PointLabelLayerProps<DataT> = TextLayerProps<DataT> & {
65
+ /**
66
+ * Radius multiplier.
67
+ * @default 1
68
+ */
69
+ radiusScale?: number;
70
+ /**
71
+ * Radius accessor.
72
+ * @default 1
73
+ */
74
+ getRadius?: Accessor<DataT, number>;
75
+ /**
76
+ * Secondary label text accessor
77
+ */
78
+ getSecondaryText?: Accessor<DataT, string>;
79
+ /**
80
+ * Secondary label color accessor
81
+ * @default [0, 0, 0, 255]
82
+ */
83
+ getSecondaryColor?: Accessor<DataT, Color>;
84
+ /**
85
+ * Secondary label color of outline around the text, in `[r, g, b, [a]]`. Each channel is a number between 0-255 and `a` is 255 if not supplied.
86
+ * @default [0, 0, 0, 255]
87
+ */
88
+ secondaryOutlineColor?: Color;
89
+ /**
90
+ * Secondary label text size multiplier.
91
+ * @default 1
92
+ */
93
+ secondarySizeScale?: number;
94
+ };
95
+
96
+ export default class PointLabelLayer<
97
+ DataT = any,
98
+ ExtraProps extends {} = {}
99
+ > extends CompositeLayer<ExtraProps & Required<_PointLabelLayerProps<DataT>>> {
100
+ static layerName = 'PointLabelLayer';
101
+ static defaultProps = defaultProps;
102
+
103
+ calculatePixelOffset(secondary) {
104
+ const {
105
+ getTextAnchor: anchor,
106
+ getAlignmentBaseline: alignment,
107
+ getRadius,
108
+ getSecondaryText,
109
+ radiusScale,
110
+ secondarySizeScale,
111
+ sizeScale
112
+ } = this.props;
113
+ const xMult = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1;
114
+ const yMult = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1;
115
+
116
+ // Padding based on font size (font size / 4)
117
+ const xPadding = sizeScale / 4;
118
+ const yPadding = sizeScale * (1 + 1 / 4);
119
+
120
+ // Place secondary label under main label (secondary label always 'top' baseline aligned)
121
+ const secondaryOffset = 0.6 * (1 - yMult) * sizeScale;
122
+ let yOffset = secondary ? secondaryOffset : 0;
123
+
124
+ // Special case, position relative to secondary label
125
+ if (anchor === 'middle' && alignment === 'top' && getSecondaryText) {
126
+ yOffset -= secondaryOffset;
127
+ yOffset -= secondarySizeScale;
128
+ yOffset += sizeScale;
129
+ }
130
+
131
+ // Padding based on point radius (radius/ 4)
132
+ const radiusPadding = 1 + 1 / 4;
133
+
134
+ return typeof getRadius === 'function'
135
+ ? (d, info) => {
136
+ const r = (info ? getRadius(d, info) : 1) * radiusScale * radiusPadding;
137
+ return [xMult * (r + xPadding), yMult * (r + yPadding) + yOffset];
138
+ }
139
+ : [
140
+ xMult * (getRadius * radiusScale * radiusPadding + xPadding),
141
+ yMult * (getRadius * radiusScale * radiusPadding + yPadding) + yOffset
142
+ ];
143
+ }
144
+
145
+ calculateBackgroundPadding() {
146
+ const {getTextAnchor: anchor, getAlignmentBaseline: alignment, sizeScale} = this.props;
147
+
148
+ // Heuristics to avoid label overlap
149
+ const paddingX = 12 * sizeScale;
150
+ const paddingY = 3 * sizeScale;
151
+ const backgroundPadding = [0, 0, 0, 0];
152
+ if (alignment === 'top') {
153
+ backgroundPadding[TOP] = paddingY;
154
+ } else if (alignment === 'bottom') {
155
+ backgroundPadding[BOTTOM] = paddingY;
156
+ } else {
157
+ backgroundPadding[TOP] = 0.5 * paddingY;
158
+ backgroundPadding[BOTTOM] = 0.5 * paddingY;
159
+ }
160
+ if (anchor === 'start') {
161
+ backgroundPadding[LEFT] = paddingX;
162
+ } else if (anchor === 'end') {
163
+ backgroundPadding[RIGHT] = paddingX;
164
+ } else {
165
+ backgroundPadding[LEFT] = 0.5 * paddingX;
166
+ backgroundPadding[RIGHT] = 0.5 * paddingX;
167
+ }
168
+
169
+ return backgroundPadding;
170
+ }
171
+
172
+ renderTextLayer(id, {updateTriggers: updateTriggersOverride = {}, ...props}): EnhancedTextLayer {
173
+ const {
174
+ data,
175
+
176
+ characterSet,
177
+ fontFamily,
178
+ fontSettings,
179
+ fontWeight,
180
+ outlineColor,
181
+ outlineWidth,
182
+ sizeScale,
183
+ radiusScale,
184
+
185
+ getAlignmentBaseline,
186
+ getColor,
187
+ getPosition,
188
+ getTextAnchor,
189
+
190
+ updateTriggers
191
+ } = this.props;
192
+
193
+ return new EnhancedTextLayer(
194
+ this.getSubLayerProps({
195
+ id,
196
+ data,
197
+
198
+ characterSet,
199
+ fontFamily,
200
+ fontSettings,
201
+ fontWeight,
202
+ outlineColor,
203
+ outlineWidth,
204
+ sizeScale,
205
+
206
+ getAlignmentBaseline,
207
+ getColor,
208
+ getPosition,
209
+ getTextAnchor,
210
+
211
+ updateTriggers: {
212
+ ...updateTriggers,
213
+ ...updateTriggersOverride,
214
+ getPixelOffset: [
215
+ updateTriggers.getRadius,
216
+ updateTriggers.getTextAnchor,
217
+ updateTriggers.getAlignmentBaseline,
218
+ radiusScale,
219
+ sizeScale
220
+ ]
221
+ }
222
+ }),
223
+ {
224
+ getSize: 1,
225
+ _subLayerProps: {background: {type: EnhancedTextBackgroundLayer}}
226
+ },
227
+ props
228
+ );
229
+ }
230
+
231
+ renderLayers(): Layer | null | LayersList {
232
+ const {
233
+ getText,
234
+ getSecondaryColor,
235
+ getSecondaryText,
236
+ secondaryOutlineColor,
237
+ secondarySizeScale,
238
+ updateTriggers
239
+ } = this.props;
240
+ const getPixelOffset = this.calculatePixelOffset(false);
241
+ const backgroundPadding = this.calculateBackgroundPadding();
242
+ const out = [
243
+ // Text doesn't update via updateTrigger for some reason
244
+ this.renderTextLayer(`${updateTriggers.getText}-primary`, {
245
+ backgroundPadding,
246
+ getText,
247
+ getPixelOffset,
248
+ background: true // Only use background for primary label for faster collisions
249
+ }),
250
+ Boolean(getSecondaryText) &&
251
+ this.renderTextLayer(`${updateTriggers.getSecondaryText}-secondary`, {
252
+ getText: getSecondaryText,
253
+ getPixelOffset: this.calculatePixelOffset(true),
254
+ getAlignmentBaseline: 'top',
255
+ // updateTriggers: {getText: updateTriggers.getSecondaryText},
256
+
257
+ // Optional overrides
258
+ ...(getSecondaryColor && {getColor: getSecondaryColor}),
259
+ ...(secondarySizeScale && {sizeScale: secondarySizeScale}),
260
+ ...(secondaryOutlineColor && {outlineColor: secondaryOutlineColor})
261
+ })
262
+ ];
263
+
264
+ return out;
265
+ }
266
+ }
@@ -1,7 +1,7 @@
1
1
  import {LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
2
2
 
3
3
  import {Tile, TileReader} from './carto-spatial-tile';
4
- import {parsePbf, unpackProperties} from './tile-loader-utils';
4
+ import {parsePbf} from './tile-loader-utils';
5
5
  import {IndexScheme, binaryToSpatialjson, SpatialJson} from './spatialjson-utils';
6
6
 
7
7
  const CartoSpatialTileLoader: LoaderWithParser = {
@@ -31,10 +31,7 @@ function parseCartoSpatialTile(
31
31
 
32
32
  const {cells} = tile;
33
33
  const scheme = options?.cartoSpatialTile?.scheme;
34
- const data = {
35
- cells: {...cells, properties: unpackProperties(cells.properties)},
36
- scheme
37
- };
34
+ const data = {cells, scheme};
38
35
 
39
36
  return binaryToSpatialjson(data);
40
37
  }
@@ -1,20 +1,15 @@
1
+ import {readPackedFixed64} from './fast-pbf';
1
2
  import {Indices, IndexScheme} from './spatialjson-utils';
2
- import {
3
- KeyValueProperties,
4
- NumericProp,
5
- NumericPropKeyValueReader,
6
- PropertiesReader
7
- } from './carto-tile';
3
+ import {NumericProp, NumericPropKeyValueReader, PropertiesReader} from './carto-tile';
8
4
 
9
5
  // Indices =====================================
10
6
 
11
7
  export class IndicesReader {
12
8
  static read(pbf, end?: number): Indices {
13
- const {value} = pbf.readFields(IndicesReader._readField, {value: []}, end);
14
- return {value: new BigUint64Array(value)};
9
+ return pbf.readFields(IndicesReader._readField, {value: []}, end);
15
10
  }
16
11
  static _readField(this: void, tag: number, obj, pbf) {
17
- if (tag === 1) readPackedFixed64(pbf, obj.value);
12
+ if (tag === 1) readPackedFixed64(pbf, obj);
18
13
  }
19
14
  }
20
15
 
@@ -22,7 +17,7 @@ export class IndicesReader {
22
17
 
23
18
  interface Cells {
24
19
  indices: Indices;
25
- properties: KeyValueProperties[];
20
+ properties: Record<string, string>[];
26
21
  numericProps: Record<string, NumericProp>;
27
22
  }
28
23
 
@@ -61,23 +56,3 @@ export class TileReader {
61
56
  else if (tag === 2) obj.cells = CellsReader.read(pbf, pbf.readVarint() + pbf.pos);
62
57
  }
63
58
  }
64
-
65
- // pbf doesn't support BigInt natively, implement support for packed fixed64 type
66
- const SHIFT_LEFT_32 = BigInt((1 << 16) * (1 << 16));
67
-
68
- function readPackedEnd(pbf) {
69
- return pbf.type === 2 ? pbf.readVarint() + pbf.pos : pbf.pos + 1;
70
- }
71
- function readFixed64(pbf) {
72
- const a = BigInt(pbf.readFixed32());
73
- const b = BigInt(pbf.readFixed32());
74
- return a + b * SHIFT_LEFT_32;
75
- }
76
-
77
- function readPackedFixed64(pbf, arr) {
78
- if (pbf.type !== 2) return arr.push(readFixed64(pbf));
79
- const end = readPackedEnd(pbf);
80
- arr = arr || [];
81
- while (pbf.pos < end) arr.push(readFixed64(pbf));
82
- return arr;
83
- }