@deck.gl/carto 9.0.26 → 9.0.28

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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.0.26",
6
+ "version": "9.0.28",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -72,5 +72,5 @@
72
72
  "@deck.gl/layers": "^9.0.0",
73
73
  "@loaders.gl/core": "^4.2.0"
74
74
  },
75
- "gitHead": "207f67da86ca13e92eee6b96a934b76f92926d4c"
75
+ "gitHead": "58a2526b9b910d3ce086d627643839c3cd8ee944"
76
76
  }
@@ -122,26 +122,33 @@ class ClusterGeoJsonLayer<
122
122
 
123
123
  const properties = extractAggregationProperties(visibleTiles[0]);
124
124
  const data = [] as ClusteredFeaturePropertiesT<FeaturePropertiesT>[];
125
+ let needsUpdate = false;
125
126
  for (const tile of visibleTiles) {
126
127
  // Calculate aggregation based on viewport zoom
127
128
  const overZoom = Math.round(zoom - tile.zoom);
128
129
  const aggregationLevels = Math.round(clusterLevel) - overZoom;
129
- aggregateTile(tile, aggregationLevels, properties, getPosition, getWeight);
130
+ const didAggregate = aggregateTile(
131
+ tile,
132
+ aggregationLevels,
133
+ properties,
134
+ getPosition,
135
+ getWeight
136
+ );
137
+ needsUpdate ||= didAggregate;
130
138
  data.push(...tile.userData![aggregationLevels]);
131
139
  }
132
140
 
133
141
  data.sort((a, b) => Number(b.count - a.count));
134
142
 
135
143
  const clusterIds = data?.map((tile: any) => tile.id);
136
- const needsUpdate = !deepEqual(clusterIds, this.state.clusterIds, 1);
144
+ needsUpdate ||= !deepEqual(clusterIds, this.state.clusterIds, 1);
137
145
  this.setState({clusterIds});
138
146
 
139
147
  if (needsUpdate) {
140
148
  const stats = computeAggregationStats(data, properties);
141
- for (const d of data) {
142
- d.stats = stats;
143
- }
144
- this.setState({data: clustersToBinary(data)});
149
+ const binaryData = clustersToBinary(data);
150
+ binaryData.points.attributes = {stats};
151
+ this.setState({data: binaryData});
145
152
  }
146
153
 
147
154
  const props = {
@@ -1,6 +1,6 @@
1
1
  import {cellToParent} from 'quadbin';
2
2
  import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers';
3
- import {Accessor} from '@deck.gl/core';
3
+ import {Accessor, log} from '@deck.gl/core';
4
4
  import {BinaryFeatureCollection} from '@loaders.gl/schema';
5
5
 
6
6
  export type Aggregation = 'any' | 'average' | 'count' | 'min' | 'max' | 'sum';
@@ -12,23 +12,37 @@ export type ClusteredFeaturePropertiesT<FeaturePropertiesT> = FeaturePropertiesT
12
12
  id: bigint;
13
13
  count: number;
14
14
  position: [number, number];
15
- stats: Record<keyof FeaturePropertiesT, {min: number; max: number}>;
16
15
  };
17
16
  export type ParsedQuadbinCell<FeaturePropertiesT> = {id: bigint; properties: FeaturePropertiesT};
18
17
  export type ParsedQuadbinTile<FeaturePropertiesT> = ParsedQuadbinCell<FeaturePropertiesT>[];
19
18
 
19
+ /**
20
+ * Aggregates tile by specified properties, caching result in tile.userData
21
+ *
22
+ * @returns true if data was aggregated, false if cache used
23
+ */
20
24
  export function aggregateTile<FeaturePropertiesT>(
21
25
  tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>,
22
26
  aggregationLevels: number,
23
27
  properties: AggregationProperties<FeaturePropertiesT> = [],
24
28
  getPosition: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>,
25
29
  getWeight: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, number>
26
- ): void {
27
- if (!tile.content) return;
30
+ ): boolean {
31
+ if (!tile.content) return false;
28
32
 
29
33
  // Aggregate on demand and cache result
30
34
  if (!tile.userData) tile.userData = {};
31
- if (tile.userData[aggregationLevels]) return;
35
+ const cell0 = tile.userData[aggregationLevels]?.[0];
36
+ if (cell0) {
37
+ // Have already aggregated this tile
38
+ if (properties.every(property => property.name in cell0)) {
39
+ // Use cached result
40
+ return false;
41
+ }
42
+
43
+ // Aggregated properties have changed, re-aggregate
44
+ tile.userData = {};
45
+ }
32
46
 
33
47
  const out: Record<number, any> = {};
34
48
  for (const cell of tile.content) {
@@ -80,6 +94,7 @@ export function aggregateTile<FeaturePropertiesT>(
80
94
  }
81
95
 
82
96
  tile.userData[aggregationLevels] = Object.values(out);
97
+ return true;
83
98
  }
84
99
 
85
100
  export function extractAggregationProperties<FeaturePropertiesT extends {}>(
@@ -90,6 +105,7 @@ export function extractAggregationProperties<FeaturePropertiesT extends {}>(
90
105
  for (const name of Object.keys(tile.content![0].properties)) {
91
106
  let aggregation = name.split('_').pop()!.toLowerCase() as Aggregation;
92
107
  if (!validAggregations.includes(aggregation)) {
108
+ log.warn(`No valid aggregation present in ${name} property`)();
93
109
  aggregation = 'any';
94
110
  }
95
111
  properties.push({name: name as keyof FeaturePropertiesT, aggregation});
@@ -125,9 +141,20 @@ const EMPTY_BINARY_PROPS = {
125
141
  globalFeatureIds: {value: EMPTY_UINT16ARRAY, size: 1}
126
142
  };
127
143
 
144
+ type BinaryFeatureCollectionWithStats<FeaturePropertiesT> = Omit<
145
+ BinaryFeatureCollection,
146
+ 'points'
147
+ > & {
148
+ points: BinaryFeatureCollection['points'] & {
149
+ attributes?: {
150
+ stats: Record<keyof FeaturePropertiesT, {min: number; max: number}>;
151
+ };
152
+ };
153
+ };
154
+
128
155
  export function clustersToBinary<FeaturePropertiesT>(
129
156
  data: ClusteredFeaturePropertiesT<FeaturePropertiesT>[]
130
- ): BinaryFeatureCollection {
157
+ ): BinaryFeatureCollectionWithStats<FeaturePropertiesT> {
131
158
  const positions = new Float32Array(data.length * 2);
132
159
  const featureIds = new Uint16Array(data.length);
133
160
  for (let i = 0; i < data.length; i++) {
@@ -42,8 +42,8 @@ export default function colorBins<DataT = Feature>({
42
42
 
43
43
  const color = scaleThreshold<number, Color>().domain(domain).range(palette);
44
44
 
45
- return d => {
46
- const value = getAttrValue(attr, d);
45
+ return (d, info) => {
46
+ const value = getAttrValue(attr, d, info);
47
47
  return typeof value === 'number' && Number.isFinite(value) ? color(value) : nullColor;
48
48
  };
49
49
  }
@@ -48,8 +48,8 @@ export default function colorCategories<DataT = Feature>({
48
48
  colorsByCategory[c] = palette[i];
49
49
  }
50
50
 
51
- return d => {
52
- const value = getAttrValue(attr, d);
51
+ return (d, info) => {
52
+ const value = getAttrValue(attr, d, info);
53
53
  return (typeof value === 'number' && Number.isFinite(value)) || typeof value === 'string'
54
54
  ? colorsByCategory[value] || othersColor
55
55
  : nullColor;
@@ -41,8 +41,8 @@ export default function colorContinuous<DataT = Feature>({
41
41
  const palette = typeof colors === 'string' ? getPalette(colors, domain.length) : colors;
42
42
  const color = scaleLinear<Color>().domain(domain).range(palette);
43
43
 
44
- return d => {
45
- const value = getAttrValue(attr, d);
44
+ return (d, info) => {
45
+ const value = getAttrValue(attr, d, info);
46
46
  return typeof value === 'number' && Number.isFinite(value) ? color(value) : nullColor;
47
47
  };
48
48
  }
@@ -3,18 +3,21 @@ import {assert} from '../utils';
3
3
 
4
4
  const ALLOWED_ATTR_TYPES = Object.freeze(['function', 'string']);
5
5
 
6
- export type AttributeSelector<DataT = Feature, OutT = any> = string | ((d: DataT) => OutT);
6
+ export type AttributeSelector<DataT = Feature, OutT = any> =
7
+ | string
8
+ | ((d: DataT, info: any) => OutT);
7
9
 
8
10
  export function getAttrValue<DataT = Feature, OutT = any>(
9
11
  attr: string | AttributeSelector<DataT, OutT>,
10
- d: DataT
12
+ d: DataT,
13
+ info: any
11
14
  ): OutT {
12
15
  assert(typeof d === 'object', 'Expected "data" to be an object');
13
16
  assert(ALLOWED_ATTR_TYPES.includes(typeof attr), 'Expected "attr" to be a function or string');
14
17
 
15
18
  // Is function
16
19
  if (typeof attr === 'function') {
17
- return attr(d);
20
+ return attr(d, info);
18
21
  }
19
22
  return (d as unknown as Feature)?.properties?.[attr] as OutT;
20
23
  }