@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
190 lines • 9.44 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _Cluster_trees, _Cluster_stride;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cluster = void 0;
const kdbush_1 = __importDefault(require("kdbush"));
const OFFSET_ZOOM = 2;
const OFFSET_ID = 3;
const OFFSET_PARENT = 4;
const OFFSET_NUM = 5;
const OFFSET_PROP = 6;
const DEFAULT_OPTIONS = {
minZoom: 0, // min zoom to generate clusters on
maxZoom: 16, // max zoom level to cluster the points on
minPoints: 2, // minimum points to form a cluster
radius: 256, // cluster radius in pixels
nodeSize: 64, // size of the KD-tree leaf node, affects performance
// a reduce function for calculating custom cluster properties
reduce: null, // (accumulated, props) => { accumulated.sum += props.sum; }
map: (props) => props, // props => ({sum: props.my_value})
};
class Cluster {
constructor(options = {}) {
this.options = options;
_Cluster_trees.set(this, []);
_Cluster_stride.set(this, void 0);
this.clusterProps = [];
this.options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), this.options);
__classPrivateFieldSet(this, _Cluster_stride, this.options.reduce ? 7 : 6, "f");
__classPrivateFieldSet(this, _Cluster_trees, new Array(this.options.maxZoom + 1), "f");
this.clusterProps = [];
}
load(points) {
const { minZoom, maxZoom } = this.options;
this.points = points;
// generate a cluster object for each point and index input points into a KD-tree
const data = [];
for (let i = 0; i < points.length; i++) {
const { x, y } = points[i];
// store internal point/cluster data in flat numeric arrays for performance
data.push(x, y, // projected point coordinates
Infinity, // the last zoom the point was processed at
i, // index of the source feature in the original input array
-1, // parent cluster id
1);
}
let tree = (__classPrivateFieldGet(this, _Cluster_trees, "f")[maxZoom + 1] = this.createTree(data));
// cluster points on max zoom, then cluster the results on previous zoom, etc.;
// results in a cluster hierarchy across zoom levels
for (let z = maxZoom; z >= minZoom; z--) {
// create a new set of clusters for the zoom and index them with a KD-tree
tree = __classPrivateFieldGet(this, _Cluster_trees, "f")[z] = this.createTree(this.cluster(tree, z));
}
}
getClusters(bbox, zoom) {
const tree = __classPrivateFieldGet(this, _Cluster_trees, "f")[this.limitZoom(zoom)];
const [minX, minY, maxX, maxY] = bbox;
const ids = tree.range(minX, minY, maxX, maxY);
const data = tree.data;
const clusters = [];
for (const id of ids) {
const k = __classPrivateFieldGet(this, _Cluster_stride, "f") * id;
clusters.push(data[k + OFFSET_NUM] > 1
? getClusterJSON(data, k, this.clusterProps)
: this.points[data[k + OFFSET_ID]]);
}
return clusters;
}
limitZoom(z) {
return Math.max(this.options.minZoom, Math.min(Math.floor(+z), this.options.maxZoom + 1));
}
createTree(data) {
const tree = new kdbush_1.default((data.length / __classPrivateFieldGet(this, _Cluster_stride, "f")) | 0, this.options.nodeSize, Float32Array);
for (let i = 0; i < data.length; i += __classPrivateFieldGet(this, _Cluster_stride, "f"))
tree.add(data[i], data[i + 1]);
tree.finish();
tree.data = data;
return tree;
}
cluster(tree, zoom) {
const { radius, reduce, minPoints } = this.options;
const r = radius / Math.pow(2, zoom);
const data = tree.data;
const nextData = [];
const stride = __classPrivateFieldGet(this, _Cluster_stride, "f");
// loop through each point
for (let i = 0; i < data.length; i += stride) {
// if we've already visited the point at this zoom level, skip it
if (data[i + OFFSET_ZOOM] <= zoom)
continue;
data[i + OFFSET_ZOOM] = zoom;
// find all nearby points
const x = data[i];
const y = data[i + 1];
const neighborIds = tree.within(data[i], data[i + 1], r);
const numPointsOrigin = data[i + OFFSET_NUM];
let numPoints = numPointsOrigin;
// count the number of points in a potential cluster
for (const neighborId of neighborIds) {
const k = neighborId * stride;
// filter out neighbors that are already processed
if (data[k + OFFSET_ZOOM] > zoom)
numPoints += data[k + OFFSET_NUM];
}
// if there were neighbors to merge, and there are enough points to form a cluster
if (numPoints > numPointsOrigin && numPoints >= minPoints) {
let wx = x * numPointsOrigin;
let wy = y * numPointsOrigin;
let clusterProperties;
let clusterPropIndex = -1;
// encode both zoom and point index on which the cluster originated -- offset by total length of features
const id = (((i / stride) | 0) << 5) + (zoom + 1) + this.points.length;
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom)
continue;
data[k + OFFSET_ZOOM] = zoom; // save the zoom (so it doesn't get processed twice)
const numPoints2 = data[k + OFFSET_NUM];
wx += data[k] * numPoints2; // accumulate coordinates for calculating weighted center
wy += data[k + 1] * numPoints2;
data[k + OFFSET_PARENT] = id;
if (reduce) {
if (!clusterProperties) {
clusterProperties = this.map(data, i, true);
clusterPropIndex = this.clusterProps.length;
this.clusterProps.push(clusterProperties);
}
reduce(clusterProperties, this.map(data, k));
}
}
data[i + OFFSET_PARENT] = id;
nextData.push(wx / numPoints, wy / numPoints, Infinity, id, -1, numPoints);
if (reduce)
nextData.push(clusterPropIndex);
}
else {
// left points as unclustered
for (let j = 0; j < stride; j++)
nextData.push(data[i + j]);
if (numPoints > 1) {
for (const neighborId of neighborIds) {
const k = neighborId * stride;
if (data[k + OFFSET_ZOOM] <= zoom)
continue;
data[k + OFFSET_ZOOM] = zoom;
for (let j = 0; j < stride; j++)
nextData.push(data[k + j]);
}
}
}
}
return nextData;
}
map(data, i, clone = false) {
if (data[i + OFFSET_NUM] > 1) {
const props = this.clusterProps[data[i + OFFSET_PROP]];
return clone ? Object.assign({}, props) : props;
}
const original = this.points[data[i + OFFSET_ID]].properties;
const result = this.options.map(original);
return clone && result === original ? Object.assign({}, result) : result;
}
}
exports.Cluster = Cluster;
_Cluster_trees = new WeakMap(), _Cluster_stride = new WeakMap();
function getClusterJSON(data, i, clusterProps) {
const count = data[i + OFFSET_NUM];
const propIndex = data[i + OFFSET_PROP];
const properties = propIndex === -1 ? {} : Object.assign({}, clusterProps[propIndex]);
return {
id: data[i + OFFSET_ID],
x: data[i],
y: data[i + 1],
properties: Object.assign({ cluster: true, count }, properties),
};
}
//# sourceMappingURL=cluster.js.map