OpenLayers 3 Reload Layer(s)
Asked Answered
C

2

9

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!

Cellarer answered 7/7, 2016 at 16:8 Comment(5)
It does exist for some sourcetypes: TileWMS.Biak
Ah, I see! Do you have any idea of a solution that would work for all source types? It seems like this would be a pretty basic feature that should be built in to the layer object.Cellarer
I think the KML-solution you refer to might still work. You are looking at map and layers, you should look at sources. Many (all?) ol sourcetypes have the refresh() method mentioned there.Biak
You didn't say, after all, what is your kind of source and what kind of file you are retrieving.Spirited
@JonatasWalker Thank you for pointing that out! I must have edited that out of my post at some point... I'm looking for a method to refresh vector sources, mainly KML and GeoJSON.Cellarer
S
3

Well, there are another options! Have your own loader.

Load this script - just in case someone is still using old browsers

<script src="//cdn.polyfill.io/v2/polyfill.min.js?features=fetch"></script>

Then load your JSON file and know when it's ready/loaded:

function refreshSelectedLayer(layer) {

  var now = Date.now();
  var source = layer.getSource();
  var format = new ol.format.GeoJSON();
  var url = '//your_server.net/tmp/points.json?t=' + now;


  fetch(url)
    .then(function(response) {
      return response.json();
    }).then(function(json) {
      console.log('parsed json', json);

      source.clear(); // if this is not enough try yours

      var features = format.readFeatures(json, {
        featureProjection: 'EPSG:3857'
      });
      source.addFeatures(features);

    }).catch(function(ex) {
      console.log('parsing failed', ex);
    });
}
Spirited answered 17/8, 2016 at 20:31 Comment(0)
S
2

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.

Spirited answered 10/8, 2016 at 15:4 Comment(4)
Best solution so far! It is working intermittently. I noticed that after updating the KML file (adding or removing features) it will load the new features each time, however it will not remove the features that have been deleted from the KML file. Even after refreshing the entire page these features stay behind. It takes reloading the page several times over the course of 5-10 minutes before they disappear. Any ideas?Cellarer
In addition, whenever I change the name of a feature, the change shows up immediately if I have added to the name. If I set the name back to it's original state and try to reload the change is not picked up unless I reload the entire page.Cellarer
Would you perhaps know how to implement the optional success failure parameters for the featureloader?Cellarer
This worked for me also. I used it inside an 'setInterval' (set interval) function similar to how ajax works.Femur

© 2022 - 2024 — McMap. All rights reserved.