TimeDimension with clustered data
Hi everyone.
Somebody have ever managed to use TimeDimension with clustered data? For example I'm currently using Supercluster plugin to render big amount of data, and I need to add a timeline feature.
This is a snippet from my code:
const clustersIndex = new Supercluster();
clustersIndex.load(myData);
const clustersLayer = L.geoJSON(null, {
pointToLayer : (feature, latlng) => generateIcon(options, feature, latlng)
});
const bounds = window.map.getBounds();
const bbox = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
const zoom = window.map.getZoom();
const clusters = clustersIndex.getClusters(bbox, zoom);
clustersLayer.clearLayers();
clustersLayer.addData(clusters);
const timedClusters = L.timeDimension.layer.timedGeojson(clustersLayer, {
addlastPoint: false,
updateTimeDimension: true,
updateTimeDimensionMode: 'intersect'
});
timedClusters.addTo(myMap);
The clusters appear correctly on the map but they're not affected by the time control.
I suppose it depends on how Supercluster processes the data, probably hiding the time information from TimeDimension.
Before starting to dig deeper I opened the issue to figure if someone has been through this before :)
Thanks.
Ok I figured out a solution: I worte a custom TimeDimension Layer that calls my API for each timestamp requested by the user with an action on the control timeline. Then when I get the data I perform the clusers computation and refresh the view.
Something like this:
L.TimeDimension.Layer.SuperClusterLayer = L.TimeDimension.Layer.extend({
initialize: function(options) {
// options
this._baseURL = options.baseURL || null;
this._clustersIndex = new Supercluster({
radius: 90
});
const clustersLayer = L.geoJSON(null, {
pointToLayer : (feature, latlng) => createClusterIcon(feature, latlng) // choose marker icon
});
L.TimeDimension.Layer.prototype.initialize.call(this, clustersLayer, options);
this._currentLoadedTime = 0;
this._currentTimeData = [];
},
onAdd: function(map) {
L.TimeDimension.Layer.prototype.onAdd.call(this, map);
map.addLayer(this._baseLayer);
// update clusters on map movements
map.on('moveend', e => {
this._updateClusters();
});
if (this._timeDimension) {
this._getDataForTime(this._timeDimension.getCurrentTime());
}
},
_onNewTimeLoading: function(ev) {
this._getDataForTime(ev.time);
return;
},
isReady: function(time) {
return (this._currentLoadedTime == time);
},
_update: function() {
// load new data
this._clustersIndex.load(this._currentTimeData);
// perform clustering
this._updateClusters();
return true;
},
_getDataForTime: function(time) {
if (!this._baseURL || !this._map || !this._mapId) {
return;
}
const url = `${this._baseURL}?timestamp=${time}`;
// get data
$.getJSON(url, json => {
this._currentTimeData = json;
this._currentLoadedTime = time;
if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
this._update();
}
this.fire('timeload', { time });
}).fail(err => console.warn('Error getting data', url, err));
},
_updateClusters: function() {
const bounds = this._map.getBounds();
const bbox = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
const zoom = this._map.getZoom();
const clusters = this._clustersIndex.getClusters(bbox, zoom);
this._baseLayer.clearLayers();
this._baseLayer.addData(clusters);
}
});
L.timeDimension.layer.clusteredLayer = options => new L.TimeDimension.Layer.SuperClusterLayer(options);
// instance example
const timedClusters = L.timeDimension.layer.clusteredLayer({
baseURL : '/my/api/url'
});
timedClusters.addTo(myMap);
If needed, a basic Supercluster example can be found here: https://github.com/mapbox/supercluster/blob/master/demo/index.js