Check hits for wrapped around geometries

This commit is contained in:
Simon Seyock
2019-09-25 18:02:04 +02:00
parent 6b5c2f47bb
commit 54cbe14f88
4 changed files with 34 additions and 20 deletions

View File

@@ -75,6 +75,8 @@ import {toUserCoordinate, fromUserCoordinate} from './proj.js';
* will be tested for features. By default, all visible layers will be tested. * will be tested for features. By default, all visible layers will be tested.
* @property {number} [hitTolerance=0] Hit-detection tolerance in pixels. Pixels * @property {number} [hitTolerance=0] Hit-detection tolerance in pixels. Pixels
* inside the radius around the given position will be checked for features. * 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; opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
const layerFilter = opt_options.layerFilter !== undefined ? const layerFilter = opt_options.layerFilter !== undefined ?
opt_options.layerFilter : TRUE; opt_options.layerFilter : TRUE;
const checkWrapped = opt_options.checkWrapped !== false;
return this.renderer_.forEachFeatureAtCoordinate( return this.renderer_.forEachFeatureAtCoordinate(
coordinate, this.frameState_, hitTolerance, callback, null, coordinate, this.frameState_, hitTolerance, checkWrapped, callback, null,
layerFilter, null); layerFilter, null);
} }
@@ -624,8 +627,9 @@ class PluggableMap extends BaseObject {
const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE; const layerFilter = opt_options.layerFilter !== undefined ? opt_options.layerFilter : TRUE;
const hitTolerance = opt_options.hitTolerance !== undefined ? const hitTolerance = opt_options.hitTolerance !== undefined ?
opt_options.hitTolerance * this.frameState_.pixelRatio : 0; opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
const checkWrapped = opt_options.checkWrapped !== false;
return this.renderer_.hasFeatureAtCoordinate( return this.renderer_.hasFeatureAtCoordinate(
coordinate, this.frameState_, hitTolerance, layerFilter, null); coordinate, this.frameState_, hitTolerance, checkWrapped, layerFilter, null);
} }
/** /**

View File

@@ -86,7 +86,6 @@ class LayerRenderer extends Observable {
} }
).bind(this); ).bind(this);
} }
/** /**
* @abstract * @abstract
* @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../coordinate.js").Coordinate} coordinate Coordinate.

View File

@@ -9,6 +9,7 @@ import {inView} from '../layer/Layer.js';
import {shared as iconImageCache} from '../style/IconImageCache.js'; import {shared as iconImageCache} from '../style/IconImageCache.js';
import {compose as composeTransform, makeInverse} from '../transform.js'; import {compose as composeTransform, makeInverse} from '../transform.js';
import {renderDeclutterItems} from '../render.js'; import {renderDeclutterItems} from '../render.js';
import {add} from '../coordinate.js';
/** /**
* @abstract * @abstract
@@ -65,6 +66,7 @@ class MapRenderer extends Disposable {
* @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../coordinate.js").Coordinate} coordinate Coordinate.
* @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {import("../PluggableMap.js").FrameState} frameState FrameState.
* @param {number} hitTolerance Hit tolerance in pixels. * @param {number} hitTolerance Hit tolerance in pixels.
* @param {boolean} checkWrapped Check for wrapped geometries.
* @param {function(this: S, import("../Feature.js").FeatureLike, * @param {function(this: S, import("../Feature.js").FeatureLike,
* import("../layer/Layer.js").default): T} callback Feature callback. * import("../layer/Layer.js").default): T} callback Feature callback.
* @param {S} thisArg Value to use as `this` when executing `callback`. * @param {S} thisArg Value to use as `this` when executing `callback`.
@@ -80,6 +82,7 @@ class MapRenderer extends Disposable {
coordinate, coordinate,
frameState, frameState,
hitTolerance, hitTolerance,
checkWrapped,
callback, callback,
thisArg, thisArg,
layerFilter, layerFilter,
@@ -103,6 +106,7 @@ class MapRenderer extends Disposable {
const projection = viewState.projection; const projection = viewState.projection;
let translatedCoordinate = coordinate; let translatedCoordinate = coordinate;
const offsets = [[0, 0]];
if (projection.canWrapX()) { if (projection.canWrapX()) {
const projectionExtent = projection.getExtent(); const projectionExtent = projection.getExtent();
const worldWidth = getWidth(projectionExtent); const worldWidth = getWidth(projectionExtent);
@@ -111,6 +115,9 @@ class MapRenderer extends Disposable {
const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth); const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]]; translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
} }
if (checkWrapped) {
offsets.push([-worldWidth, 0], [worldWidth, 0]);
}
} }
const layerStates = frameState.layerStatesArray; const layerStates = frameState.layerStatesArray;
@@ -121,21 +128,22 @@ class MapRenderer extends Disposable {
return entry.value; return entry.value;
}); });
} }
let i; for (let i = 0; i < offsets.length; i++) {
for (i = numLayers - 1; i >= 0; --i) { for (let j = numLayers - 1; j >= 0; --j) {
const layerState = layerStates[i]; const layerState = layerStates[j];
const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer); const layer = /** @type {import("../layer/Layer.js").default} */ (layerState.layer);
if (layer.hasRenderer() && inView(layerState, viewState) && layerFilter.call(thisArg2, layer)) { if (layer.hasRenderer() && inView(layerState, viewState) && layerFilter.call(thisArg2, layer)) {
const layerRenderer = layer.getRenderer(); const layerRenderer = layer.getRenderer();
const source = layer.getSource(); const source = layer.getSource();
if (layerRenderer && source) { if (layerRenderer && source) {
const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed); const callback = forEachFeatureAtCoordinate.bind(null, layerState.managed);
result = layerRenderer.forEachFeatureAtCoordinate( result = layerRenderer.forEachFeatureAtCoordinate(
source.getWrapX() ? translatedCoordinate : coordinate, add(source.getWrapX() ? translatedCoordinate : coordinate, offsets[i]),
frameState, hitTolerance, callback, declutteredFeatures); frameState, hitTolerance, callback, declutteredFeatures);
} }
if (result) { if (result) {
return result; return result;
}
} }
} }
} }
@@ -164,6 +172,7 @@ class MapRenderer extends Disposable {
* @param {import("../coordinate.js").Coordinate} coordinate Coordinate. * @param {import("../coordinate.js").Coordinate} coordinate Coordinate.
* @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {import("../PluggableMap.js").FrameState} frameState FrameState.
* @param {number} hitTolerance Hit tolerance in pixels. * @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 * @param {function(this: U, import("../layer/Layer.js").default): boolean} layerFilter Layer filter
* function, only layers which are visible and for which this function * function, only layers which are visible and for which this function
* returns `true` will be tested for features. By default, all visible * 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? * @return {boolean} Is there a feature at the given coordinate?
* @template U * @template U
*/ */
hasFeatureAtCoordinate(coordinate, frameState, hitTolerance, layerFilter, thisArg) { hasFeatureAtCoordinate(coordinate, frameState, hitTolerance, checkWrapped, layerFilter, thisArg) {
const hasFeature = this.forEachFeatureAtCoordinate( const hasFeature = this.forEachFeatureAtCoordinate(
coordinate, frameState, hitTolerance, TRUE, this, layerFilter, thisArg); coordinate, frameState, hitTolerance, checkWrapped, TRUE, this, layerFilter, thisArg);
return hasFeature !== undefined; return hasFeature !== undefined;
} }

View File

@@ -202,6 +202,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const layer = this.getLayer(); const layer = this.getLayer();
/** @type {!Object<string, boolean>} */ /** @type {!Object<string, boolean>} */
const features = {}; const features = {};
const result = this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, const result = this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance,
/** /**
* @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {import("../../Feature.js").FeatureLike} feature Feature.
@@ -214,6 +215,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
return callback(feature, layer); return callback(feature, layer);
} }
}, layer.getDeclutter() ? declutteredFeatures : null); }, layer.getDeclutter() ? declutteredFeatures : null);
return result; return result;
} }
} }