Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods for inspecting GeoJSON clusters #6829

Merged
merged 4 commits into from
Jun 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 95 additions & 57 deletions debug/cluster.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,105 @@
<script src='/debug/access_token_generated.js'></script>
<script>

var style = {
"version": 8,
"metadata": {
"test": {
"native": false,
"width": 512,
"height": 512
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 0,
center: [0, 0],
style: 'mapbox://styles/mapbox/cjf4m44iw0uza2spb3q0a7s41',
hash: true
});

map.on('load', () => {
map.addSource('geojson', {
"type": "geojson",
"data": "/test/integration/data/places.geojson",
"cluster": true,
"clusterRadius": 50
});
map.addLayer({
"id": "cluster",
"type": "circle",
"source": "geojson",
"filter": ["==", "cluster", true],
"paint": {
"circle-color": "rgba(0, 200, 0, 1)",
"circle-radius": 20
}
},
"center": [0, 0],
"zoom": 0,
"sources": {
"geojson": {
"type": "geojson",
"data": "/test/integration/data/places.geojson",
"cluster": true,
"clusterRadius": 25
});
map.addLayer({
"id": "cluster_label",
"type": "symbol",
"source": "geojson",
"filter": ["==", "cluster", true],
"layout": {
"text-field": "{point_count}",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-size": 12,
"text-allow-overlap": true,
"text-ignore-placement": true
}
},
"glyphs": "/test/integration/glyphs/{fontstack}/{range}.pbf",
"layers": [
{
"id": "cluster",
"type": "circle",
"source": "geojson",
"filter": ["==", "cluster", true],
"paint": {
"circle-color": "rgba(0, 200, 0, 1)",
"circle-radius": 20
}
},
{
"id": "cluster_label",
"type": "symbol",
"source": "geojson",
"filter": ["==", "cluster", true],
"layout": {
"text-field": "{point_count}",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-size": 12,
"text-allow-overlap": true,
"text-ignore-placement": true
}
},
{
"id": "unclustered_point",
"type": "circle",
"source": "geojson",
"filter": ["!=", "cluster", true],
"paint": {
"circle-color": "rgba(0, 0, 200, 1)",
"circle-radius": 10
}
});
map.addLayer({
"id": "unclustered_point",
"type": "circle",
"source": "geojson",
"filter": ["!=", "cluster", true],
"paint": {
"circle-color": "rgb(0, 0, 200)",
"circle-radius": 10
}
]
};
});

var map = window.map = new mapboxgl.Map({
container: 'map',
style: style,
hash: true
var hoverData = {
"type": "FeatureCollection",
"features": []
};

map.addSource('cluster_children', {
"type": "geojson",
"data": hoverData
});

map.addLayer({
"id": "cluster_children",
"type": "circle",
"source": "cluster_children",
"paint": {
"circle-color": "rgb(0, 150, 0)",
"circle-radius": 7
}
});

function updateHover(features) {
hoverData.features = features;
map.getSource('cluster_children').setData(hoverData);
}

map.on('click', 'cluster', (e) => {
var feature = e.features[0];
map.getSource('geojson').getClusterExpansionZoom(feature.properties.cluster_id, (err, zoom) => {
if (err) throw err;
updateHover([]);
map.easeTo({
center: feature.geometry.coordinates,
zoom: zoom
});
});
});

map.on('mouseenter', 'cluster', (e) => {
map.getCanvas().style.cursor = 'pointer';

map.getSource('geojson').getClusterLeaves(e.features[0].properties.cluster_id, Infinity, 0, (err, features) => {
if (err) throw err;
updateHover(features);
});
});

map.on('mouseleave', 'cluster', (e) => {
map.getCanvas().style.cursor = '';
updateHover([]);
});
});

map.addControl(new mapboxgl.NavigationControl());
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"rw": "^1.3.3",
"shuffle-seed": "^1.1.6",
"sort-object": "^0.3.2",
"supercluster": "^2.3.0",
"supercluster": "^4.0.1",
"through2": "^2.0.3",
"tinyqueue": "^1.1.0",
"vt-pbf": "^3.0.1"
Expand Down
48 changes: 46 additions & 2 deletions src/source/geojson_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ class GeoJSONSource extends Evented implements Source {
this.fire(new Event('dataloading', {dataType: 'source'}));
this._updateWorkerData((err) => {
if (err) {
return this.fire(new ErrorEvent(err));
this.fire(new ErrorEvent(err));
return;
}

const data: Object = { dataType: 'source', sourceDataType: 'content' };
Expand All @@ -189,12 +190,55 @@ class GeoJSONSource extends Evented implements Source {
return this;
}

/**
* For clustered sources, fetches the zoom at which the given cluster expands.
*
* @param clusterId The value of the cluster's `cluster_id` property.
* @param callback A callback to be called when the zoom value is retrieved (`(error, zoom) => { ... }`).
* @returns {GeoJSONSource} this
*/
getClusterExpansionZoom(clusterId: number, callback: Callback<number>) {
this.dispatcher.send('geojson.getClusterExpansionZoom', { clusterId, source: this.id }, callback, this.workerID);
return this;
}

/**
* For clustered sources, fetches the children of the given cluster on the next zoom level (as an array of GeoJSON features).
*
* @param clusterId The value of the cluster's `cluster_id` property.
* @param callback A callback to be called when the features are retrieved (`(error, features) => { ... }`).
* @returns {GeoJSONSource} this
*/
getClusterChildren(clusterId: number, callback: Callback<Array<GeoJSONFeature>>) {
this.dispatcher.send('geojson.getClusterChildren', { clusterId, source: this.id }, callback, this.workerID);
return this;
}

/**
* For clustered sources, fetches the original points that belong to the cluster (as an array of GeoJSON features).
*
* @param clusterId The value of the cluster's `cluster_id` property.
* @param limit The maximum number of features to return.
* @param offset The number of features to skip (e.g. for pagination).
* @param callback A callback to be called when the features are retrieved (`(error, features) => { ... }`).
* @returns {GeoJSONSource} this
*/
getClusterLeaves(clusterId: number, limit: number, offset: number, callback: Callback<Array<GeoJSONFeature>>) {
this.dispatcher.send('geojson.getClusterLeaves', {
source: this.id,
clusterId,
limit,
offset
}, callback, this.workerID);
return this;
}

/*
* Responsible for invoking WorkerSource's geojson.loadData target, which
* handles loading the geojson data and preparing to serve it up as tiles,
* using geojson-vt or supercluster as appropriate.
*/
_updateWorkerData(callback: Function) {
_updateWorkerData(callback: Callback<void>) {
const options = extend({}, this.workerOptions);
const data = this._data;
if (typeof data === 'string') {
Expand Down
20 changes: 18 additions & 2 deletions src/source/geojson_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import type {LoadVectorDataCallback} from './vector_tile_worker_source';
import type {RequestParameters} from '../util/ajax';
import type { Callback } from '../types/callback';

export type GeoJSON = Object;

export type LoadGeoJSONParameters = {
request?: RequestParameters,
data?: string,
Expand All @@ -37,6 +35,12 @@ export type LoadGeoJSONParameters = {
export type LoadGeoJSON = (params: LoadGeoJSONParameters, callback: Callback<mixed>) => void;

export interface GeoJSONIndex {
getTile(z: number, x: number, y: number): Object;

// supercluster methods
getClusterExpansionZoom(clusterId: number): number;
getChildren(clusterId: number): Array<GeoJSONFeature>;
getLeaves(clusterId: number, limit: number, offset: number): Array<GeoJSONFeature>;
}

function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataCallback) {
Expand Down Expand Up @@ -274,6 +278,18 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
}
callback();
}

getClusterExpansionZoom(params: {clusterId: number}, callback: Callback<number>) {
callback(null, this._geoJSONIndex.getClusterExpansionZoom(params.clusterId));
}

getClusterChildren(params: {clusterId: number}, callback: Callback<Array<GeoJSONFeature>>) {
callback(null, this._geoJSONIndex.getChildren(params.clusterId));
}

getClusterLeaves(params: {clusterId: number, limit: number, offset: number}, callback: Callback<Array<GeoJSONFeature>>) {
callback(null, this._geoJSONIndex.getLeaves(params.clusterId, params.limit, params.offset));
}
}

export default GeoJSONWorkerSource;
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5911,9 +5911,9 @@ just-extend@^1.1.27:
version "1.1.27"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"

kdbush@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-1.0.1.tgz#3cbd03e9dead9c0f6f66ccdb96450e5cecc640e0"
kdbush@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-2.0.1.tgz#90c6128e3001ac68c550d7c9e2f222c0269666f1"

kebab-case@^1.0.0:
version "1.0.0"
Expand Down Expand Up @@ -9695,11 +9695,11 @@ sugarss@^1.0.0:
dependencies:
postcss "^6.0.14"

supercluster@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-2.3.0.tgz#87ab56081bbea9a1d724df5351ee9e8c3af2f48b"
supercluster@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-4.0.1.tgz#eead7ab49f50322b265e0087859ebcabdc5c2ed8"
dependencies:
kdbush "^1.0.1"
kdbush "^2.0.1"

supports-color@^2.0.0:
version "2.0.0"
Expand Down