UNPKG

@deck.gl/carto

Version:

CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.

134 lines 5.54 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors /* eslint-disable no-shadow */ import { GeoJsonLayer } from '@deck.gl/layers'; import { TileLayer } from '@deck.gl/geo-layers'; import { registerLoaders } from '@loaders.gl/core'; import { binaryToGeojson } from '@loaders.gl/gis'; import { CompositeLayer, _deepEqual as deepEqual } from '@deck.gl/core'; import { aggregateTile, clustersToBinary, computeAggregationStats, extractAggregationProperties } from "./cluster-utils.js"; import { DEFAULT_TILE_SIZE } from "../constants.js"; import QuadbinTileset2D from "./quadbin-tileset-2d.js"; import { getQuadbinPolygon } from "./quadbin-utils.js"; import CartoSpatialTileLoader from "./schema/carto-spatial-tile-loader.js"; import { TilejsonPropType, mergeLoadOptions } from "./utils.js"; registerLoaders([CartoSpatialTileLoader]); const defaultProps = { data: TilejsonPropType, clusterLevel: { type: 'number', value: 5, min: 1 }, getPosition: { type: 'accessor', value: ({ id }) => getQuadbinPolygon(id, 0.5).slice(2, 4) }, getWeight: { type: 'accessor', value: 1 }, refinementStrategy: 'no-overlap', tileSize: DEFAULT_TILE_SIZE }; class ClusterGeoJsonLayer extends TileLayer { initializeState() { super.initializeState(); this.state.aggregationCache = new WeakMap(); } // eslint-disable-next-line max-statements renderLayers() { const visibleTiles = this.state.tileset?.tiles.filter((tile) => { return tile.isLoaded && tile.content && this.state.tileset.isTileVisible(tile); }); if (!visibleTiles?.length) { return null; } visibleTiles.sort((a, b) => b.zoom - a.zoom); const { zoom } = this.context.viewport; const { clusterLevel, getPosition, getWeight } = this.props; const { aggregationCache } = this.state; const properties = extractAggregationProperties(visibleTiles[0]); const data = []; let needsUpdate = false; for (const tile of visibleTiles) { // Calculate aggregation based on viewport zoom const overZoom = Math.round(zoom - tile.zoom); const aggregationLevels = Math.round(clusterLevel) - overZoom; let tileAggregationCache = aggregationCache.get(tile.content); if (!tileAggregationCache) { tileAggregationCache = new Map(); aggregationCache.set(tile.content, tileAggregationCache); } const didAggregate = aggregateTile(tile, tileAggregationCache, aggregationLevels, properties, getPosition, getWeight); needsUpdate || (needsUpdate = didAggregate); data.push(...tileAggregationCache.get(aggregationLevels)); } data.sort((a, b) => Number(b.count - a.count)); const clusterIds = data?.map((tile) => tile.id); needsUpdate || (needsUpdate = !deepEqual(clusterIds, this.state.clusterIds, 1)); this.setState({ clusterIds }); if (needsUpdate) { const stats = computeAggregationStats(data, properties); const binaryData = clustersToBinary(data); binaryData.points.attributes = { stats }; this.setState({ data: binaryData }); } const props = { ...this.props, id: 'clusters', data: this.state.data, dataComparator: (data, oldData) => { const newIds = data?.points?.properties?.map((tile) => tile.id); const oldIds = oldData?.points?.properties?.map((tile) => tile.id); return deepEqual(newIds, oldIds, 1); } }; return new GeoJsonLayer(this.getSubLayerProps(props)); } getPickingInfo(params) { const info = params.info; if (info.index !== -1) { const { data } = params.sourceLayer.props; info.object = binaryToGeojson(data, { globalFeatureId: info.index }); } return info; } _updateAutoHighlight(info) { for (const layer of this.getSubLayers()) { layer.updateAutoHighlight(info); } } filterSubLayer() { return true; } } ClusterGeoJsonLayer.layerName = 'ClusterGeoJsonLayer'; ClusterGeoJsonLayer.defaultProps = defaultProps; // Adapter layer around ClusterLayer that converts tileJSON into TileLayer API class ClusterTileLayer extends CompositeLayer { getLoadOptions() { const tileJSON = this.props.data; return mergeLoadOptions(super.getLoadOptions(), { fetch: { headers: { Authorization: `Bearer ${tileJSON.accessToken}` } }, cartoSpatialTile: { scheme: 'quadbin' } }); } renderLayers() { const tileJSON = this.props.data; if (!tileJSON) return null; const { tiles: data, maxresolution: maxZoom } = tileJSON; return [ // @ts-ignore new ClusterGeoJsonLayer(this.props, { id: `cluster-geojson-layer-${this.props.id}`, data, // TODO: Tileset2D should be generic over TileIndex type TilesetClass: QuadbinTileset2D, maxZoom, loadOptions: this.getLoadOptions() }) ]; } } ClusterTileLayer.layerName = 'ClusterTileLayer'; ClusterTileLayer.defaultProps = defaultProps; export default ClusterTileLayer; //# sourceMappingURL=cluster-tile-layer.js.map