diff --git a/examples/getfeatureinfo-image.html b/examples/getfeatureinfo-image.html index 85b3044acd..bde7a8501d 100644 --- a/examples/getfeatureinfo-image.html +++ b/examples/getfeatureinfo-image.html @@ -35,9 +35,11 @@

GetFeatureInfo example (image layer)

This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS image layer.

+

Additionally map.forEachLayerAtPixel is used to change the mouse + pointer when hovering a non-transparent pixel on the map.

See the getfeatureinfo-image.js source to see how this is done.

-
getfeatureinfo
+
getfeatureinfo, forEachLayerAtPixel
diff --git a/examples/getfeatureinfo-image.js b/examples/getfeatureinfo-image.js index 1806cae784..316fda17bc 100644 --- a/examples/getfeatureinfo-image.js +++ b/examples/getfeatureinfo-image.js @@ -7,7 +7,8 @@ goog.require('ol.source.ImageWMS'); var wmsSource = new ol.source.ImageWMS({ url: 'http://demo.boundlessgeo.com/geoserver/wms', params: {'LAYERS': 'ne:ne'}, - serverType: 'geoserver' + serverType: 'geoserver', + crossOrigin: '' }); var wmsLayer = new ol.layer.Image({ @@ -20,6 +21,7 @@ var view = new ol.View({ }); var map = new ol.Map({ + renderer: exampleNS.getRendererFromQueryString(), layers: [wmsLayer], target: 'map', view: view @@ -36,3 +38,14 @@ map.on('singleclick', function(evt) { ''; } }); + +map.on('pointermove', function(evt) { + if (evt.dragging) { + return; + } + var pixel = map.getEventPixel(evt.originalEvent); + var hit = map.forEachLayerAtPixel(pixel, function(layer) { + return true; + }); + map.getTargetElement().style.cursor = hit ? 'pointer' : ''; +}); diff --git a/examples/getfeatureinfo-tile.html b/examples/getfeatureinfo-tile.html index 46a5289c4b..b59b9205ec 100644 --- a/examples/getfeatureinfo-tile.html +++ b/examples/getfeatureinfo-tile.html @@ -35,9 +35,11 @@

WMS GetFeatureInfo example (tile layer)

This example shows how to trigger WMS GetFeatureInfo requests on click for a WMS tile layer.

+

Additionally map.forEachLayerAtPixel is used to change the mouse + pointer when hovering a non-transparent pixel on the map.

See the getfeatureinfo-tile.js source to see how this is done.

-
getfeatureinfo
+
getfeatureinfo, forEachLayerAtPixel
diff --git a/examples/getfeatureinfo-tile.js b/examples/getfeatureinfo-tile.js index 70704a7b8f..022606b611 100644 --- a/examples/getfeatureinfo-tile.js +++ b/examples/getfeatureinfo-tile.js @@ -7,7 +7,8 @@ goog.require('ol.source.TileWMS'); var wmsSource = new ol.source.TileWMS({ url: 'http://demo.boundlessgeo.com/geoserver/wms', params: {'LAYERS': 'ne:ne'}, - serverType: 'geoserver' + serverType: 'geoserver', + crossOrigin: '' }); var wmsLayer = new ol.layer.Tile({ @@ -20,6 +21,7 @@ var view = new ol.View({ }); var map = new ol.Map({ + renderer: exampleNS.getRendererFromQueryString(), layers: [wmsLayer], target: 'map', view: view @@ -36,3 +38,14 @@ map.on('singleclick', function(evt) { ''; } }); + +map.on('pointermove', function(evt) { + if (evt.dragging) { + return; + } + var pixel = map.getEventPixel(evt.originalEvent); + var hit = map.forEachLayerAtPixel(pixel, function(layer) { + return true; + }); + map.getTargetElement().style.cursor = hit ? 'pointer' : ''; +}); diff --git a/src/ol/map.js b/src/ol/map.js index c353275e55..16620afb8b 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -583,12 +583,49 @@ ol.Map.prototype.forEachFeatureAtPixel = var layerFilter = goog.isDef(opt_layerFilter) ? opt_layerFilter : goog.functions.TRUE; var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null; - return this.renderer_.forEachFeatureAtPixel( + return this.renderer_.forEachFeatureAtCoordinate( coordinate, this.frameState_, callback, thisArg, layerFilter, thisArg2); }; +/** + * Detect layers that have a color value at a pixel on the viewport, and + * execute a callback with each matching layer. Layers included in the + * detection can be configured through `opt_layerFilter`. Feature overlays will + * always be included in the detection. + * @param {ol.Pixel} pixel Pixel. + * @param {function(this: S, ol.layer.Layer): T} callback Layer + * callback. If the detected feature is not on a layer, but on a + * {@link ol.FeatureOverlay}, then the argument to this function will + * be `null`. To stop detection, callback functions can return a truthy + * value. + * @param {S=} opt_this Value to use as `this` when executing `callback`. + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer + * filter function, only layers which are visible and for which this + * function returns `true` will be tested for features. By default, all + * visible layers will be tested. Feature overlays will always be tested. + * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result, i.e. the return value of last + * callback execution, or the first truthy callback return value. + * @template S,T,U + * @api stable + */ +ol.Map.prototype.forEachLayerAtPixel = + function(pixel, callback, opt_this, opt_layerFilter, opt_this2) { + if (goog.isNull(this.frameState_)) { + return; + } + var thisArg = goog.isDef(opt_this) ? opt_this : null; + var layerFilter = goog.isDef(opt_layerFilter) ? + opt_layerFilter : goog.functions.TRUE; + var thisArg2 = goog.isDef(opt_this2) ? opt_this2 : null; + return this.renderer_.forEachLayerAtPixel( + pixel, this.frameState_, callback, thisArg, + layerFilter, thisArg2); +}; + + /** * Detect if features intersect a pixel on the viewport. Layers included in the * detection can be configured through `opt_layerFilter`. Feature overlays will @@ -612,7 +649,7 @@ ol.Map.prototype.hasFeatureAtPixel = var layerFilter = goog.isDef(opt_layerFilter) ? opt_layerFilter : goog.functions.TRUE; var thisArg = goog.isDef(opt_this) ? opt_this : null; - return this.renderer_.hasFeatureAtPixel( + return this.renderer_.hasFeatureAtCoordinate( coordinate, this.frameState_, layerFilter, thisArg); }; diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 8b8eed5689..e8f70c7c1d 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1858,16 +1858,16 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() { /** + * @param {ol.Coordinate} coordinate Coordinate. * @param {number} resolution Resolution. * @param {number} rotation Rotation. - * @param {ol.Coordinate} coordinate Coordinate. * @param {Object} skippedFeaturesHash Ids of features to skip * @param {function(ol.Feature): T} callback Feature callback. * @return {T|undefined} Callback result. * @template T */ -ol.render.canvas.ReplayGroup.prototype.forEachGeometryAtPixel = function( - resolution, rotation, coordinate, skippedFeaturesHash, callback) { +ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, skippedFeaturesHash, callback) { var transform = this.hitDetectionTransform_; ol.vec.Mat4.makeTransform2D(transform, 0.5, 0.5, diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 9af1caa62d..5432e0aab6 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -1154,6 +1154,7 @@ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, /** + * @param {ol.Coordinate} coordinate Coordinate. * @param {ol.webgl.Context} context Context. * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. @@ -1166,15 +1167,14 @@ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, * @param {number} hue Global hue. * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {ol.Coordinate} coordinate Coordinate. * @param {function(ol.Feature): T|undefined} callback Feature callback. * @return {T|undefined} Callback result. * @template T */ -ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( - context, center, resolution, rotation, size, pixelRatio, +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - coordinate, callback) { + callback) { var gl = context.getGL(); gl.bindFramebuffer( gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); @@ -1215,6 +1215,7 @@ ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( /** + * @param {ol.Coordinate} coordinate Coordinate. * @param {ol.webgl.Context} context Context. * @param {ol.Coordinate} center Center. * @param {number} resolution Resolution. @@ -1227,13 +1228,11 @@ ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( * @param {number} hue Global hue. * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {ol.Coordinate} coordinate Coordinate. - * @return {boolean} Is there a feature at the given pixel? + * @return {boolean} Is there a feature at the given coordinate? */ -ol.render.webgl.ReplayGroup.prototype.hasFeatureAtPixel = function( - context, center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - coordinate) { +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { var gl = context.getGL(); gl.bindFramebuffer( gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js index 834f0e771f..8edd16d709 100644 --- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -4,11 +4,13 @@ goog.require('goog.asserts'); goog.require('goog.vec.Mat4'); goog.require('ol.ImageBase'); goog.require('ol.ViewHint'); +goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.layer.Image'); goog.require('ol.proj'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.source.ImageVector'); goog.require('ol.vec.Mat4'); @@ -35,6 +37,18 @@ ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) { */ this.imageTransform_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.imageTransformInv_ = null; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + }; goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); @@ -42,15 +56,15 @@ goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); /** * @inheritDoc */ -ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel = +ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { var layer = this.getLayer(); var source = layer.getSource(); var resolution = frameState.viewState.resolution; var rotation = frameState.viewState.rotation; var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. @@ -61,6 +75,55 @@ ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.getImage())) { + return undefined; + } + + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } else { + // for all other image sources directly check the image + if (goog.isNull(this.imageTransformInv_)) { + this.imageTransformInv_ = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_); + } + + var pixelOnCanvas = + this.getPixelOnCanvas(pixel, this.imageTransformInv_); + + if (goog.isNull(this.hitCanvasContext_)) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage( + this.getImage(), pixelOnCanvas[0], pixelOnCanvas[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } +}; + + /** * @inheritDoc */ @@ -135,6 +198,7 @@ ol.renderer.canvas.ImageLayer.prototype.prepareFrame = viewRotation, imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + this.imageTransformInv_ = null; this.updateAttributions(frameState.attributions, image.getAttributions()); this.updateLogos(frameState, imageSource); } diff --git a/src/ol/renderer/canvas/canvaslayerrenderer.js b/src/ol/renderer/canvas/canvaslayerrenderer.js index a9a699d8b2..7e8dc1cefc 100644 --- a/src/ol/renderer/canvas/canvaslayerrenderer.js +++ b/src/ol/renderer/canvas/canvaslayerrenderer.js @@ -216,6 +216,21 @@ ol.renderer.canvas.Layer.prototype.getTransform = function(frameState) { ol.renderer.canvas.Layer.prototype.prepareFrame = goog.abstractMethod; +/** + * @param {ol.Pixel} pixelOnMap Pixel. + * @param {goog.vec.Mat4.Number} imageTransformInv The transformation matrix + * to convert from a map pixel to a canvas pixel. + * @return {ol.Pixel} + * @protected + */ +ol.renderer.canvas.Layer.prototype.getPixelOnCanvas = + function(pixelOnMap, imageTransformInv) { + var pixelOnCanvas = [0, 0]; + ol.vec.Mat4.multVec2(imageTransformInv, pixelOnMap, pixelOnCanvas); + return pixelOnCanvas; +}; + + /** * @param {ol.Size} size Size. * @return {boolean} True when the canvas with the current size does not exceed diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index b4549b1ad8..f583b6aa48 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -60,6 +60,12 @@ ol.renderer.canvas.TileLayer = function(mapRenderer, tileLayer) { */ this.imageTransform_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.imageTransformInv_ = null; + /** * @private * @type {number} @@ -408,6 +414,35 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = viewState.rotation, (origin[0] - center[0]) / tilePixelResolution, (center[1] - origin[1]) / tilePixelResolution); + this.imageTransformInv_ = null; return true; }; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.context_)) { + return undefined; + } + + if (goog.isNull(this.imageTransformInv_)) { + this.imageTransformInv_ = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.imageTransform_, this.imageTransformInv_); + } + + var pixelOnCanvas = + this.getPixelOnCanvas(pixel, this.imageTransformInv_); + + var imageData = this.context_.getImageData( + pixelOnCanvas[0], pixelOnCanvas[1], 1, 1).data; + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } +}; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 1588568263..219288c185 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -116,7 +116,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = /** * @inheritDoc */ -ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel = +ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { if (goog.isNull(this.replayGroup_)) { return undefined; @@ -126,8 +126,8 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtPixel = var layer = this.getLayer(); /** @type {Object.} */ var features = {}; - return this.replayGroup_.forEachGeometryAtPixel(resolution, - rotation, coordinate, frameState.skippedFeatureUids, + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + resolution, rotation, frameState.skippedFeatureUids, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js index 624fa8f0c2..cce9db4785 100644 --- a/src/ol/renderer/dom/domimagelayerrenderer.js +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -47,15 +47,15 @@ goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer); /** * @inheritDoc */ -ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtPixel = +ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { var layer = this.getLayer(); var source = layer.getSource(); var resolution = frameState.viewState.resolution; var rotation = frameState.viewState.rotation; var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index 90fa5127b2..bcb5ec63f8 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -176,7 +176,7 @@ ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = /** * @inheritDoc */ -ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel = +ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { if (goog.isNull(this.replayGroup_)) { return undefined; @@ -186,8 +186,8 @@ ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtPixel = var layer = this.getLayer(); /** @type {Object.} */ var features = {}; - return this.replayGroup_.forEachGeometryAtPixel(resolution, - rotation, coordinate, frameState.skippedFeatureUids, + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + resolution, rotation, frameState.skippedFeatureUids, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 77b76f17f9..1519d679d1 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -53,15 +53,37 @@ goog.inherits(ol.renderer.Layer, goog.Disposable); * @return {T|undefined} Callback result. * @template S,T */ -ol.renderer.Layer.prototype.forEachFeatureAtPixel = goog.nullFunction; +ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = goog.nullFunction; + + +/** + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState Frame state. + * @param {function(this: S, ol.layer.Layer): T} callback Layer callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T + */ +ol.renderer.Layer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.layer_); + } else { + return undefined; + } +}; /** * @param {ol.Coordinate} coordinate Coordinate. * @param {olx.FrameState} frameState Frame state. - * @return {boolean} Is there a feature at the given pixel? + * @return {boolean} Is there a feature at the given coordinate? */ -ol.renderer.Layer.prototype.hasFeatureAtPixel = goog.functions.FALSE; +ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE; /** diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index fb8adbcb42..bc6ba4666c 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -121,7 +121,7 @@ ol.renderer.Map.expireIconCache_ = function(map, frameState) { * @return {T|undefined} Callback result. * @template S,T,U */ -ol.renderer.Map.prototype.forEachFeatureAtPixel = +ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg, layerFilter, thisArg2) { var result; @@ -131,8 +131,8 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel = if (!goog.isNull(this.replayGroup)) { /** @type {Object.} */ var features = {}; - result = this.replayGroup.forEachGeometryAtPixel(viewResolution, - viewRotation, coordinate, {}, + result = this.replayGroup.forEachFeatureAtCoordinate(coordinate, + viewResolution, viewRotation, {}, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. @@ -158,7 +158,7 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel = if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && layerFilter.call(thisArg2, layer)) { var layerRenderer = this.getLayerRenderer(layer); - result = layerRenderer.forEachFeatureAtPixel( + result = layerRenderer.forEachFeatureAtCoordinate( coordinate, frameState, callback, thisArg); if (result) { return result; @@ -169,6 +169,60 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel = }; +/** + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter + * function, only layers which are visible and for which this function + * returns `true` will be tested for features. By default, all visible + * layers will be tested. + * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.Map.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + var viewState = frameState.viewState; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + + if (!goog.isNull(this.replayGroup)) { + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + var hasFeature = this.replayGroup.forEachFeatureAtCoordinate(coordinate, + viewResolution, viewRotation, {}, goog.functions.TRUE); + + if (hasFeature) { + result = callback.call(thisArg, null); + if (result) { + return result; + } + } + } + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + /** * @param {ol.Coordinate} coordinate Coordinate. * @param {olx.FrameState} frameState FrameState. @@ -177,12 +231,12 @@ ol.renderer.Map.prototype.forEachFeatureAtPixel = * returns `true` will be tested for features. By default, all visible * layers will be tested. * @param {U} thisArg Value to use as `this` when executing `layerFilter`. - * @return {boolean} Is there a feature at the given pixel? + * @return {boolean} Is there a feature at the given coordinate? * @template U */ -ol.renderer.Map.prototype.hasFeatureAtPixel = +ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) { - var hasFeature = this.forEachFeatureAtPixel( + var hasFeature = this.forEachFeatureAtCoordinate( coordinate, frameState, goog.functions.TRUE, this, layerFilter, thisArg); return goog.isDef(hasFeature); diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 13d3c7af16..af74c1e244 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -7,10 +7,13 @@ goog.require('ol.Coordinate'); goog.require('ol.Extent'); goog.require('ol.ImageBase'); goog.require('ol.ViewHint'); +goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.layer.Image'); goog.require('ol.proj'); goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.source.ImageVector'); +goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Context'); @@ -32,6 +35,18 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { */ this.image_ = null; + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; + + /** + * @private + * @type {?goog.vec.Mat4.Number} + */ + this.hitTransformationMatrix_ = null; + }; goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); @@ -58,15 +73,15 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { /** * @inheritDoc */ -ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtPixel = +ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { var layer = this.getLayer(); var source = layer.getSource(); var resolution = frameState.viewState.resolution; var rotation = frameState.viewState.rotation; var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtPixel( - resolution, rotation, coordinate, skippedFeatureUids, + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, skippedFeatureUids, /** * @param {ol.Feature} feature Feature. @@ -143,6 +158,7 @@ ol.renderer.webgl.ImageLayer.prototype.prepareFrame = this.updateProjectionMatrix_(canvas.width, canvas.height, viewCenter, viewResolution, viewRotation, image.getExtent()); + this.hitTransformationMatrix_ = null; // Translate and scale to flip the Y coord. var texCoordMatrix = this.texCoordMatrix; @@ -193,3 +209,114 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = goog.vec.Mat4.translate(projectionMatrix, 1, 1, 0); }; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = + function(coordinate, frameState) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + return goog.isDef(hasFeature); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.image_) || goog.isNull(this.image_.getImage())) { + return undefined; + } + + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, goog.functions.TRUE, this); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; + + if (goog.isNull(this.hitTransformationMatrix_)) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); + } + + var pixelOnFrameBuffer = [0, 0]; + ol.vec.Mat4.multVec2( + this.hitTransformationMatrix_, pixel, pixelOnFrameBuffer); + + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check + return undefined; + } + + if (goog.isNull(this.hitCanvasContext_)) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); + } + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } + } +}; + + +/** + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize + * @param {ol.Size} imageSize + * @return {goog.vec.Mat4.Number} + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = + function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(mapCoordMatrix); + goog.vec.Mat4.translate(mapCoordMatrix, -1, -1, 0); + goog.vec.Mat4.scale(mapCoordMatrix, 2 / mapSize[0], 2 / mapSize[1], 1); + goog.vec.Mat4.translate(mapCoordMatrix, 0, mapSize[1], 0); + goog.vec.Mat4.scale(mapCoordMatrix, 1, -1, 1); + + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.invert(this.projectionMatrix, projectionMatrixInv); + + // the third matrix scales to the image dimensions and flips the y-axis again + var imageCoordMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(imageCoordMatrix); + goog.vec.Mat4.translate(imageCoordMatrix, 0, imageSize[1], 0); + goog.vec.Mat4.scale(imageCoordMatrix, 1, -1, 1); + goog.vec.Mat4.scale(imageCoordMatrix, imageSize[0] / 2, imageSize[1] / 2, 1); + goog.vec.Mat4.translate(imageCoordMatrix, 1, 1, 0); + + var transformMatrix = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.multMat( + imageCoordMatrix, projectionMatrixInv, transformMatrix); + goog.vec.Mat4.multMat( + transformMatrix, mapCoordMatrix, transformMatrix); + + return transformMatrix; +}; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index c5985c194b..7fd95f3580 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -540,7 +540,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { /** * @inheritDoc */ -ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = +ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg, layerFilter, thisArg2) { var result; @@ -560,11 +560,10 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = // use default color values var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - result = this.replayGroup.forEachFeatureAtPixel(context, - viewState.center, viewState.resolution, viewState.rotation, + result = this.replayGroup.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, - coordinate, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. @@ -590,7 +589,7 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg2, layer)) { var layerRenderer = this.getLayerRenderer(layer); - result = layerRenderer.forEachFeatureAtPixel( + result = layerRenderer.forEachFeatureAtCoordinate( coordinate, frameState, callback, thisArg); if (result) { return result; @@ -604,7 +603,7 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = /** * @inheritDoc */ -ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = +ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) { var hasFeature = false; @@ -620,11 +619,10 @@ ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = // use default color values var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - hasFeature = this.replayGroup.hasFeatureAtPixel(context, - viewState.center, viewState.resolution, viewState.rotation, + hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, - d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, - coordinate); + d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}); if (hasFeature) { return true; } @@ -638,7 +636,8 @@ ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg, layer)) { var layerRenderer = this.getLayerRenderer(layer); - hasFeature = layerRenderer.hasFeatureAtPixel(coordinate, frameState); + hasFeature = + layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); if (hasFeature) { return true; } @@ -648,6 +647,57 @@ ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + + var context = this.getContext(); + var viewState = frameState.viewState; + var result; + + // do the hit-detection for the overlays first + if (!goog.isNull(this.replayGroup)) { + // use default color values + var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + + var hasFeature = this.replayGroup.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}); + if (hasFeature) { + result = callback.call(thisArg, null); + if (result) { + return result; + } + } + } + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + /** * @private * @const diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 68ceed8d0d..9e79e8ce19 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -17,6 +17,7 @@ goog.require('ol.math'); goog.require('ol.renderer.webgl.Layer'); goog.require('ol.renderer.webgl.tilelayer.shader'); goog.require('ol.tilecoord'); +goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Buffer'); @@ -326,3 +327,38 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame = return true; }; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + if (goog.isNull(this.framebuffer)) { + return undefined; + } + var mapSize = this.getMap().getSize(); + + var pixelOnMapScaled = [ + pixel[0] / mapSize[0], + (mapSize[1] - pixel[1]) / mapSize[1]]; + + var pixelOnFrameBufferScaled = [0, 0]; + ol.vec.Mat4.multVec2( + this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + + var gl = this.getWebGLMapRenderer().getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; + } +}; diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index f2a00be9e0..7a3de9e676 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -106,7 +106,7 @@ ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { /** * @inheritDoc */ -ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = +ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) { if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { return undefined; @@ -118,12 +118,11 @@ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = var layerState = this.layerState_; /** @type {Object.} */ var features = {}; - return this.replayGroup_.forEachFeatureAtPixel(context, - viewState.center, viewState.resolution, viewState.rotation, + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, layerState.opacity, layerState.brightness, layerState.contrast, layerState.hue, layerState.saturation, frameState.skippedFeatureUids, - coordinate, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. @@ -143,7 +142,7 @@ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = /** * @inheritDoc */ -ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtPixel = +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { return false; @@ -152,12 +151,27 @@ ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtPixel = var context = mapRenderer.getContext(); var viewState = frameState.viewState; var layerState = this.layerState_; - return this.replayGroup_.hasFeatureAtPixel(context, - viewState.center, viewState.resolution, viewState.rotation, + return this.replayGroup_.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, layerState.opacity, layerState.brightness, layerState.contrast, - layerState.hue, layerState.saturation, frameState.skippedFeatureUids, - coordinate); + layerState.hue, layerState.saturation, frameState.skippedFeatureUids); + } +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = + function(pixel, frameState, callback, thisArg) { + var coordinate = this.getMap().getCoordinateFromPixel(pixel); + var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); + + if (hasFeature) { + return callback.call(thisArg, this.getLayer()); + } else { + return undefined; } }; diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js index ee40500e33..521ad8e8ae 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -152,15 +152,15 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = /** * @inheritDoc */ -ol.source.ImageVector.prototype.forEachFeatureAtPixel = function( - resolution, rotation, coordinate, skippedFeatureUids, callback) { +ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function( + coordinate, resolution, rotation, skippedFeatureUids, callback) { if (goog.isNull(this.replayGroup_)) { return undefined; } else { /** @type {Object.} */ var features = {}; - return this.replayGroup_.forEachGeometryAtPixel( - resolution, 0, coordinate, skippedFeatureUids, + return this.replayGroup_.forEachFeatureAtCoordinate( + coordinate, resolution, 0, skippedFeatureUids, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. diff --git a/src/ol/source/source.js b/src/ol/source/source.js index ea2c84c9c9..a54d304caa 100644 --- a/src/ol/source/source.js +++ b/src/ol/source/source.js @@ -77,15 +77,15 @@ goog.inherits(ol.source.Source, ol.Observable); /** + * @param {ol.Coordinate} coordinate Coordinate. * @param {number} resolution Resolution. * @param {number} rotation Rotation. - * @param {ol.Coordinate} coordinate Coordinate. * @param {Object.} skippedFeatureUids Skipped feature uids. * @param {function(ol.Feature): T} callback Feature callback. * @return {T|undefined} Callback result. * @template T */ -ol.source.Source.prototype.forEachFeatureAtPixel = +ol.source.Source.prototype.forEachFeatureAtCoordinate = goog.nullFunction; diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index de1783e130..ccfe2cbaa0 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -295,7 +295,7 @@ ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) { * @return {S|undefined} The return value from the last call to the callback. * @template T,S */ -ol.source.Vector.prototype.forEachFeatureAtCoordinate = +ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = function(coordinate, callback, opt_this) { var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]]; return this.forEachFeatureInExtent(extent, function(feature) { @@ -409,7 +409,7 @@ ol.source.Vector.prototype.getFeatures = function() { */ ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) { var features = []; - this.forEachFeatureAtCoordinate(coordinate, function(feature) { + this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) { features.push(feature); }); return features; diff --git a/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js b/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js index 65971bb0a6..2457dae729 100644 --- a/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js +++ b/test/spec/ol/renderer/canvas/canvasvectorlayerrenderer.test.js @@ -58,7 +58,7 @@ describe('ol.renderer.canvas.VectorLayer', function() { }); - describe('#forEachFeatureAtPixel', function() { + describe('#forEachFeatureAtCoordinate', function() { var renderer; beforeEach(function() { @@ -70,8 +70,8 @@ describe('ol.renderer.canvas.VectorLayer', function() { map.getRenderer(), layer); var replayGroup = {}; renderer.replayGroup_ = replayGroup; - replayGroup.forEachGeometryAtPixel = function(resolution, - rotation, coordinate, skippedFeaturesUids, callback) { + replayGroup.forEachFeatureAtCoordinate = function(coordinate, + resolution, rotation, skippedFeaturesUids, callback) { var geometry = new ol.geom.Point([0, 0]); var feature = new ol.Feature(); callback(geometry, feature); @@ -89,7 +89,7 @@ describe('ol.renderer.canvas.VectorLayer', function() { rotation: 0 } }; - renderer.forEachFeatureAtPixel( + renderer.forEachFeatureAtCoordinate( coordinate, frameState, spy, undefined); expect(spy.callCount).to.be(1); });