From 5a7e4dfaf6f0acdb3872533a766e605806005899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Tue, 9 Feb 2021 22:33:08 +0100 Subject: [PATCH 1/5] Add a constant for the hit-detection resolution --- src/ol/render/canvas/hitdetect.js | 15 +++++++++------ src/ol/renderer/canvas/VectorLayer.js | 19 ++++++++++--------- src/ol/renderer/canvas/VectorTileLayer.js | 16 ++++++++-------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/ol/render/canvas/hitdetect.js b/src/ol/render/canvas/hitdetect.js index a95b77d653..b3da3725ff 100644 --- a/src/ol/render/canvas/hitdetect.js +++ b/src/ol/render/canvas/hitdetect.js @@ -11,6 +11,8 @@ import {createCanvasContext2D} from '../../dom.js'; import {intersects} from '../../extent.js'; import {numberSafeCompareFunction} from '../../array.js'; +export const HIT_DETECT_RESOLUTION = 0.5; + /** * @param {import("../../size.js").Size} size Canvas size in css pixels. * @param {Array} transforms Transforms @@ -34,14 +36,14 @@ export function createHitDetectionImageData( resolution, rotation ) { - const width = size[0] / 2; - const height = size[1] / 2; + const width = size[0] * HIT_DETECT_RESOLUTION; + const height = size[1] * HIT_DETECT_RESOLUTION; const context = createCanvasContext2D(width, height); context.imageSmoothingEnabled = false; const canvas = context.canvas; const renderer = new CanvasImmediateRenderer( context, - 0.5, + HIT_DETECT_RESOLUTION, extent, null, rotation @@ -163,13 +165,14 @@ export function createHitDetectionImageData( export function hitDetect(pixel, features, imageData) { const resultFeatures = []; if (imageData) { + const x = Math.floor(Math.round(pixel[0]) * HIT_DETECT_RESOLUTION); + const y = Math.floor(Math.round(pixel[1]) * HIT_DETECT_RESOLUTION); // The pixel coordinate is clamped down to the hit-detect canvas' size to account // for browsers returning coordinates slightly larger than the actual canvas size // due to a non-integer pixel ratio. const index = - (clamp(Math.floor(Math.round(pixel[0]) / 2), 0, imageData.width - 1) + - clamp(Math.floor(Math.round(pixel[1]) / 2), 0, imageData.height - 1) * - imageData.width) * + (clamp(x, 0, imageData.width - 1) + + clamp(y, 0, imageData.height - 1) * imageData.width) * 4; const r = imageData.data[index]; const g = imageData.data[index + 1]; diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index c27704de4e..2f123e99df 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -5,6 +5,11 @@ import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasLayerRenderer from './Layer.js'; import ExecutorGroup from '../../render/canvas/ExecutorGroup.js'; import ViewHint from '../../ViewHint.js'; +import { + HIT_DETECT_RESOLUTION, + createHitDetectionImageData, + hitDetect, +} from '../../render/canvas/hitdetect.js'; import { apply, makeInverse, @@ -19,10 +24,6 @@ import { intersects as intersectsExtent, wrapX as wrapExtentX, } from '../../extent.js'; -import { - createHitDetectionImageData, - hitDetect, -} from '../../render/canvas/hitdetect.js'; import { defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, @@ -325,14 +326,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const extent = this.renderedExtent_; const layer = this.getLayer(); const transforms = []; - const width = size[0] / 2; - const height = size[1] / 2; + const width = size[0] * HIT_DETECT_RESOLUTION; + const height = size[1] * HIT_DETECT_RESOLUTION; transforms.push( this.getRenderTransform( center, resolution, rotation, - 0.5, + HIT_DETECT_RESOLUTION, width, height, 0 @@ -357,7 +358,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { center, resolution, rotation, - 0.5, + HIT_DETECT_RESOLUTION, width, height, offsetX @@ -375,7 +376,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { center, resolution, rotation, - 0.5, + HIT_DETECT_RESOLUTION, width, height, offsetX diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 89f44111db..f0a61fcce6 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -9,6 +9,11 @@ import ReplayType from '../../render/canvas/BuilderType.js'; import TileState from '../../TileState.js'; import VectorTileRenderType from '../../layer/VectorTileRenderType.js'; import ViewHint from '../../ViewHint.js'; +import { + HIT_DETECT_RESOLUTION, + createHitDetectionImageData, + hitDetect, +} from '../../render/canvas/hitdetect.js'; import { apply as applyTransform, create as createTransform, @@ -28,10 +33,6 @@ import { intersects, } from '../../extent.js'; import {clear} from '../../obj.js'; -import { - createHitDetectionImageData, - hitDetect, -} from '../../render/canvas/hitdetect.js'; import { getSquaredTolerance as getSquaredRenderTolerance, renderFeature, @@ -549,16 +550,15 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const tileSize = toSize( tileGrid.getTileSize(tileGrid.getZForResolution(resolution)) ); - const size = [tileSize[0] / 2, tileSize[1] / 2]; const rotation = this.renderedRotation_; const transforms = [ this.getRenderTransform( tileGrid.getTileCoordCenter(tile.wrappedTileCoord), resolution, 0, - 0.5, - size[0], - size[1], + HIT_DETECT_RESOLUTION, + tileSize[0] * HIT_DETECT_RESOLUTION, + tileSize[1] * HIT_DETECT_RESOLUTION, 0 ), ]; From 8645a3109d1d0c9ea5e41d28bf55bac9bee0985f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Wed, 10 Feb 2021 00:05:24 +0100 Subject: [PATCH 2/5] Fix zIndex handling for hitdetection getZIndex returns number|undefined, so Number conversion is unnecessary, but undefined was converted to NaN instead of zero. --- src/ol/render/canvas/hitdetect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/render/canvas/hitdetect.js b/src/ol/render/canvas/hitdetect.js index b3da3725ff..c907a736ed 100644 --- a/src/ol/render/canvas/hitdetect.js +++ b/src/ol/render/canvas/hitdetect.js @@ -114,7 +114,7 @@ export function createHitDetectionImageData( }) ); } - const zIndex = Number(style.getZIndex()); + const zIndex = style.getZIndex() || 0; let byGeometryType = featuresByZIndex[zIndex]; if (!byGeometryType) { byGeometryType = {}; From de3f60861d2349751befa9995c0a896091306b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Wed, 10 Feb 2021 00:15:43 +0100 Subject: [PATCH 3/5] Fix hitdetection of icons with opacity < 1 Hit detection works by reading the pixel color, if the icon is not drawn with full opacity it won't work. --- src/ol/render/canvas/hitdetect.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/render/canvas/hitdetect.js b/src/ol/render/canvas/hitdetect.js index c907a736ed..0efda29a36 100644 --- a/src/ol/render/canvas/hitdetect.js +++ b/src/ol/render/canvas/hitdetect.js @@ -81,7 +81,7 @@ export function createHitDetectionImageData( } style.setText(undefined); const image = originalStyle.getImage(); - if (image) { + if (image && image.getOpacity() !== 0) { const imgSize = image.getImageSize(); if (!imgSize) { continue; @@ -106,8 +106,8 @@ export function createHitDetectionImageData( anchorXUnits: IconAnchorUnits.PIXELS, anchorYUnits: IconAnchorUnits.PIXELS, offset: image.getOrigin(), + opacity: 1, size: image.getSize(), - opacity: image.getOpacity(), scale: image.getScale(), rotation: image.getRotation(), rotateWithView: image.getRotateWithView(), From 99564671c1a58c292e86e527c9e9337d5638d65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Wed, 10 Feb 2021 00:14:28 +0100 Subject: [PATCH 4/5] Only create one canvas per icon --- src/ol/render/canvas/hitdetect.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ol/render/canvas/hitdetect.js b/src/ol/render/canvas/hitdetect.js index 0efda29a36..c8d702874f 100644 --- a/src/ol/render/canvas/hitdetect.js +++ b/src/ol/render/canvas/hitdetect.js @@ -87,17 +87,12 @@ export function createHitDetectionImageData( continue; } - const canvas = document.createElement('canvas'); - canvas.width = imgSize[0]; - canvas.height = imgSize[1]; - const imgContext = canvas.getContext('2d', {alpha: false}); + const img = document.createElement('canvas'); + img.width = imgSize[0]; + img.height = imgSize[1]; + const imgContext = img.getContext('2d', {alpha: false}); imgContext.fillStyle = color; - const img = imgContext.canvas; imgContext.fillRect(0, 0, img.width, img.height); - const width = imgSize ? imgSize[0] : img.width; - const height = imgSize ? imgSize[1] : img.height; - const iconContext = createCanvasContext2D(width, height); - iconContext.drawImage(img, 0, 0); style.setImage( new Icon({ img: img, From dde6d6559c6166a5ea712ce52c027297d889ab47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Wed, 10 Feb 2021 21:26:53 +0100 Subject: [PATCH 5/5] Reduce work for hitdetection Skip cloning of style if geometry is not in extent. --- src/ol/render/canvas/hitdetect.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ol/render/canvas/hitdetect.js b/src/ol/render/canvas/hitdetect.js index c8d702874f..d32acc468c 100644 --- a/src/ol/render/canvas/hitdetect.js +++ b/src/ol/render/canvas/hitdetect.js @@ -69,6 +69,10 @@ export function createHitDetectionImageData( const color = '#' + ('000000' + index.toString(16)).slice(-6); for (let j = 0, jj = styles.length; j < jj; ++j) { const originalStyle = styles[j]; + const geometry = originalStyle.getGeometryFunction()(feature); + if (!geometry || !intersects(extent, geometry.getExtent())) { + continue; + } const style = originalStyle.clone(); const fill = style.getFill(); if (fill) { @@ -119,13 +123,10 @@ export function createHitDetectionImageData( byGeometryType[GeometryType.LINE_STRING] = []; byGeometryType[GeometryType.POINT] = []; } - const geometry = style.getGeometryFunction()(feature); - if (geometry && intersects(extent, geometry.getExtent())) { - byGeometryType[geometry.getType().replace('Multi', '')].push( - geometry, - style - ); - } + byGeometryType[geometry.getType().replace('Multi', '')].push( + geometry, + style + ); } }