[GIS] Getting features in source for GeoJSON vector layer, and then altering them

geojsonopenlayers

I have managed to do everything I need for getting and reading features using the technique from https://github.com/openlayers/ol3/blob/master/doc/faq.md#why-arent-there-any-features-in-my-source (Note that the link at the top of the FAQ page is not quite correct). (I am using OpenLayers 3.15)

The trouble comes when I try to actually alter a feature (eg by changing a property) then the .on('change') seems to fire again. This means that if I have 6 features they are processed repeatedly, thus: 0, 01, 012, 0123, 01234, 012345. This can confuse things, repeat indefintely, or just take a long time.

I have tried several ways round this, starting with using .once rather than .on, but either the routines fire repeatedly (sometimes) or they do not fire at all (sometimes). I have also tried using ol.Observable.unByKey(key). You will realise that I am rather struggling in the dark.

You can see some of my attempts in my rough little demo at http://mapping4ops.org/ShowMapsDev/foreachfeature.html. What I have tried to do in this is to hide alternate features (by setting a 'hidden' property) in a vector layer containg 20 features, using 4 different techniques. The essence of the code is below, but the full code has console.log statements, loop limits etc. (Operation of this is best observed by seeing the console.log messages, using eg Chrome Inspector.)

Hide1) uses simple getSource and getFeatures:
[While this works well in some circumstances most times it just returns no features]

var source = vectorLayer.getSource();
if (source.getState() === 'ready') {
    var features = source.getFeatures();
    for(var i=0; i< features.length; i++) {DO THE HIDING}}

Hide2) uses simple getSource and forEachFeature
[Similar to Hide1, most times it just returns no features]

var source = vectorLayer.getSource();
if (source.getState() === 'ready') {
    source.forEachFeature(function(feature) {DO THE HIDING})}

Hide3) uses .getSource().once('change') and forEachFeature
[I thought using .once would get over the issue of repeated firing, but it does not seem to]

vectorLayer.getSource().once('change', function(evt){
    var source = evt.target;
    if (source.getState() === 'ready') {
        source.forEachFeature(function(feature) {DO THE HIDING})}})

Hide4) uses .getSource().on('change') and forEachFeature
[This is where I started from as this works fine when the fetures are not changed]

vectorLayer.getSource().on('change', function(evt){
    var source = evt.target;
    if (source.getState() === 'ready') {
        source.forEachFeature(function(feature) {DO THE HIDING})}})

If I try hiding (and unhiding) the features, what happens depends on the order I do things (reloading the page between each attempt). For example:

Case A) Using just Hide1 or Hide2, the hiding and unhiding happens OK – BUT I know I cannot use this in other circumstances because getsource is asynchronous.

Case B) 3 then 1: Hide3 first (nothing happens), and then Hide1 (hiding happens OK, but the Hide3 fires now and then the Hide1)

Case C) 1 then 3: Hide1 (hiding happens OK), Unhide1 (OK), and then Hide3 (nothing happens)

Case D) 4 then 2: Hide4 first (nothing is changed), and then Hide2 (the Hide4 fires now repeatedly, thus: 0, 01, 012, 0123, 01234 …, and I abort it when it reaches a limit)

I did think I found the answer at https://freeopengis.blogspot.co.uk/2015/09/openlayers-3-load-start-load-end-events.html, but I do not understand it, and the fiddle does not work.

What I want is a way to access the features in the layer and manipulate them, that will work every time and not process the features more than once.

Best Answer

I have managed to find a part solution to my issue. Note that my first example site did not show the real issue, as the source was always loaded by the time the user clicks on a button. The problem I have mostly occurs when I am trying to do someting before the (asynchronous) load of features.

My new example site is http://mapping4ops.org/ShowMapsDev/foreachfeature2.html. This is similar to the first one, except that if you call it with a parameter (eg ?H=2) it does an immediate Hide using the given numbered Hide function, but you can still Click on theHide/Unhide buttons to try them.

Using the same functions as before, the results are that:

  • Hide1 [getSource and getFeatures] does NOT work in immediate mode, but does work using the button
  • Hide2 [getSource and forEachFeature] does NOT work in immediate mode, but does work using the button
  • Hide3 [.getSource().once('change') and forEachFeature] does work in immediate mode, but does NOT work using the button (although it will later fire if there is any change to the source, eg by clicking on the Hide1 button)
  • Hide4 [.getSource().on('change') and forEachFeature] does NOT work properly in immediate mode (it gets into a loop), and something similar happens when using the button

So I have created Hide5 which combines the best of these (2 and 3), and this works both in immediate mode, and using the button. You can try it at http://mapping4ops.org/ShowMapsDev/foreachfeature2.html?H=5, and also then clicking on Hide5 and Unhide5.

The code for Hide5 (without the console.log statements) is

function Hide5(hide){ // hide is boolean, TRUE means hide alternate features, FALSE unhide all
    var source = vectorLayer.getSource();
    var numFeatures = source.getFeatures().length;
    if (numFeatures==0) {
        vectorLayer.getSource().once('change', function(evt){
            source = evt.target;
            if (source.getState() === 'ready') {
                doHide5Actions(source,hide);
            }
        });
    } else {
        if (source.getState() === 'ready') {
            doHide5Actions(source,hide);
        }
    }
}

Note this will not work properly if the number of features is really zero.

A problem with this approach is that when I try to do more than one action updating features (eg a Hide and then also an Unhide immediately, using http://mapping4ops.org/ShowMapsDev/foreachfeature2.html?H=5&U=Y. [The &U=Y here gets it to Unhide after the Hide.]) the behaviour is complicated.

Note that it may seem to silly to Hide and Unhide straight away but these are just simple examples of changing the feature properties. In my full application I am doing other things.

What seems to happen is that both actions get set up using .once, and then both get triggered. The first action set up (the Hide) seems to get processed after the second (the Unhide); and an error is triggered in ol-debug (something to do with a listener not being defined).

I shall keep trying to understand and solve this, but if someone can tell me a universal solution I would be grateful. It feels like functionality that many would need, but maybe I am going about it the wrong way?


Following on this research I have now implemented a solution in my real application (not the example I have been using). This involves combining all the processes I want to do to the features into the one routine, with options. Then I can call it just as doHide5Actions is called above. This works fine, both immediately and on user input, without any errors.