Skip to content

Commit

Permalink
introduce clusterProperties option for aggregating cluster properties
Browse files Browse the repository at this point in the history
  • Loading branch information
mourner committed Jan 4, 2019
1 parent 1208cfc commit 7624804
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 6 deletions.
11 changes: 8 additions & 3 deletions debug/cluster.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@
"type": "geojson",
"data": "/test/integration/data/places.geojson",
"cluster": true,
"clusterRadius": 50
"clusterRadius": 50,
"clusterProperties": {
"max": ["max", 0, ["get", "scalerank"]],
"sum": ["+", 0, ["get", "scalerank"]],
"has_island": ["any", false, ["==", ["get", "featureclass"], "island"]]
}
});
map.addLayer({
"id": "cluster",
"type": "circle",
"source": "geojson",
"filter": ["==", "cluster", true],
"paint": {
"circle-color": "rgba(0, 200, 0, 1)",
"circle-color": ["case", ["get", "has_island"], "orange", "rgba(0, 200, 0, 1)"],
"circle-radius": 20
}
});
Expand All @@ -49,7 +54,7 @@
"source": "geojson",
"filter": ["==", "cluster", true],
"layout": {
"text-field": "{point_count}",
"text-field": "{point_count} ({max})",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-size": 12,
"text-allow-overlap": true,
Expand Down
3 changes: 2 additions & 1 deletion src/source/geojson_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ class GeoJSONSource extends Evented implements Source {
extent: EXTENT,
radius: (options.clusterRadius || 50) * scale,
log: false
}
},
clusterProperties: options.clusterProperties
}, options.workerOptions);
}

Expand Down
56 changes: 55 additions & 1 deletion src/source/geojson_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Supercluster from 'supercluster';
import geojsonvt from 'geojson-vt';
import assert from 'assert';
import VectorTileWorkerSource from './vector_tile_worker_source';
import { createExpression } from '../style-spec/expression';

import type {
WorkerTileParameters,
Expand Down Expand Up @@ -171,7 +172,7 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {

try {
this._geoJSONIndex = params.cluster ?
new Supercluster(params.superclusterOptions).load(data.features) :
new Supercluster(getSuperclusterOptions(params)).load(data.features) :
geojsonvt(data, params.geojsonVtOptions);
} catch (err) {
return callback(err);
Expand Down Expand Up @@ -293,4 +294,57 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
}
}

function getSuperclusterOptions({superclusterOptions, clusterProperties}) {
if (!clusterProperties || !superclusterOptions) return superclusterOptions;

const initialValues = {};
const mapExpressions = {};
const reduceExpressions = {};
const globals = {accumulated: null};
const feature = {properties: null};
const propertyNames = Object.keys(clusterProperties);

for (const key of propertyNames) {
const [operator, initialExpression, mapExpression] = clusterProperties[key];
initialValues[key] = createClusterExpression(key, initialExpression).evaluate(globals);
mapExpressions[key] = createClusterExpression(key, mapExpression);
reduceExpressions[key] = createClusterExpression(key, [operator, ['accumulated'], ['get', key]]);
}

superclusterOptions.initial = () => {
const properties = {};
for (const key of propertyNames) {
properties[key] = initialValues[key];
}
return properties;
};
superclusterOptions.map = (pointProperties) => {
feature.properties = pointProperties;
const properties = {};
for (const key of propertyNames) {
properties[key] = mapExpressions[key].evaluate(globals, feature);
}
return properties;
};
superclusterOptions.reduce = (accumulated, clusterProperties) => {
feature.properties = clusterProperties;
for (const key of propertyNames) {
globals.accumulated = accumulated[key];
accumulated[key] = reduceExpressions[key].evaluate(globals, feature);
}
};

return superclusterOptions;
}

function createClusterExpression(key, expression) {
const parsed = createExpression(expression, {});
if (parsed.result === 'success') {
return parsed.value;
}
/* eslint no-warning-comments: 0 */
// TODO proper error handling
console.log(`Error: ${parsed.value.map(e => e.message).join('; ')}`);
}

export default GeoJSONWorkerSource;
5 changes: 5 additions & 0 deletions src/style-spec/expression/definitions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ CompoundExpression.register(expressions, {
[],
(ctx) => ctx.globals.lineProgress || 0
],
'accumulated': [
ValueType,
[],
(ctx) => ctx.globals.accumulated
],
'+': [
NumberType,
varargs(NumberType),
Expand Down
2 changes: 1 addition & 1 deletion src/style-spec/expression/parsing_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,5 @@ function isConstant(expression: Expression) {
}

return isFeatureConstant(expression) &&
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'line-progress', 'is-supported-script']);
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'line-progress', 'accumulated', 'is-supported-script']);
}
4 changes: 4 additions & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@
"type": "number",
"doc": "Max zoom on which to cluster points if clustering is enabled. Defaults to one zoom less than maxzoom (so that last zoom features are not clustered)."
},
"clusterProperties": {
"type": "*",
"doc": "TODO cluster properties"
},
"lineMetrics": {
"type": "boolean",
"default": false,
Expand Down
1 change: 1 addition & 0 deletions src/style-spec/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export type GeoJSONSourceSpecification = {|
"cluster"?: boolean,
"clusterRadius"?: number,
"clusterMaxZoom"?: number,
"clusterProperties"?: mixed,
"lineMetrics"?: boolean,
"generateId"?: boolean
|}
Expand Down

0 comments on commit 7624804

Please sign in to comment.