diff --git a/externs/olx.js b/externs/olx.js index a255777607..fede544830 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -301,6 +301,49 @@ olx.MapOptions.prototype.target; */ olx.MapOptions.prototype.view; +/** + * The filter function will receive one argument, the + * {@link ol.layer.Layer layer-candidate} and it should return a boolean + * value. + * @typedef {(function(ol.layer.Layer): boolean)} + */ +olx.LayerFilterFunction; + +/** + * Object literal with options for the forEachFeatureAtCoordinate methods. + * @typedef {{layerFilter: (olx.LayerFilterFunction|undefined), + * layerFilterThis: (Object|undefined), + * hitTolerance: (number|undefined)}} + */ +olx.ForEachFeatureOptions; + + +/** + * 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. + * @type {olx.LayerFilterFunction|undefined} + * @api stable + */ +olx.ForEachFeatureOptions.prototype.layerFilter; + + +/** + * Value to use as `this` when executing `layerFilter`. + * @type {Object} + * @api stable + */ +olx.ForEachFeatureOptions.prototype.layerFilterThis; + + +/** + * Value of a radius in whichs area around the given coordinate features are + * called. + * @type {number} + * @api stable + */ +olx.ForEachFeatureOptions.prototype.hitTolerance; + /** * Object literal with config options for the overlay. diff --git a/src/ol/interaction/select.js b/src/ol/interaction/select.js index 2820df269f..d6b233cab6 100644 --- a/src/ol/interaction/select.js +++ b/src/ol/interaction/select.js @@ -200,9 +200,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { // pixel, or clear the selected feature(s) if there is no feature at // the pixel. ol.obj.clear(this.featureLayerAssociation_); - map.forEachFeatureAtPixel(mapBrowserEvent.pixel, { - layerFilter: this.layerFilter_ - }, + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, /** * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.layer.Layer} layer Layer. @@ -214,7 +212,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { this.addFeatureLayerAssociation_(feature, layer); return !this.multi_; } - }, this); + }, this, {layerFilter: this.layerFilter_}); var i; for (i = features.getLength() - 1; i >= 0; --i) { var feature = features.item(i); @@ -232,9 +230,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { } } else { // Modify the currently selected feature(s). - map.forEachFeatureAtPixel(mapBrowserEvent.pixel, { - layerFilter: this.layerFilter_ - }, + map.forEachFeatureAtPixel(mapBrowserEvent.pixel, /** * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.layer.Layer} layer Layer. @@ -253,7 +249,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { } return !this.multi_; } - }, this); + }, this, {layerFilter: this.layerFilter_}); var j; for (j = deselected.length - 1; j >= 0; --j) { features.remove(deselected[j]); diff --git a/src/ol/map.js b/src/ol/map.js index 8cea0b20e3..36d24f0fc6 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -554,26 +554,11 @@ ol.Map.prototype.disposeInternal = function() { }; -/** - * @typedef {object} ol.MapForEachFeatureOptions - * @property {(function(this: U, ol.layer.Layer): boolean)=} layerFilter Layer - * filter function. The filter function will receive one argument, the - * {@link ol.layer.Layer layer-candidate} and it should return a boolean - * value. 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. - * @property {U=} layerFilterThis Value to use as `this` when executing `layerFilter`. - * @property {number=} hitTolerance the hitTolerance in pixels in which features - * get hit. - */ - - /** * Detect features that intersect a pixel on the viewport, and execute a * callback with each intersecting feature. Layers included in the detection can * be configured through `opt_layerFilter`. * @param {ol.Pixel} pixel Pixel. - * @param {ol.MapForEachFeatureOptions=} opt_options * @param {function(this: S, (ol.Feature|ol.render.Feature), * ol.layer.Layer): T} callback Feature callback. The callback will be * called with two arguments. The first argument is one @@ -582,27 +567,25 @@ ol.Map.prototype.disposeInternal = function() { * the {@link ol.layer.Layer layer} of the feature and will be null for * unmanaged layers. To stop detection, callback functions can return a * truthy value. - * @param {S=} opt_this + * @param {S=} opt_this Value to use as this when executing callback. + * @param {olx.ForEachFeatureOptions=} opt_options Optional options. * @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 + * @template S,T * @api stable */ -ol.Map.prototype.forEachFeatureAtPixel = function(pixel, opt_options, callback, opt_this) { - if (typeof opt_options !== 'object') { - opt_this = callback; - callback = opt_options; - opt_options = {}; - } +ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_this, opt_options) { + opt_options = opt_options !== undefined ? opt_options : {}; if (!this.frameState_) { return; } var coordinate = this.getCoordinateFromPixel(pixel); + var hitTolerance = opt_options.hitTolerance || 0; var thisArg = opt_this !== undefined ? opt_this : null; var layerFilter = opt_options.layerFilter || ol.functions.TRUE; var thisArg2 = opt_options.layerFilterThis || null; return this.renderer_.forEachFeatureAtCoordinate( - coordinate, this.frameState_, opt_options.hitTolerance || 0, callback, thisArg, + coordinate, this.frameState_, hitTolerance, callback, thisArg, layerFilter, thisArg2); }; diff --git a/src/ol/render/canvas/replaygroup.js b/src/ol/render/canvas/replaygroup.js index 089f06df29..9250a55887 100644 --- a/src/ol/render/canvas/replaygroup.js +++ b/src/ol/render/canvas/replaygroup.js @@ -69,29 +69,32 @@ ol.render.canvas.ReplayGroup = function( ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup); /** - * This methods creates a circle inside an exactly fitting array. Points inside the circle are marked by true, points - * on the outside are marked with false. - * This method uses the midpoint circle algorithm. - * @param {Number} radius - * @returns {Boolean[][]} + * This methods creates a circle inside a fitting array. Points inside the + * circle are marked by true, points on the outside are marked with false. + * It uses the midpoint circle algorithm. + * @param {number} radius Radius. + * @returns {Array.>} an array with marked circel points. * @private */ ol.render.canvas.ReplayGroup.createPixelCircle_ = function(radius) { var arraySize = radius * 2 + 1; var arr = new Array(arraySize); for (var i = 0; i < arraySize; i++) { - arr[i] = (new Array(arraySize)).fill(false); + arr[i] = new Array(arraySize); + for (var j = 0; j < arraySize; j++) { + arr[i][j] = false; + } } function fillRowToMiddle(x, y) { var i; if (x >= radius) { for (i = radius; i < x; i++) { - arr[i][y] = true + arr[i][y] = true; } } else if (x < radius) { for (i = x + 1; i < radius; i++) { - arr[i][y] = true + arr[i][y] = true; } } } @@ -112,8 +115,7 @@ ol.render.canvas.ReplayGroup.createPixelCircle_ = function(radius) { y++; error += 1 + 2 * y; - if (2 * (error - x) + 1 > 0) - { + if (2 * (error - x) + 1 > 0) { x -= 1; error += 1 - 2 * x; } @@ -175,7 +177,12 @@ ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent); } - var mask = ol.render.canvas.ReplayGroup.createPixelCircle_(hitTolerance); + var mask; + if (hitTolerance === 0) { + mask = [[true]]; + } else { + mask = ol.render.canvas.ReplayGroup.createPixelCircle_(hitTolerance); + } return this.replayHitDetection_(context, transform, rotation, skippedFeaturesHash, diff --git a/src/ol/renderer/map.js b/src/ol/renderer/map.js index da80986fd2..401b9b500b 100644 --- a/src/ol/renderer/map.js +++ b/src/ol/renderer/map.js @@ -199,7 +199,7 @@ ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, call */ ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) { var hasFeature = this.forEachFeatureAtCoordinate( - coordinate, frameState, ol.functions.TRUE, this, layerFilter, thisArg); + coordinate, frameState, 0, ol.functions.TRUE, this, layerFilter, thisArg); return hasFeature !== undefined; }; diff --git a/src/ol/renderer/webgl/imagelayer.js b/src/ol/renderer/webgl/imagelayer.js index 9ef477e9a8..2218f42ede 100644 --- a/src/ol/renderer/webgl/imagelayer.js +++ b/src/ol/renderer/webgl/imagelayer.js @@ -213,7 +213,7 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvas */ ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { var hasFeature = this.forEachFeatureAtCoordinate( - coordinate, frameState, ol.functions.TRUE, this); + coordinate, frameState, 0, ol.functions.TRUE, this); return hasFeature !== undefined; }; @@ -232,7 +232,7 @@ ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, fra var coordinate = ol.transform.apply( frameState.pixelToCoordinateTransform, pixel.slice()); var hasFeature = this.forEachFeatureAtCoordinate( - coordinate, frameState, ol.functions.TRUE, this); + coordinate, frameState, 0, ol.functions.TRUE, this); if (hasFeature) { return callback.call(thisArg, this.getLayer(), null); diff --git a/test/spec/ol/renderer/canvas/map.test.js b/test/spec/ol/renderer/canvas/map.test.js index a9ae31d819..9d82ac5435 100644 --- a/test/spec/ol/renderer/canvas/map.test.js +++ b/test/spec/ol/renderer/canvas/map.test.js @@ -111,11 +111,11 @@ describe('ol.renderer.canvas.Map', function() { map.addLayer(layer); map.renderSync(); var cb = sinon.spy(); - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), { - layerFilter: function() { - return false; - } - }, cb); + map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb, null, { + layerFilter: function() { + return false; + } + }); expect(cb).to.not.be.called(); }); @@ -151,13 +151,13 @@ describe('ol.renderer.canvas.Map', function() { ]; for (var i = 0; i < 4; i++) { - map.forEachFeatureAtPixel(pixelsInside[i], {hitTolerance:10}, cb1); + map.forEachFeatureAtPixel(pixelsInside[i], cb1, null, {hitTolerance:10}); } expect(cb1.callCount).to.be(4); expect(cb1.firstCall.args[1]).to.be(layer); for (var j = 0; j < 4; j++) { - map.forEachFeatureAtPixel(pixelsOutside[j], {hitTolerance:10}, cb2); + map.forEachFeatureAtPixel(pixelsOutside[j], cb2, null, {hitTolerance:10}); } expect(cb2).not.to.be.called(); });