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

queryRenderedFeatures not querying current data when styles are added / removed. #4222

Closed
dougajmcdonald opened this issue Feb 7, 2017 · 10 comments

Comments

@dougajmcdonald
Copy link

dougajmcdonald commented Feb 7, 2017

I have a map which has a filter applied to a layer. We have a function refreshFilter(filterValue) which clears and then re-adds the filter with the new value.
e.g.

refreshFilter(filterValue) {
  removeLayer('ourLayer');
  addLayer({
      'id': 'ourLayer',
      'type': 'line',
      'filter': ['all', ['==', 'sex', filterValue]]
    }, 'marker');
}

This works as expected, and the features I see rendered from the GeoJson match the sex provided.

My problem comes when I want to return from the map, the features which match my filter.

I updated the refreshFilter function to return the features which were visible using queryRenderedFeatures but this always returns the opposite of what I'm expecting. So if pass in male I visually see the features which match sex=='male' (expected) but the queryRenderedFeatures function returns features which match sex=='female' (unexpected).

Simplified version

refreshFilter(filterValue) {
  removeLayer('ourLayer');
  addLayer({
      'id': 'ourLayer',
      'type': 'line',
      'filter': ['all', ['==', 'sex', filterValue]]
    }, 'marker');
  return queryRenderedFeatures({layers: ['ourLayer']});
}

It seems to me that the call to queryRenderedFeatures hasn't waited for the layers to be changed and has queried the previous set of features to work out what to return.

Am I doing something wrong here? Any pointers greatly appreciated.

@dougajmcdonald
Copy link
Author

As an update I hacked a setTimeout round the queryRenderedFeatures call and this works. This would suggest that the update through addLayer is async. Having looked over the API I can't see a callback or anything to use to ensure the new filter has been applied.

Is these a recommended approach to handle this kind of situation?

@jfirebaugh
Copy link
Contributor

Can you please create a minimal self-contained example that demonstrates the issue?

@dougajmcdonald
Copy link
Author

dougajmcdonald commented Feb 9, 2017

@jfirebaugh Ok, fixed it, here's the JSBin with the access token removed:

https://jsbin.com/binawol/edit?html,js,console,output

If you click the button it will output the values I pass into the filter property of addLayer and then after that, the properties.DEV_STATUS value from the features which queryRenderedFeatures returns.
Click it a few times to get the idea, you will notice that the features returned from queryRenderedFeatures are always one step the filters passed to addLayer.

You can wrap the console log part of the click handler in a setTimeout like this:

    setTimeout(function() {
      var features = map.queryRenderedFeatures({ layers: ['sites-outline'] }).map(function(feat) {
        return feat.properties && feat.properties.DEV_STATUS;
      });
        
      console.log(features);
    }, 500);

This then returns the expected results from the queryRenderedFeatures function.

Hope that makes sense, shout for more info,

@anandthakker
Copy link
Contributor

anandthakker commented Feb 13, 2017

@dougajmcdonald thank you for providing the example. The reason the results of your queryRenderedFeatures lags 'behind' the most recent layer settings is that adding a layer (or using map.setFilter to modify a layer's filter) is an asynchronous operation. We are discussing ways to improve the API of async methods like this, but in the meantime, you can check map.loaded() to determine whether the operation is complete. A common pattern for doing this without resorting to setTimeout is:

map.addLayer(...) // make your change
map.on('render', afterChangeComplete); // warning: this fires many times per second!

function afterChangeComplete () {
  if (!map.loaded()) { return } // still not loaded; bail out.

  // now that the map is loaded, it's safe to query the features:
  map.queryRenderedFeatures(...);

  map.off('render', afterChangeComplete); // remove this handler now that we're done.
}

update: As @dougajmcdonald notes below, be careful to use a named function (afterChangeComplete above), not .bind() or an anonymous function (function () {} or () => {}), for the render event handler you attach. Otherwise, the map.off(...) line will fail to remove the handler, as it won't refer to the same instance that was attached.

@anandthakker
Copy link
Contributor

@dougajmcdonald I'm closing this issue, but if the above suggestion does not help, please feel free to contact Mapbox Support or reopen it!

@dougajmcdonald
Copy link
Author

Thanks for the info, this is what I suspected.

As a work around for my use case could I just query the source with the map bounds passed in? Or would I see the same kind of issue?

@dougajmcdonald
Copy link
Author

dougajmcdonald commented Feb 20, 2017

@anandthakker I just wanted to add one more note to this issue.

I got caught out by using either es6 fat arrow functions OR es5 bind calls when setting up a handler for the render event from inside an es6 class

If you use the following:

map.on('render', () => this.afterChangeComplete);
map.off('render', () => this.afterChangeComplete);

OR

map.on('render', this.afterChangeComplete.bind(this));
map.off('render', this.afterChangeComplete.bind(this));

Then the handler won't be removed as JS will create an anonomous function under the hood and the off call won't remove it.

@cartogreaves
Copy link

@anandthakker I've been working with queryRenderedFeature to iterate through an array of previously undefined intersect point coordinates upon a current layer on the map. The returned feature array is intermittent and often incomplete and varies with zoom level. Am i experiencing the same issue as above. Heres an example bl.ock of whats going on. Sorry if this is a dead issue but I'm not sure where to turn. Thanks.

@dlmu-lq
Copy link

dlmu-lq commented Sep 5, 2019

Thanks to you all, Hope this helps:

                    // debounce from loadsh/debounce
                    let debouncedOnRender = debounce(()=>{
                        console.log("debounced render");
                        func();
                        map.off('render', debouncedOnRender);
                    },100);
                   map.on('render', debouncedOnRender);

dnsos added a commit to technologiestiftung/baumblick-frontend that referenced this issue Sep 8, 2022
@tmlmt
Copy link

tmlmt commented Dec 25, 2022

Hey is this hack still valid? I have the same situation where I need to update a list after a filter has been set. Checking with some logs, I can see that afterChangeComplete gets fired 8 times before queryRenderedFeatures(...) is called, but the function return the wrong list. Only a setTimeout sufficiently long enables queryRenderedFeatures to return the right list.

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

6 participants