Skip to content

Introduce hasFeatureAtPixel #3066

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

Merged
merged 17 commits into from
Jan 22, 2015

Conversation

tsauerwein
Copy link
Member

This PR is based on #3065.
#3065 implements hit-detection for WebGL, but unfortunately the performance is not good enough to do hit-detection on mousemove (if you have many features). This is because we are calling drawElements for every feature, which is expensive.

For mousemove you are usually not interested in the actual features that forEachFeatureAtPixel provides, you just want to know if there is a feature or not, so that you can change the mouse pointer. So in this case you can draw all features at "once" (or to be precise: per texture group) for the hit-detection, and then check if there is a feature or not.

That's why we introduced a hasFeatureAtPixel method, which simply returns true if there is a feature at the given pixel. For WebGL this function is very fast, for 50000 features it takes less than 20ms (on my system).

Example usage:

$(map.getViewport()).on('mousemove', function(evt) {
  var pixel = map.getEventPixel(evt.originalEvent);
  var hit = map.hasFeatureAtPixel(pixel);
  map.getTarget().style.cursor = hit ? 'pointer' : '';
});

Example: icon-sprite-webgl.html

ol.renderer.Map.prototype.hasFeatureAtPixel =
function(coordinate, frameState, layerFilter, thisArg) {
var hasFeature = this.forEachFeatureAtPixel(
coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused why this wouldn't call hasFeatureAtPixel for each layer renderer (with a visible layer).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused why this wouldn't call hasFeatureAtPixel for each layer renderer (with a visible layer).

Am I not doing exactly that by calling forEachFeatureAtPixel?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused why this wouldn't call hasFeatureAtPixel for each layer renderer (with a visible layer).

This one is the default implementation, used by the Canvas and DOM renderers. It is based on the forEachFeatureAtPixel function.

The WebGL implementation does call hasFeatuteAtPixel for each layer renderer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting to see something like this at the map renderer level:

ol.renderer.Map.prototype.hasFeatureAtPixel =
    function(coordinate, frameState, layerFilter, thisArg) {
  var layerStates = this.getMap().getLayerGroup().getLayerStatesArray();
  for (var i = layerStates.length - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
        layerFilter.call(thisArg, layer) &&
        this.getLayerRenderer(layer).hasFeatureAtPixel(coordinate, frameState)) {
      return true;
    }
  }
  return false;
};

And then see the default layer renderer implementation of hasFeatureAtPixel delegate to forEachFeatureAtPixel.

But I see now that this doesn't account for feature overlays and the specific work that the WebGL renderer has to do there. This can happen later, but I still can imagine that we could have one hasFeatureAtPixel implementation at the map renderer level that could call a specific overlayHasFeatureAtPixel method for each map renderer type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see. I think this would make sense if there was a layer renderer which had an optimized implementation for hasFeatureAtPixel. As this is currently not the case, I would prefer to stick with the solution of simply falling back to forEachFeatureAtPixel of the map renderer.

@tschaub
Copy link
Member

tschaub commented Dec 19, 2014

I like the idea of hasFeatureAtPixel. I think it would make sense to have a basic (albeit inefficient) implementation for all renderers (instead of having the Canvas and DOM implementations return false). It could simply use forEachFeatureAtPixel until a more efficient implementation is written.

@tschaub tschaub added this to the v3.2.0 milestone Dec 22, 2014
@tsauerwein
Copy link
Member Author

I like the idea of hasFeatureAtPixel. I think it would make sense to have a basic (albeit inefficient) implementation for all renderers (instead of having the Canvas and DOM implementations return false). It could simply use forEachFeatureAtPixel until a more efficient implementation is written.

In fact, the hasFeatureAtPixel implementation in ol.renderer.Map calls forEachFeatureAtPixel. So there is a default implementation that also works for canvas and DOM.

@tsauerwein
Copy link
Member Author

I think it would make sense instead to update this example to use the new hasFeatureAtPixel method (assuming it works for all renderers).

Good point!

var hasFeature = this.forEachFeatureAtPixel(
coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg);

return goog.isDef(hasFeature) ? hasFeature : false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be goog.isDef(hasFeature) ? true : false; ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better: return goog.isDef(hasFeature) !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously. I was about to edit my comment :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right.

@elemoine
Copy link
Member

elemoine commented Jan 8, 2015

I've just added a pretty minor comment, otherwise looks very good to me.



/**
* @private
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an @enum.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you meant @const. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did! ;-)

@tsauerwein tsauerwein force-pushed the webgl-point-hasFeatureAtPixel branch 2 times, most recently from 6521f05 to abb5fad Compare January 8, 2015 15:31
* @inheritDoc
*/
ol.renderer.webgl.Map.prototype.hasFeatureAtPixel =
function(coordinate, frameState, layerFilter, thisArg2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor, but the last argument could be named thisArg (without the 2).

@tsauerwein tsauerwein force-pushed the webgl-point-hasFeatureAtPixel branch from abb5fad to c85982b Compare January 9, 2015 08:22
@tsauerwein
Copy link
Member Author

Any other comments? cc @tschaub

* @api
*/
ol.Map.prototype.hasFeatureAtPixel =
function(pixel, opt_layerFilter, opt_this2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opt_this2 can be changed to opt_this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I thought I did this already. :) Fixed in #3108

@elemoine
Copy link
Member

I just made a comment that @tschaub already made regarding the name opt_this2. This is minor.

Otherwise it looks very good to me. It's good to merge to me.

tsauerwein pushed a commit that referenced this pull request Jan 22, 2015
@tsauerwein tsauerwein merged commit 1702a4e into openlayers:master Jan 22, 2015
@tsauerwein tsauerwein deleted the webgl-point-hasFeatureAtPixel branch January 22, 2015 09:20
@tsauerwein
Copy link
Member Author

Thanks for reviewing!

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

Successfully merging this pull request may close these issues.

4 participants