From 54cbe14f88802fe0fa437c23fcfbc36caf2a8d5c Mon Sep 17 00:00:00 2001 From: Simon Seyock Date: Wed, 25 Sep 2019 18:02:04 +0200 Subject: [PATCH] Check hits for wrapped around geometries --- src/ol/PluggableMap.js | 8 +++-- src/ol/renderer/Layer.js | 1 - src/ol/renderer/Map.js | 43 ++++++++++++++++----------- src/ol/renderer/canvas/VectorLayer.js | 2 ++ 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index 98afcc77f2..415cf31317 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -75,6 +75,8 @@ import {toUserCoordinate, fromUserCoordinate} from './proj.js'; * will be tested for features. By default, all visible layers will be tested. * @property {number} [hitTolerance=0] Hit-detection tolerance in pixels. Pixels * inside the radius around the given position will be checked for features. + * @property {boolean} [checkWrapped=true] Check-Wrapped Will check for for wrapped geometries inside the range of + * +/- 1 world width. Works only if a projection is used that can be wrapped. */ @@ -553,8 +555,9 @@ class PluggableMap extends BaseObject { opt_options.hitTolerance * this.frameState_.pixelRatio : 0; const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE; + const checkWrapped = opt_options.checkWrapped !== false; return this.renderer_.forEachFeatureAtCoordinate( - coordinate, this.frameState_, hitTolerance, callback, null, + coordinate, this.frameState_, hitTolerance, checkWrapped, callback, null, layerFilter, null); } @@ -624,8 +627,9 @@ class PluggableMap extends BaseObject { const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE; const hitTolerance = opt_options.hitTolerance !== undefined ? opt_options.hitTolerance * this.frameState_.pixelRatio : 0; + const checkWrapped = opt_options.checkWrapped !== false; return this.renderer_.hasFeatureAtCoordinate( - coordinate, this.frameState_, hitTolerance, layerFilter, null); + coordinate, this.frameState_, hitTolerance, checkWrapped, layerFilter, null); } /** diff --git a/src/ol/renderer/Layer.js b/src/ol/renderer/Layer.js index 51e0202081..d5860dcaa6 100644 --- a/src/ol/renderer/Layer.js +++ b/src/ol/renderer/Layer.js @@ -86,7 +86,6 @@ class LayerRenderer extends Observable { } ).bind(this); } - /** * @abstract * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. diff --git a/src/ol/renderer/Map.js b/src/ol/renderer/Map.js index 829d66916a..ee5ec70342 100644 --- a/src/ol/renderer/Map.js +++ b/src/ol/renderer/Map.js @@ -9,6 +9,7 @@ import {inView} from '../layer/Layer.js'; import {shared as iconImageCache} from '../style/IconImageCache.js'; import {compose as composeTransform, makeInverse} from '../transform.js'; import {renderDeclutterItems} from '../render.js'; +import {add} from '../coordinate.js'; /** * @abstract @@ -65,6 +66,7 @@ class MapRenderer extends Disposable { * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {number} hitTolerance Hit tolerance in pixels. + * @param {boolean} checkWrapped Check for wrapped geometries. * @param {function(this: S, import("../Feature.js").FeatureLike, * import("../layer/Layer.js").default): T} callback Feature callback. * @param {S} thisArg Value to use as `this` when executing `callback`. @@ -80,6 +82,7 @@ class MapRenderer extends Disposable { coordinate, frameState, hitTolerance, + checkWrapped, callback, thisArg, layerFilter, @@ -103,6 +106,7 @@ class MapRenderer extends Disposable { const projection = viewState.projection; let translatedCoordinate = coordinate; + const offsets = [[0, 0]]; if (projection.canWrapX()) { const projectionExtent = projection.getExtent(); const worldWidth = getWidth(projectionExtent); @@ -111,6 +115,9 @@ class MapRenderer extends Disposable { const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; } + if (checkWrapped) { + offsets.push([-worldWidth, 0], [worldWidth, 0]); + } } const layerStates = frameState.layerStatesArray; @@ -121,21 +128,22 @@ class MapRenderer extends Disposable { return entry.value; }); } - let i; - for (i = numLayers - 1; i >= 0; --i) { - const layerState = layerStates[i]; - const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer); - if (layer.hasRenderer() && inView(layerState, viewState) && layerFilter.call(thisArg2, layer)) { - const layerRenderer = layer.getRenderer(); - const source = layer.getSource(); - if (layerRenderer && source) { - const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed); - result = layerRenderer.forEachFeatureAtCoordinate( - source.getWrapX() ? translatedCoordinate : coordinate, - frameState, hitTolerance, callback, declutteredFeatures); - } - if (result) { - return result; + for (let i = 0; i < offsets.length; i++) { + for (let j = numLayers - 1; j >= 0; --j) { + const layerState = layerStates[j]; + const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer); + if (layer.hasRenderer() && inView(layerState, viewState) && layerFilter.call(thisArg2, layer)) { + const layerRenderer = layer.getRenderer(); + const source = layer.getSource(); + if (layerRenderer && source) { + const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed); + result = layerRenderer.forEachFeatureAtCoordinate( + add(source.getWrapX() ? translatedCoordinate : coordinate, offsets[i]), + frameState, hitTolerance, callback, declutteredFeatures); + } + if (result) { + return result; + } } } } @@ -164,6 +172,7 @@ class MapRenderer extends Disposable { * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {number} hitTolerance Hit tolerance in pixels. + * @param {boolean} checkWrapped Check for wrapped geometries. * @param {function(this: U, import("../layer/Layer.js").default): 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 @@ -172,9 +181,9 @@ class MapRenderer extends Disposable { * @return {boolean} Is there a feature at the given coordinate? * @template U */ - hasFeatureAtCoordinate(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + hasFeatureAtCoordinate(coordinate, frameState, hitTolerance, checkWrapped, layerFilter, thisArg) { const hasFeature = this.forEachFeatureAtCoordinate( - coordinate, frameState, hitTolerance, TRUE, this, layerFilter, thisArg); + coordinate, frameState, hitTolerance, checkWrapped, TRUE, this, layerFilter, thisArg); return hasFeature !== undefined; } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 9628ba51f5..ee9110e5e1 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -202,6 +202,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const layer = this.getLayer(); /** @type {!Object} */ const features = {}; + const result = this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, /** * @param {import("../../Feature.js").FeatureLike} feature Feature. @@ -214,6 +215,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { return callback(feature, layer); } }, layer.getDeclutter() ? declutteredFeatures : null); + return result; } }