@deck.gl/carto 9.0.21 → 9.0.24
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/dist/api/layer-map.d.ts +4 -6
- package/dist/api/layer-map.d.ts.map +1 -1
- package/dist/api/layer-map.js +16 -17
- package/dist/cartoRasterTile-worker.js +1 -1
- package/dist/cartoSpatialTile-worker.js +1 -1
- package/dist/cartoVectorTile-worker.js +1 -1
- package/dist/dist.dev.js +1982 -1606
- package/dist/index.cjs +747 -517
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/layers/cluster-tile-layer.d.ts +41 -0
- package/dist/layers/cluster-tile-layer.d.ts.map +1 -0
- package/dist/layers/cluster-tile-layer.js +116 -0
- package/dist/layers/cluster-utils.d.ts +30 -0
- package/dist/layers/cluster-utils.d.ts.map +1 -0
- package/dist/layers/cluster-utils.js +122 -0
- package/dist/layers/schema/carto-raster-tile-loader.js +1 -1
- package/dist/layers/schema/carto-spatial-tile-loader.js +1 -1
- package/dist/layers/schema/carto-vector-tile-loader.js +1 -1
- package/dist/style/color-bins-style.d.ts +1 -2
- package/dist/style/color-bins-style.d.ts.map +1 -1
- package/dist/style/color-categories-style.d.ts +1 -2
- package/dist/style/color-categories-style.d.ts.map +1 -1
- package/dist/style/color-continuous-style.d.ts +1 -2
- package/dist/style/color-continuous-style.d.ts.map +1 -1
- package/dist/style/palette.d.ts +1 -1
- package/dist/style/palette.d.ts.map +1 -1
- package/dist.min.js +10 -10
- package/package.json +2 -2
- package/src/api/layer-map.ts +30 -29
- package/src/index.ts +3 -0
- package/src/layers/cluster-tile-layer.ts +218 -0
- package/src/layers/cluster-utils.ts +160 -0
- package/src/style/color-bins-style.ts +2 -2
- package/src/style/color-categories-style.ts +2 -2
- package/src/style/color-continuous-style.ts +2 -2
- package/src/style/palette.ts +1 -2
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.
|
|
6
|
+
"version": "9.0.24",
|
|
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": "
|
|
75
|
+
"gitHead": "0949b9e539222d84d35b1b315e55710106409553"
|
|
76
76
|
}
|
package/src/api/layer-map.ts
CHANGED
|
@@ -18,11 +18,11 @@ import {CPUGridLayer, HeatmapLayer, HexagonLayer} from '@deck.gl/aggregation-lay
|
|
|
18
18
|
import {GeoJsonLayer} from '@deck.gl/layers';
|
|
19
19
|
import {H3HexagonLayer} from '@deck.gl/geo-layers';
|
|
20
20
|
|
|
21
|
+
import ClusterTileLayer from '../layers/cluster-tile-layer';
|
|
21
22
|
import H3TileLayer from '../layers/h3-tile-layer';
|
|
22
23
|
import QuadbinTileLayer from '../layers/quadbin-tile-layer';
|
|
23
24
|
import RasterTileLayer from '../layers/raster-tile-layer';
|
|
24
25
|
import VectorTileLayer from '../layers/vector-tile-layer';
|
|
25
|
-
import {MapType} from './types';
|
|
26
26
|
import {assert, createBinaryProxy, scaleIdentity} from '../utils';
|
|
27
27
|
import {
|
|
28
28
|
CustomMarkersRange,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
VisualChannelField,
|
|
33
33
|
VisualChannels
|
|
34
34
|
} from './types';
|
|
35
|
+
import HeatmapTileLayer from '../layers/heatmap-tile-layer';
|
|
35
36
|
|
|
36
37
|
const SCALE_FUNCS = {
|
|
37
38
|
linear: scaleLinear,
|
|
@@ -46,6 +47,17 @@ const SCALE_FUNCS = {
|
|
|
46
47
|
};
|
|
47
48
|
export type SCALE_TYPE = keyof typeof SCALE_FUNCS;
|
|
48
49
|
|
|
50
|
+
type TileLayerType =
|
|
51
|
+
| 'clusterTile'
|
|
52
|
+
| 'h3'
|
|
53
|
+
| 'heatmapTile'
|
|
54
|
+
| 'mvt'
|
|
55
|
+
| 'quadbin'
|
|
56
|
+
| 'raster'
|
|
57
|
+
| 'tileset';
|
|
58
|
+
type DocumentLayerType = 'geojson' | 'grid' | 'heatmap' | 'hexagon' | 'hexagonId' | 'point';
|
|
59
|
+
type LayerType = TileLayerType | DocumentLayerType;
|
|
60
|
+
|
|
49
61
|
function identity<T>(v: T): T {
|
|
50
62
|
return v;
|
|
51
63
|
}
|
|
@@ -74,6 +86,16 @@ const AGGREGATION_FUNC = {
|
|
|
74
86
|
variance
|
|
75
87
|
};
|
|
76
88
|
|
|
89
|
+
const TILE_LAYER_TYPE_TO_LAYER: Record<TileLayerType, ConstructorOf<Layer>> = {
|
|
90
|
+
clusterTile: ClusterTileLayer,
|
|
91
|
+
h3: H3TileLayer,
|
|
92
|
+
heatmapTile: HeatmapTileLayer,
|
|
93
|
+
mvt: VectorTileLayer,
|
|
94
|
+
quadbin: QuadbinTileLayer,
|
|
95
|
+
raster: RasterTileLayer,
|
|
96
|
+
tileset: VectorTileLayer
|
|
97
|
+
};
|
|
98
|
+
|
|
77
99
|
const hexToRGBA = c => {
|
|
78
100
|
const {r, g, b, opacity} = rgb(c);
|
|
79
101
|
return [r, g, b, 255 * opacity];
|
|
@@ -135,7 +157,7 @@ function mergePropMaps(a: Record<string, any> = {}, b: Record<string, any> = {})
|
|
|
135
157
|
}
|
|
136
158
|
|
|
137
159
|
export function getLayer(
|
|
138
|
-
type:
|
|
160
|
+
type: LayerType,
|
|
139
161
|
config: MapTextSubLayerConfig,
|
|
140
162
|
dataset: MapDataset
|
|
141
163
|
): {Layer: ConstructorOf<Layer>; propMap: any; defaultProps: any} {
|
|
@@ -144,8 +166,8 @@ export function getLayer(
|
|
|
144
166
|
if (config.visConfig?.customMarkers) {
|
|
145
167
|
basePropMap = mergePropMaps(sharedPropMap, customMarkersPropsMap);
|
|
146
168
|
}
|
|
147
|
-
if (type
|
|
148
|
-
return getTileLayer(dataset, basePropMap);
|
|
169
|
+
if (TILE_LAYER_TYPE_TO_LAYER[type]) {
|
|
170
|
+
return getTileLayer(dataset, basePropMap, type);
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
const geoColumn = dataset?.geoColumn;
|
|
@@ -154,7 +176,7 @@ export function getLayer(
|
|
|
154
176
|
const hexagonId = config.columns?.hex_id;
|
|
155
177
|
|
|
156
178
|
const layerTypeDefs: Record<
|
|
157
|
-
|
|
179
|
+
DocumentLayerType,
|
|
158
180
|
{Layer: ConstructorOf<Layer>; propMap?: any; defaultProps?: any}
|
|
159
181
|
> = {
|
|
160
182
|
point: {
|
|
@@ -201,32 +223,11 @@ export function getLayer(
|
|
|
201
223
|
};
|
|
202
224
|
}
|
|
203
225
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
type?: MapType
|
|
207
|
-
): typeof VectorTileLayer | typeof H3TileLayer | typeof QuadbinTileLayer {
|
|
208
|
-
if (type === 'raster') {
|
|
209
|
-
return RasterTileLayer;
|
|
210
|
-
}
|
|
211
|
-
if (scheme === 'h3') {
|
|
212
|
-
return H3TileLayer;
|
|
213
|
-
}
|
|
214
|
-
if (scheme === 'quadbin') {
|
|
215
|
-
return QuadbinTileLayer;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return VectorTileLayer;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function getTileLayer(dataset: MapDataset, basePropMap) {
|
|
222
|
-
const {
|
|
223
|
-
aggregationExp,
|
|
224
|
-
aggregationResLevel,
|
|
225
|
-
data: {scheme}
|
|
226
|
-
} = dataset;
|
|
226
|
+
function getTileLayer(dataset: MapDataset, basePropMap, type: LayerType) {
|
|
227
|
+
const {aggregationExp, aggregationResLevel} = dataset;
|
|
227
228
|
|
|
228
229
|
return {
|
|
229
|
-
Layer:
|
|
230
|
+
Layer: TILE_LAYER_TYPE_TO_LAYER[type] || VectorTileLayer,
|
|
230
231
|
propMap: basePropMap,
|
|
231
232
|
defaultProps: {
|
|
232
233
|
...defaultProps,
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {default as ClusterTileLayer} from './layers/cluster-tile-layer';
|
|
1
2
|
import {default as H3TileLayer} from './layers/h3-tile-layer';
|
|
2
3
|
import {default as HeatmapTileLayer} from './layers/heatmap-tile-layer';
|
|
3
4
|
import {default as _PointLabelLayer} from './layers/point-label-layer';
|
|
@@ -5,6 +6,7 @@ import {default as QuadbinTileLayer} from './layers/quadbin-tile-layer';
|
|
|
5
6
|
import {default as RasterTileLayer} from './layers/raster-tile-layer';
|
|
6
7
|
import {default as VectorTileLayer} from './layers/vector-tile-layer';
|
|
7
8
|
const CARTO_LAYERS = {
|
|
9
|
+
ClusterTileLayer,
|
|
8
10
|
H3TileLayer,
|
|
9
11
|
HeatmapTileLayer,
|
|
10
12
|
_PointLabelLayer,
|
|
@@ -14,6 +16,7 @@ const CARTO_LAYERS = {
|
|
|
14
16
|
};
|
|
15
17
|
export {
|
|
16
18
|
CARTO_LAYERS,
|
|
19
|
+
ClusterTileLayer,
|
|
17
20
|
H3TileLayer,
|
|
18
21
|
HeatmapTileLayer,
|
|
19
22
|
_PointLabelLayer,
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {GeoJsonLayer, GeoJsonLayerProps} from '@deck.gl/layers';
|
|
2
|
+
import {
|
|
3
|
+
TileLayer,
|
|
4
|
+
_Tile2DHeader as Tile2DHeader,
|
|
5
|
+
TileLayerProps,
|
|
6
|
+
TileLayerPickingInfo
|
|
7
|
+
} from '@deck.gl/geo-layers';
|
|
8
|
+
import {registerLoaders} from '@loaders.gl/core';
|
|
9
|
+
import {binaryToGeojson} from '@loaders.gl/gis';
|
|
10
|
+
import {BinaryFeatureCollection} from '@loaders.gl/schema';
|
|
11
|
+
import type {Feature, Geometry} from 'geojson';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Accessor,
|
|
15
|
+
DefaultProps,
|
|
16
|
+
CompositeLayer,
|
|
17
|
+
_deepEqual as deepEqual,
|
|
18
|
+
GetPickingInfoParams,
|
|
19
|
+
Layer,
|
|
20
|
+
LayersList,
|
|
21
|
+
PickingInfo
|
|
22
|
+
} from '@deck.gl/core';
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
aggregateTile,
|
|
26
|
+
ClusteredFeaturePropertiesT,
|
|
27
|
+
clustersToBinary,
|
|
28
|
+
computeAggregationStats,
|
|
29
|
+
extractAggregationProperties,
|
|
30
|
+
ParsedQuadbinCell,
|
|
31
|
+
ParsedQuadbinTile
|
|
32
|
+
} from './cluster-utils';
|
|
33
|
+
import {DEFAULT_TILE_SIZE} from '../constants';
|
|
34
|
+
import QuadbinTileset2D from './quadbin-tileset-2d';
|
|
35
|
+
import {getQuadbinPolygon} from './quadbin-utils';
|
|
36
|
+
import CartoSpatialTileLoader from './schema/carto-spatial-tile-loader';
|
|
37
|
+
import {injectAccessToken, TilejsonPropType} from './utils';
|
|
38
|
+
import type {TilejsonResult} from '../sources/types';
|
|
39
|
+
|
|
40
|
+
registerLoaders([CartoSpatialTileLoader]);
|
|
41
|
+
|
|
42
|
+
const defaultProps: DefaultProps<ClusterTileLayerProps> = {
|
|
43
|
+
data: TilejsonPropType,
|
|
44
|
+
clusterLevel: {type: 'number', value: 5, min: 1},
|
|
45
|
+
getPosition: {
|
|
46
|
+
type: 'accessor',
|
|
47
|
+
value: ({id}) => getQuadbinPolygon(id, 0.5).slice(2, 4) as [number, number]
|
|
48
|
+
},
|
|
49
|
+
getWeight: {type: 'accessor', value: 1},
|
|
50
|
+
refinementStrategy: 'no-overlap',
|
|
51
|
+
tileSize: DEFAULT_TILE_SIZE
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ClusterTileLayerPickingInfo<FeaturePropertiesT = {}> = TileLayerPickingInfo<
|
|
55
|
+
ParsedQuadbinTile<FeaturePropertiesT>,
|
|
56
|
+
PickingInfo<Feature<Geometry, FeaturePropertiesT>>
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
/** All properties supported by ClusterTileLayer. */
|
|
60
|
+
export type ClusterTileLayerProps<FeaturePropertiesT = unknown> =
|
|
61
|
+
_ClusterTileLayerProps<FeaturePropertiesT> &
|
|
62
|
+
Omit<TileLayerProps<ParsedQuadbinTile<FeaturePropertiesT>>, 'data'>;
|
|
63
|
+
|
|
64
|
+
/** Properties added by ClusterTileLayer. */
|
|
65
|
+
type _ClusterTileLayerProps<FeaturePropertiesT> = Omit<
|
|
66
|
+
GeoJsonLayerProps<ClusteredFeaturePropertiesT<FeaturePropertiesT>>,
|
|
67
|
+
'data'
|
|
68
|
+
> & {
|
|
69
|
+
data: null | TilejsonResult | Promise<TilejsonResult>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The number of aggregation levels to cluster cells by. Larger values increase
|
|
73
|
+
* the clustering radius, with an increment of `clusterLevel` doubling the radius.
|
|
74
|
+
*
|
|
75
|
+
* @default 5
|
|
76
|
+
*/
|
|
77
|
+
clusterLevel?: number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The (average) position of points in a cell used for clustering.
|
|
81
|
+
* If not supplied the center of the quadbin cell is used.
|
|
82
|
+
*
|
|
83
|
+
* @default cell center
|
|
84
|
+
*/
|
|
85
|
+
getPosition?: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The weight of each cell used for clustering.
|
|
89
|
+
*
|
|
90
|
+
* @default 1
|
|
91
|
+
*/
|
|
92
|
+
getWeight?: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, number>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
class ClusterGeoJsonLayer<
|
|
96
|
+
FeaturePropertiesT extends {} = {},
|
|
97
|
+
ExtraProps extends {} = {}
|
|
98
|
+
> extends TileLayer<
|
|
99
|
+
ParsedQuadbinTile<FeaturePropertiesT>,
|
|
100
|
+
ExtraProps & Required<_ClusterTileLayerProps<FeaturePropertiesT>>
|
|
101
|
+
> {
|
|
102
|
+
static layerName = 'ClusterGeoJsonLayer';
|
|
103
|
+
static defaultProps = defaultProps;
|
|
104
|
+
state!: TileLayer<FeaturePropertiesT>['state'] & {
|
|
105
|
+
data: BinaryFeatureCollection;
|
|
106
|
+
clusterIds: bigint[];
|
|
107
|
+
hoveredFeatureId: bigint | number | null;
|
|
108
|
+
highlightColor: number[];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
renderLayers(): Layer | null | LayersList {
|
|
112
|
+
const visibleTiles = this.state.tileset?.tiles.filter((tile: Tile2DHeader) => {
|
|
113
|
+
return tile.isLoaded && tile.content && this.state.tileset!.isTileVisible(tile);
|
|
114
|
+
}) as Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>[];
|
|
115
|
+
if (!visibleTiles?.length) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
visibleTiles.sort((a, b) => b.zoom - a.zoom);
|
|
119
|
+
|
|
120
|
+
const {zoom} = this.context.viewport;
|
|
121
|
+
const {clusterLevel, getPosition, getWeight} = this.props;
|
|
122
|
+
|
|
123
|
+
const properties = extractAggregationProperties(visibleTiles[0]);
|
|
124
|
+
const data = [] as ClusteredFeaturePropertiesT<FeaturePropertiesT>[];
|
|
125
|
+
for (const tile of visibleTiles) {
|
|
126
|
+
// Calculate aggregation based on viewport zoom
|
|
127
|
+
const overZoom = Math.round(zoom - tile.zoom);
|
|
128
|
+
const aggregationLevels = Math.round(clusterLevel) - overZoom;
|
|
129
|
+
aggregateTile(tile, aggregationLevels, properties, getPosition, getWeight);
|
|
130
|
+
data.push(...tile.userData![aggregationLevels]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
data.sort((a, b) => Number(b.count - a.count));
|
|
134
|
+
|
|
135
|
+
const clusterIds = data?.map((tile: any) => tile.id);
|
|
136
|
+
const needsUpdate = !deepEqual(clusterIds, this.state.clusterIds, 1);
|
|
137
|
+
this.setState({clusterIds});
|
|
138
|
+
|
|
139
|
+
if (needsUpdate) {
|
|
140
|
+
const stats = computeAggregationStats(data, properties);
|
|
141
|
+
for (const d of data) {
|
|
142
|
+
d.stats = stats;
|
|
143
|
+
}
|
|
144
|
+
this.setState({data: clustersToBinary(data)});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const props = {
|
|
148
|
+
...this.props,
|
|
149
|
+
id: 'clusters',
|
|
150
|
+
data: this.state.data,
|
|
151
|
+
dataComparator: (data?: BinaryFeatureCollection, oldData?: BinaryFeatureCollection) => {
|
|
152
|
+
const newIds = data?.points?.properties?.map((tile: any) => tile.id);
|
|
153
|
+
const oldIds = oldData?.points?.properties?.map((tile: any) => tile.id);
|
|
154
|
+
return deepEqual(newIds, oldIds, 1);
|
|
155
|
+
}
|
|
156
|
+
} as GeoJsonLayerProps<ClusteredFeaturePropertiesT<FeaturePropertiesT>>;
|
|
157
|
+
|
|
158
|
+
return new GeoJsonLayer(this.getSubLayerProps(props));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getPickingInfo(params: GetPickingInfoParams): ClusterTileLayerPickingInfo<FeaturePropertiesT> {
|
|
162
|
+
const info = params.info as TileLayerPickingInfo<ParsedQuadbinTile<FeaturePropertiesT>>;
|
|
163
|
+
|
|
164
|
+
if (info.index !== -1) {
|
|
165
|
+
const {data} = params.sourceLayer!.props;
|
|
166
|
+
info.object = binaryToGeojson(data as BinaryFeatureCollection, {
|
|
167
|
+
globalFeatureId: info.index
|
|
168
|
+
}) as Feature;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return info;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected _updateAutoHighlight(info: PickingInfo): void {
|
|
175
|
+
for (const layer of this.getSubLayers()) {
|
|
176
|
+
layer.updateAutoHighlight(info);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
filterSubLayer() {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Adapter layer around ClusterLayer that converts tileJSON into TileLayer API
|
|
186
|
+
export default class ClusterTileLayer<
|
|
187
|
+
FeaturePropertiesT = any,
|
|
188
|
+
ExtraProps extends {} = {}
|
|
189
|
+
> extends CompositeLayer<ExtraProps & Required<_ClusterTileLayerProps<FeaturePropertiesT>>> {
|
|
190
|
+
static layerName = 'ClusterTileLayer';
|
|
191
|
+
static defaultProps = defaultProps;
|
|
192
|
+
|
|
193
|
+
getLoadOptions(): any {
|
|
194
|
+
const loadOptions = super.getLoadOptions() || {};
|
|
195
|
+
const tileJSON = this.props.data as TilejsonResult;
|
|
196
|
+
injectAccessToken(loadOptions, tileJSON.accessToken);
|
|
197
|
+
loadOptions.cartoSpatialTile = {...loadOptions.cartoSpatialTile, scheme: 'quadbin'};
|
|
198
|
+
return loadOptions;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
renderLayers(): Layer | null | LayersList {
|
|
202
|
+
const tileJSON = this.props.data as TilejsonResult;
|
|
203
|
+
if (!tileJSON) return null;
|
|
204
|
+
|
|
205
|
+
const {tiles: data, maxresolution: maxZoom} = tileJSON;
|
|
206
|
+
return [
|
|
207
|
+
// @ts-ignore
|
|
208
|
+
new ClusterGeoJsonLayer(this.props, {
|
|
209
|
+
id: `cluster-geojson-layer-${this.props.id}`,
|
|
210
|
+
data,
|
|
211
|
+
// TODO: Tileset2D should be generic over TileIndex type
|
|
212
|
+
TilesetClass: QuadbinTileset2D as any,
|
|
213
|
+
maxZoom,
|
|
214
|
+
loadOptions: this.getLoadOptions()
|
|
215
|
+
})
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {cellToParent} from 'quadbin';
|
|
2
|
+
import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers';
|
|
3
|
+
import {Accessor} from '@deck.gl/core';
|
|
4
|
+
import {BinaryFeatureCollection} from '@loaders.gl/schema';
|
|
5
|
+
|
|
6
|
+
export type Aggregation = 'any' | 'average' | 'count' | 'min' | 'max' | 'sum';
|
|
7
|
+
export type AggregationProperties<FeaturePropertiesT> = {
|
|
8
|
+
aggregation: Aggregation;
|
|
9
|
+
name: keyof FeaturePropertiesT;
|
|
10
|
+
}[];
|
|
11
|
+
export type ClusteredFeaturePropertiesT<FeaturePropertiesT> = FeaturePropertiesT & {
|
|
12
|
+
id: bigint;
|
|
13
|
+
count: number;
|
|
14
|
+
position: [number, number];
|
|
15
|
+
stats: Record<keyof FeaturePropertiesT, {min: number; max: number}>;
|
|
16
|
+
};
|
|
17
|
+
export type ParsedQuadbinCell<FeaturePropertiesT> = {id: bigint; properties: FeaturePropertiesT};
|
|
18
|
+
export type ParsedQuadbinTile<FeaturePropertiesT> = ParsedQuadbinCell<FeaturePropertiesT>[];
|
|
19
|
+
|
|
20
|
+
export function aggregateTile<FeaturePropertiesT>(
|
|
21
|
+
tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>,
|
|
22
|
+
aggregationLevels: number,
|
|
23
|
+
properties: AggregationProperties<FeaturePropertiesT> = [],
|
|
24
|
+
getPosition: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>,
|
|
25
|
+
getWeight: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, number>
|
|
26
|
+
): void {
|
|
27
|
+
if (!tile.content) return;
|
|
28
|
+
|
|
29
|
+
// Aggregate on demand and cache result
|
|
30
|
+
if (!tile.userData) tile.userData = {};
|
|
31
|
+
if (tile.userData[aggregationLevels]) return;
|
|
32
|
+
|
|
33
|
+
const out: Record<number, any> = {};
|
|
34
|
+
for (const cell of tile.content) {
|
|
35
|
+
let id = cell.id;
|
|
36
|
+
const position = typeof getPosition === 'function' ? getPosition(cell, {} as any) : getPosition;
|
|
37
|
+
|
|
38
|
+
// Aggregate by parent id
|
|
39
|
+
for (let i = 0; i < aggregationLevels - 1; i++) {
|
|
40
|
+
id = cellToParent(id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Unfortunately TS doesn't support Record<bigint, any>
|
|
44
|
+
// https://github.com/microsoft/TypeScript/issues/46395
|
|
45
|
+
const parentId = Number(id);
|
|
46
|
+
if (!(parentId in out)) {
|
|
47
|
+
out[parentId] = {id, count: 0, position: [0, 0]};
|
|
48
|
+
for (const {name, aggregation} of properties) {
|
|
49
|
+
if (aggregation === 'any') {
|
|
50
|
+
// Just pick first value for ANY
|
|
51
|
+
out[parentId][name] = cell.properties[name];
|
|
52
|
+
} else {
|
|
53
|
+
out[parentId][name] = 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Layout props
|
|
58
|
+
const prevTotalW = out[parentId].count;
|
|
59
|
+
out[parentId].count += typeof getWeight === 'function' ? getWeight(cell, {} as any) : getWeight;
|
|
60
|
+
|
|
61
|
+
const totalW = out[parentId].count;
|
|
62
|
+
const W = totalW - prevTotalW;
|
|
63
|
+
out[parentId].position[0] = (prevTotalW * out[parentId].position[0] + W * position[0]) / totalW;
|
|
64
|
+
out[parentId].position[1] = (prevTotalW * out[parentId].position[1] + W * position[1]) / totalW;
|
|
65
|
+
|
|
66
|
+
// Re-aggregate other properties so clusters can be styled
|
|
67
|
+
for (const {name, aggregation} of properties) {
|
|
68
|
+
const prevValue = out[parentId][name];
|
|
69
|
+
const value = cell.properties[name] as number;
|
|
70
|
+
if (aggregation === 'average') {
|
|
71
|
+
out[parentId][name] = (prevTotalW * prevValue + W * value) / totalW;
|
|
72
|
+
} else if (aggregation === 'count' || aggregation === 'sum') {
|
|
73
|
+
out[parentId][name] = prevValue + value;
|
|
74
|
+
} else if (aggregation === 'max') {
|
|
75
|
+
out[parentId][name] = Math.max(prevValue, value);
|
|
76
|
+
} else if (aggregation === 'min') {
|
|
77
|
+
out[parentId][name] = Math.min(prevValue, value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
tile.userData[aggregationLevels] = Object.values(out);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function extractAggregationProperties<FeaturePropertiesT extends {}>(
|
|
86
|
+
tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>
|
|
87
|
+
): AggregationProperties<FeaturePropertiesT> {
|
|
88
|
+
const properties: AggregationProperties<FeaturePropertiesT> = [];
|
|
89
|
+
const validAggregations: Aggregation[] = ['any', 'average', 'count', 'min', 'max', 'sum'];
|
|
90
|
+
for (const name of Object.keys(tile.content![0].properties)) {
|
|
91
|
+
let aggregation = name.split('_').pop()!.toLowerCase() as Aggregation;
|
|
92
|
+
if (!validAggregations.includes(aggregation)) {
|
|
93
|
+
aggregation = 'any';
|
|
94
|
+
}
|
|
95
|
+
properties.push({name: name as keyof FeaturePropertiesT, aggregation});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return properties;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function computeAggregationStats<FeaturePropertiesT>(
|
|
102
|
+
data: ClusteredFeaturePropertiesT<FeaturePropertiesT>[],
|
|
103
|
+
properties: AggregationProperties<FeaturePropertiesT>
|
|
104
|
+
) {
|
|
105
|
+
const stats = {} as Record<keyof FeaturePropertiesT, {min: number; max: number}>;
|
|
106
|
+
for (const {name, aggregation} of properties) {
|
|
107
|
+
stats[name] = {min: Infinity, max: -Infinity};
|
|
108
|
+
if (aggregation !== 'any') {
|
|
109
|
+
for (const d of data) {
|
|
110
|
+
stats[name].min = Math.min(stats[name].min, d[name] as number);
|
|
111
|
+
stats[name].max = Math.max(stats[name].max, d[name] as number);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return stats;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const EMPTY_UINT16ARRAY = new Uint16Array();
|
|
120
|
+
const EMPTY_BINARY_PROPS = {
|
|
121
|
+
positions: {value: new Float32Array(), size: 2},
|
|
122
|
+
properties: [],
|
|
123
|
+
numericProps: {},
|
|
124
|
+
featureIds: {value: EMPTY_UINT16ARRAY, size: 1},
|
|
125
|
+
globalFeatureIds: {value: EMPTY_UINT16ARRAY, size: 1}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export function clustersToBinary<FeaturePropertiesT>(
|
|
129
|
+
data: ClusteredFeaturePropertiesT<FeaturePropertiesT>[]
|
|
130
|
+
): BinaryFeatureCollection {
|
|
131
|
+
const positions = new Float32Array(data.length * 2);
|
|
132
|
+
const featureIds = new Uint16Array(data.length);
|
|
133
|
+
for (let i = 0; i < data.length; i++) {
|
|
134
|
+
positions.set(data[i].position, 2 * i);
|
|
135
|
+
featureIds[i] = i;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
shape: 'binary-feature-collection',
|
|
140
|
+
points: {
|
|
141
|
+
type: 'Point',
|
|
142
|
+
positions: {value: positions, size: 2},
|
|
143
|
+
properties: data,
|
|
144
|
+
numericProps: {},
|
|
145
|
+
featureIds: {value: featureIds, size: 1},
|
|
146
|
+
globalFeatureIds: {value: featureIds, size: 1}
|
|
147
|
+
},
|
|
148
|
+
lines: {
|
|
149
|
+
type: 'LineString',
|
|
150
|
+
pathIndices: {value: EMPTY_UINT16ARRAY, size: 1},
|
|
151
|
+
...EMPTY_BINARY_PROPS
|
|
152
|
+
},
|
|
153
|
+
polygons: {
|
|
154
|
+
type: 'Polygon',
|
|
155
|
+
polygonIndices: {value: EMPTY_UINT16ARRAY, size: 1},
|
|
156
|
+
primitivePolygonIndices: {value: EMPTY_UINT16ARRAY, size: 1},
|
|
157
|
+
...EMPTY_BINARY_PROPS
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {scaleThreshold} from 'd3-scale';
|
|
2
|
-
import {AccessorFunction} from '@deck.gl/core';
|
|
2
|
+
import {AccessorFunction, Color} from '@deck.gl/core';
|
|
3
3
|
import {Feature} from 'geojson';
|
|
4
|
-
import getPalette, {
|
|
4
|
+
import getPalette, {DEFAULT_PALETTE, NULL_COLOR} from './palette';
|
|
5
5
|
import {assert} from '../utils';
|
|
6
6
|
import {AttributeSelector, getAttrValue} from './utils';
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {AccessorFunction} from '@deck.gl/core';
|
|
1
|
+
import {AccessorFunction, Color} from '@deck.gl/core';
|
|
2
2
|
import {Feature} from 'geojson';
|
|
3
|
-
import getPalette, {
|
|
3
|
+
import getPalette, {DEFAULT_PALETTE, NULL_COLOR, OTHERS_COLOR} from './palette';
|
|
4
4
|
import {assert} from '../utils';
|
|
5
5
|
import {AttributeSelector, getAttrValue} from './utils';
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {AccessorFunction} from '@deck.gl/core';
|
|
1
|
+
import {AccessorFunction, Color} from '@deck.gl/core';
|
|
2
2
|
import {scaleLinear} from 'd3-scale';
|
|
3
3
|
import {Feature} from 'geojson';
|
|
4
|
-
import getPalette, {
|
|
4
|
+
import getPalette, {DEFAULT_PALETTE, NULL_COLOR} from './palette';
|
|
5
5
|
import {assert} from '../utils';
|
|
6
6
|
import {AttributeSelector, getAttrValue} from './utils';
|
|
7
7
|
|
package/src/style/palette.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as cartoColors from 'cartocolor';
|
|
2
2
|
import {assert} from '../utils';
|
|
3
|
-
|
|
4
|
-
export type Color = [r: number, b: number, g: number, a?: number];
|
|
3
|
+
import {Color} from '@deck.gl/core';
|
|
5
4
|
|
|
6
5
|
export const DEFAULT_PALETTE = 'PurpOr';
|
|
7
6
|
export const NULL_COLOR: Color = [204, 204, 204];
|