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

TimeDimension with clustered data #189

Open
francescocretti opened this issue Apr 14, 2020 · 1 comment
Open

TimeDimension with clustered data #189

francescocretti opened this issue Apr 14, 2020 · 1 comment

Comments

@francescocretti
Copy link

francescocretti commented Apr 14, 2020

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.

@francescocretti
Copy link
Author

francescocretti commented Apr 17, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant