@deck.gl/carto
Version:
CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.
117 lines • 4.74 kB
JavaScript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { cellToParent } from 'quadbin';
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) {
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++) {
id = cellToParent(id);
}
// Unfortunately TS doesn't support Record<bigint, any>
// https://github.com/microsoft/TypeScript/issues/46395
const parentId = Number(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