UNPKG

@deck.gl/carto

Version:

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

124 lines 5.11 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { cellToParent } from 'quadbin'; import { cellToParent as h3CellToParent, getResolution as getH3Resolution } from 'h3-js'; import { log } from '@deck.gl/core'; import { createBinaryPointFeature, createEmptyBinary } from "../utils.js"; /** * Aggregates tile by specified properties, caching result in tile.userData * * @returns true if data was aggregated, false if cache used */ export function aggregateTile(tile, tileAggregationCache, aggregationLevels, properties = [], getPosition, getWeight, scheme = 'quadbin') { if (!tile.content) return false; // Aggregate on demand and cache result if (!tile.userData) tile.userData = {}; const cell0 = tileAggregationCache.get(aggregationLevels)?.[0]; if (cell0) { // Have already aggregated this tile if (properties.every(property => property.name in cell0)) { // Use cached result return false; } // Aggregated properties have changed, re-aggregate tileAggregationCache.clear(); } const out = {}; for (const cell of tile.content) { let id = cell.id; const position = typeof getPosition === 'function' ? getPosition(cell, {}) : getPosition; // Aggregate by parent rid for (let i = 0; i < aggregationLevels - 1; i++) { if (scheme === 'h3') { const currentResolution = getH3Resolution(id); id = h3CellToParent(id, Math.max(0, currentResolution - 1)); } else { id = cellToParent(id); } } // Use string key for both H3 and Quadbin to avoid TypeScript Record<bigint, any> issues // https://github.com/microsoft/TypeScript/issues/46395 const parentId = String(id); if (!(parentId in out)) { out[parentId] = { id, count: 0, position: [0, 0] }; for (const { name, aggregation } of properties) { if (aggregation === 'any') { // Just pick first value for ANY out[parentId][name] = cell.properties[name]; } else { out[parentId][name] = 0; } } } // Layout props const prevTotalW = out[parentId].count; out[parentId].count += typeof getWeight === 'function' ? getWeight(cell, {}) : getWeight; const totalW = out[parentId].count; const W = totalW - prevTotalW; out[parentId].position[0] = (prevTotalW * out[parentId].position[0] + W * position[0]) / totalW; out[parentId].position[1] = (prevTotalW * out[parentId].position[1] + W * position[1]) / totalW; // Re-aggregate other properties so clusters can be styled for (const { name, aggregation } of properties) { const prevValue = out[parentId][name]; const value = cell.properties[name]; if (aggregation === 'average') { out[parentId][name] = (prevTotalW * prevValue + W * value) / totalW; } else if (aggregation === 'count' || aggregation === 'sum') { out[parentId][name] = prevValue + value; } else if (aggregation === 'max') { out[parentId][name] = Math.max(prevValue, value); } else if (aggregation === 'min') { out[parentId][name] = Math.min(prevValue, value); } } } tileAggregationCache.set(aggregationLevels, Object.values(out)); return true; } export function extractAggregationProperties(tile) { const properties = []; const validAggregations = ['any', 'average', 'count', 'min', 'max', 'sum']; for (const name of Object.keys(tile.content[0].properties)) { let aggregation = name.split('_').pop().toLowerCase(); if (!validAggregations.includes(aggregation)) { log.warn(`No valid aggregation present in ${name} property`)(); aggregation = 'any'; } properties.push({ name: name, aggregation }); } return properties; } export function computeAggregationStats(data, properties) { const stats = {}; for (const { name, aggregation } of properties) { stats[name] = { min: Infinity, max: -Infinity }; if (aggregation !== 'any') { for (const d of data) { stats[name].min = Math.min(stats[name].min, d[name]); stats[name].max = Math.max(stats[name].max, d[name]); } } } return stats; } export function clustersToBinary(data) { const positions = new Float32Array(data.length * 2); const featureIds = new Uint16Array(data.length); for (let i = 0; i < data.length; i++) { positions.set(data[i].position, 2 * i); featureIds[i] = i; } return { ...createEmptyBinary(), points: createBinaryPointFeature(positions, featureIds, featureIds, {}, data) }; } //# sourceMappingURL=cluster-utils.js.map