Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenLayers 3 Reload Layer(s)

I am working on a project using OL3 in which I need to be able to manually (by button press) or automatically (time based) reload vector layers IF they have been updated since the last load using HTTP conditional GETs (304 headers and such).

I found this very old post (https://gis.stackexchange.com/questions/333/how-to-dynamically-refresh-reload-a-kml-layer-in-openlayers) for KML layers, but it appears to use variables no longer found in OL3 and I am not sure that it would allow for only loading files that have been modified since the last load. At first glance it appears that a full reload is forced, even if the file has not been modified.

There does not seem to be anything in the API that resembles a reload function for the map or layer objects in OL3. Is there a way to do this?

Update 1:

I found a possible way to do this as an answer in this question: https://gis.stackexchange.com/questions/125074/openlayers3-how-to-reload-a-layer-from-geoserver-when-underlying-data-change using the code:

layer.getSource().updateParams({"time": Date.now()});

however when I run this code I get the error:

TypeError: selectedLayer.getSource(...).updateParams is not a function

Upon checking the API Reference for OL3 it appears that no such functions exist. The closest is setProperties() or setAttributions(). Neither of which work. It also appears that not all layer types implement getSource().

Update 2:

The refresh() reloads the tiles, but does not appear to be requesting them from the server. Rather, it appears they are being loaded from a cache (but not the HTTP cache). No requests are made, no HTTP 304s or anything like that. Will be trying a variant of the KML approach and posting the results soon.

Update 3:

After trying LOTS of different solutions I accidentally stumbled upon something that worked for vector layers. By calling the layer source.clear() function and then calling Map.updateSize(), the layer is automagically reloaded from it's source URL. An XHR GET request is issued and if the source file has changed, it will be reloaded from the file. If the source file has not changed, a 304 will be issued and the source will be reloaded from the cache.

Below is a function that should uses this method to reload a given layer:

function refreshLayer(selectedLayer)
{
    var selectedLayerSource = selectedLayer.getSource();

    if(selectedLayerSource instanceof ol.source.Vector)
    {
        //do vector reload
        selectedLayerSource.clear();
        map.updateSize();
    }
    else
    {
        //reload the entire page
        window.location.reload();
    }
}

However, it appears that on the first few tries (depending on the browser) the request is sent, a 200 code is sent back, but the layer does not reflect any changes. After a few tries (and reloading the page a few times) it works. Once it starts working for a layer it continues to work as often as the source file is changed. Does anyone have any idea what is going on?

Update 4:

Using an adaptation of Jonatas' answer I am getting better results. New features pop up instantly on a reload. However, old features are not removed from the map and many features that have moved locations are shown on the map twice. Below is my code:

function refreshSelectedLayer()
{
    console.log("This feature is still in the process of being implemented. Refresh may not actually occur.");

    var selectedLayerSource = selectedLayer.getSource();

    if(selectedLayerSource instanceof ol.source.Vector)
    {
        var now = Date.now();
        var format = selectedLayerSource.getFormat();
        var url = selectedLayerSource.getUrl();
        url = url + '?t=' + now;

        loader = ol.featureloader.xhr(url, format);

        selectedLayerSource.clear();
        loader.call(selectedLayerSource, [], 1, 'EPSG:3857');

        map.updateSize();
    }
    else if(selectedLayerSource instanceof ol.source.Tile)
    {
        selectedLayerSource.changed();
        selectedLayerSource.refresh();
    }
}

Note that the var selectedLayer is set elsewhere in the code. Any ideas why these very odd results are occuring?

Update 5:

I noticed that if I remove all other code besides the:

source.clear();

call an XHR GET request is made and the features do not disappear. Why is clearing the source not removing all of the features?

Update 6:

After discovering that ol.source.clear() was not actually removing features from a given data source/layer I replaced it using the following code:

selectedLayerSource.forEachFeature(function(feature){
        selectedLayerSource.removeFeature(feature);
    });

By outputting the features in the layer before and after each step, I got this:

var now = Date.now();
    var format = selectedLayerSource.getFormat();
    var url = selectedLayerSource.getUrl();
    url = url + '?t=' + now;

    console.log("time: "+now+"  format: "+format+"  url: "+url);

    loader = ol.featureloader.xhr(url, format);

    console.log(selectedLayerSource.getFeatures());
    console.log("Deleting features...");

    /*
        Try adding code here to manually remove all features from source
    */
    selectedLayerSource.forEachFeature(function(feature){
        selectedLayerSource.removeFeature(feature);
    });

    console.log(selectedLayerSource.getFeatures());

    console.log("Loading features from file...");

    loader.call(selectedLayerSource, [], 1, 'EPSG:3857');

    window.setTimeout(function(){
        console.log(selectedLayerSource.getFeatures());
        map.updateSize();
    }, 500);

Which outputs into the console:

"time: 1471462410554  format: [object Object]  url: http://server/file.ext?t=1471462410554" file.php:484:3
Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, 1 more… ] file.php:491:3
Deleting features... file.php:492:3
Array [  ] file.php:501:3
Loading features from file... file.php:503:3
GET XHR http://server/file.ext [HTTP/1.1 200 OK 34ms]
Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, 1 more… ]

After several tests with GeoJSON and KML layers I confirmed that this method works!!!

However, because the loader makes its request asynchronously, I am left with the problem of how to execute code after the loader function has been called. Obviously using setTimeout() is a horrible way to do this and was only implemented for testing purposes. A success/failure callback function would be perfect and when looking at the source of featureloader.js it appears that they are offered as parameters in ol.featureloader.loadFeaturesXhr. See below code block from featureloader.js:

/**
 * @param {string|ol.FeatureUrlFunction} url Feature URL service.
 * @param {ol.format.Feature} format Feature format.
 * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success
 *     Function called with the loaded features and optionally with the data
 *     projection. Called with the vector tile or source as `this`.
 * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure
 *     Function called when loading failed. Called with the vector tile or
 *     source as `this`.
 * @return {ol.FeatureLoader} The feature loader.
 */
ol.featureloader.loadFeaturesXhr = function(url, format, success, failure)

I attempted to implement these functions like so when creating the loader:

loader = ol.featureloader.xhr(url, format, 
        function(){
            console.log(selectedLayerSource.getFeatures());
            map.updateSize();
            console.log("Successful load!");
        },
        function(){
            console.log("Could not load "+selectedLayerName+" layer data from "+url);
        }
    );

but neither function is being called. Any suggestions? I feel like I am missing something really simple here...

Update 7:

Using the solution provided by @Jonatas Walker I adapted it to use jQuery:

        var now = Date.now();
        var format = selectedLayerSource.getFormat();
        var url = selectedLayerSource.getUrl();
        url = url + '?t=' + now;

        //make AJAX request to source url
        $.ajax({url: url, success: function(result){

            //manually remove features from the source
            selectedLayerSource.forEachFeature(function(feature){
                selectedLayerSource.removeFeature(feature);
            });

            //create features from AJAX results
            var features = format.readFeatures(result, {
                featureProjection: 'EPSG:3857'
            });

            //add features to the source
            selectedLayerSource.addFeatures(features);

        },
        error: function(err){
            alert("Could not load features from "+selectedLayerName+" at "+url+" error code: "+err.status);
        }
        });

After extensive testing with GeoJSON and KML sources this has proved an extremely reliable refresh method!

like image 463
user2395126 Avatar asked Jul 07 '16 16:07

user2395126


1 Answers

Try an adaptation of this:

function refreshSource() {
  var now = Date.now();
  var source = vectorLayer.getSource();
  var format = new ol.format.GeoJSON();
  var url = '//your_server.net/tmp/points.json?t=' + now;
  var loader = ol.featureloader.xhr(url, format);

  source.clear();
  loader.call(source, [], 1, 'EPSG:3857');
}

The trick is to tell the browser this is a new loading by changing the url.

like image 69
Jonatas Walker Avatar answered Oct 06 '22 00:10

Jonatas Walker