socib / Leaflet.TimeDimension

Add time dimension capabilities on a Leaflet map.
MIT License
436 stars 138 forks source link

Loading GeoJSON step by step #19

Closed fox91 closed 8 years ago

fox91 commented 8 years ago

Hi @bielfrontera, you can at the time, load the various steps of a GeoJSON, as with a WMS layer?

I'll explain: I have a GeoJSON that contains a lot of data (tens of MB). You can load a file (eg index.json) containing an array of dates (eg ["20151202T220000Z","20151202T230000Z"]), and then load the various GeoJSON necessary (eg 20151202T220000Z.geojson, 20151202T230000Z.geojson, etc.)?

bielfrontera commented 8 years ago

Hi @fox91, yes, you can show GeoJSON layers with data obtained independently for each time.

This is the idea in example12. It doesn't use a GeoJSON layer (its a Heatmap), but at the end it's the same.

Here you have the code adapted to GeoJSON. I haven't tested it, and it doesn't include any cache.

L.TimeDimension.Layer.AjaxGeoJSON = L.TimeDimension.Layer.extend({

    initialize: function(layer, options) {
        L.TimeDimension.Layer.prototype.initialize.call(this, layer, options);
        this._currentLoadedTime = 0;
        this._currentTimeData = null;
    },

    onAdd: function(map) {
        L.TimeDimension.Layer.prototype.onAdd.call(this, map);
        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() {
        if (!this._map)
            return;
        var layer = L.geoJson(this._currentTimeData, this._baseLayer.options);
        if (this._currentLayer) {
            this._map.removeLayer(this._currentLayer);
        }
        layer.addTo(this._map);
        this._currentLayer = layer;
    },

    _getDataForTime: function(time) {
        if (!this._map) {
            return;
        }
        var d = new Date(time);
        var url = d.format("yyyymmdd'T'HHMMss'Z'", true) + '.geojson';
        var callback = function(status, data) {            
            if (status == 'ok'){
                this._currentTimeData = data;
            } else{
                this._currentTimeData = [];
            }
            this._currentLoadedTime = time;
            if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
                this._update();
            }
            this.fire('timeload', {
                time: time
            });
        };
        $.getJSON(url, callback.bind(this, 'ok')).fail(callback.bind(this, 'error'));
    },
});

L.timeDimension.layer.ajaxGeoJSON = function(layer, options) {
    return new L.TimeDimension.Layer.AjaxGeoJSON(layer, options);
};

Something similar might be added to the plugin as new kind of timedimension layer. It should include as an option a function to construct the URL of the data from a time.

rsethur commented 8 years ago

Hi @bielfrontera ,

Any chance we can get clear comments on the above code: i.e for better understanding? Currently i want to use this library for updating the leaflet map : every month will have a geo json (not wms based). I'm working on a data science project with the geo political data set(GDELT): and I would love to use this library for viz.

Cheers

rsethur commented 8 years ago

Thanks I figured it out :) :+1:

bielfrontera commented 8 years ago

Hi @rsethur.

Ok, perfect. Basically, it loads json data via ajax from a dynamic url (constructed with time as parameter). This data is stored at _currentTimeData. Then, when the layer is updated (this is called from timedimension, when a new time is applied), this class removes the old layer and creates a new layer with current data.

You might change the code to add an object to store the data for each time, and therefore, you can have cached times and create a buffer (like the cache for WMS layers. See leaflet.timedimension.layer.wms.js ).

I think you can take a look of the code of this example, which loads a json with tweets location for each time to make a heatmap: http://elpais.com/elpais/2015/09/10/ciencia/1441884739_007223.html

maddan commented 5 years ago

@bielfrontera Have you done similar but to get the Weather API metadata (Open Weather Map API) to be precise when you drag the player button?

tekija commented 3 years ago

I tried to mimic this behavior with some weather data in 24 JSON files. I changed your code in order do link heatmap plugin with data of each JSON, here is the full code of javascript:

Date.prototype.format = function (mask, utc) {
    return dateFormat(this, mask, utc);
};
L.TimeDimension.Layer.AjaxGeoJSON = L.TimeDimension.Layer.extend({

    initialize: function() {
        var heatmapCfg = this._getHeatmapOptions();
        console.log(heatmapCfg)
        var layer = new HeatmapOverlay(heatmapCfg);
        L.TimeDimension.Layer.prototype.initialize.call(this, layer);
        this._currentLoadedTime = 0;
        this._currentTimeData = null

        ;
    },

    _getHeatmapOptions: function() {
        var config = {};
        var defaultConfig = {
            radius: 0.3,
            maxOpacity: .8,
            scaleRadius: false,
            useLocalExtrema: false,
            latField: 'lat',
            lngField: 'lng',
            valueField: 'temp'
        };
        for (var attrname in defaultConfig) {
            config[attrname] = defaultConfig[attrname]; 
        }

        return config;
    },

    onAdd: function(map) {
        L.TimeDimension.Layer.prototype.onAdd.call(this, map);
        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() {

        if (!this._map)
            return;
        var layer = L.geoJson(this._currentTimeData, this._baseLayer.options);
        if (this._currentLayer) {
            this._map.removeLayer(this._currentLayer);
        }
        layer.addTo(this._map);
        this._currentLayer = layer;
    },

    _getDataForTime: function(time) {
        if (!this._map) {
            return;
        }
        var d = new Date(time);
        url0="https://raw.githubusercontent.com/tekija/LCd/master/"
        var url = url0+ d.format("yyyy-mm-dd'T'HH", true) + '.geojson';
        var callback = function(status, data) {        
            console.log(status);
            // console.log(data.features.length);
            // console.log(data.features[0].geometry.coordinates[1]);
            this._currentTimeData = [];
            if (status == 'ok'){
                for (var i = 0; i < data.features.length; i++) {

                    // console.log(this._currentTimeData);
                        this._currentTimeData.push({
                            lat: data.features[i].geometry.coordinates[1],
                            lng: data.features[i].geometry.coordinates[0],
                            temp: data.features[i].properties.t2m
                        });

                }
                //console.log(this._currentTimeData);

                // this._currentTimeData = data;
            } else{
                this._currentTimeData = [];
            }
            this._currentLoadedTime = time;
            if (this._timeDimension && time == this._timeDimension.getCurrentTime() && !this._timeDimension.isLoading()) {
                this._update();
            }
            this.fire('timeload', {
                time: time
            });
        };

        $.getJSON(url, callback.bind(this, 'ok')).fail(callback.bind(this, 'error'));

    },

});

L.timeDimension.layer.ajaxGeoJSON = function( ) {
    return new L.TimeDimension.Layer.AjaxGeoJSON( );
};

var currentTime = new Date();
currentTime.setUTCDate(1, 0, 0, 0, 0);

var map = L.map('map', {
    zoom: 9,
    fullscreenControl: true,
    timeDimension: true,
    timeDimensionOptions: {
        timeInterval: "2020-01-01/2020-01-02",
        period: "PT1H",
        currentTime: currentTime
    },
    center: [35, 25],
});

var layer = new L.StamenTileLayer("toner-lite");
map.addLayer(layer);

var geojsonLayer = L.timeDimension.layer.ajaxGeoJSON(
    {

    }

);
geojsonLayer.addTo(map);

L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
    _getDisplayDateFormat: function(date){

        return date.format("mm dd hh");
    }
});
var timeDimensionControl = new L.Control.TimeDimensionCustom({
    playerOptions: {
        buffer: 1,
        minBufferReady: -1
    }
});
map.addControl(this.timeDimensionControl);

This is not working due to this._currentTimeData is set to null. Can you please provide some guidelines for achieving this behaviour.

bielfrontera commented 3 years ago

Hi @tekija, change this._currentTimeData.data = []; by this._currentTimeData = []; and this._currentTimeData.data.push by this._currentTimeData.push. You were trying to add a property (data) to an object that has been initialised with null (_currentTimeData).

Hope this helps!

tekija commented 3 years ago

@bielfrontera Great hint. Helped with correcting error. TimeDimension is running fine and loads data in __currentTimeDatain each iteration, but i still cant get heatmap animated. I guess I am not feeding heatmap layer properly, but still cant see how to fix this. Edit: It looks like module _update is not converting layer properly in order to be read with heatmap.