From 0ddf3d9f8c33c8ab0df560e402b60ed455f8b2ce Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 13 Dec 2023 10:28:49 +0000 Subject: [PATCH 1/3] Moved over to basic canvas rendering for circles to fix stroke position. Also `circle-blur` is now possible so I added that also. --- src/stylefunction.js | 78 ++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/src/stylefunction.js b/src/stylefunction.js index 040da6b7..141c3d43 100644 --- a/src/stylefunction.js +++ b/src/stylefunction.js @@ -4,7 +4,6 @@ Copyright 2016-present ol-mapbox-style contributors License: https://raw.githubusercontent.com/openlayers/ol-mapbox-style/master/LICENSE */ -import Circle from 'ol/style/Circle.js'; import Fill from 'ol/style/Fill.js'; import Icon from 'ol/style/Icon.js'; import RenderFeature from 'ol/render/Feature.js'; @@ -1140,6 +1139,16 @@ export function stylefunction( functionCache, featureState ); + + const circleBlur = getValue( + layer, + 'paint', + 'circle-blur', + zoom, + f, + functionCache, + featureState + ); const cache_key = circleRadius + '.' + @@ -1147,33 +1156,54 @@ export function stylefunction( '.' + circleColor + '.' + - circleStrokeWidth; - iconImg = iconImageCache[cache_key]; - if (!iconImg) { - iconImg = new Circle({ - radius: circleRadius, - stroke: - circleStrokeColor && circleStrokeWidth > 0 - ? new Stroke({ - width: circleStrokeWidth, - color: circleStrokeColor, - }) - : undefined, - fill: circleColor - ? new Fill({ - color: circleColor, - }) - : undefined, - declutterMode: 'none', - }); - iconImageCache[cache_key] = iconImg; - } - style.setImage(iconImg); + circleStrokeWidth + + '.' + + circleBlur; + text = style.getText(); style.setText(undefined); style.setGeometry(undefined); style.setZIndex(index); - hasImage = true; + style.setRenderer(([x, y], renderOpts) => { + const r = circleRadius * renderOpts.pixelRatio; + const sw = circleStrokeWidth * renderOpts.pixelRatio; + const cb = circleBlur * renderOpts.pixelRatio; + + const w = r * 2 + sw * 2 + sw / 2 + cb * 2; + const h = r * 2 + sw * 2 + sw / 2 + cb * 2; + + let bitmap = iconImageCache[cache_key]; + if (!bitmap) { + const offscreenBuffer = new OffscreenCanvas(w, h); + const ctx = offscreenBuffer.getContext('2d'); + + if (cb !== 0) { + ctx.filter = `blur(${cb}px)`; + } + + const ox = w / 2; + const oy = h / 2; + + ctx.save(); + ctx.beginPath(); + ctx.arc(ox, oy, r, 0, 2 * Math.PI); + ctx.fillStyle = circleColor; + ctx.fill(); + ctx.restore(); + + ctx.save(); + ctx.beginPath(); + ctx.arc(ox, oy, r + sw / 2, 0, 2 * Math.PI); + ctx.strokeStyle = circleStrokeColor; + ctx.lineWidth = sw; + ctx.stroke(); + ctx.restore(); + ctx.restore(); + bitmap = offscreenBuffer.transferToImageBitmap(); + iconImageCache[cache_key] = bitmap; + } + renderOpts.context.drawImage(bitmap, x - w / 2, y - h / 2); + }); } let label, font, textLineHeight, textSize, letterSpacing, maxTextWidth; From e392ac8cda0a77ddfb47c0723c9fbaf8c5bb63b8 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 13 Dec 2023 10:39:26 +0000 Subject: [PATCH 2/3] Added translate back into circle cache key. --- src/stylefunction.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stylefunction.js b/src/stylefunction.js index 509a06ff..436bdee5 100644 --- a/src/stylefunction.js +++ b/src/stylefunction.js @@ -1170,6 +1170,10 @@ export function stylefunction( '.' + circleStrokeWidth + '.' + + circleTranslate[0] + + '.' + + circleTranslate[1] + + '.' + circleBlur; text = style.getText(); style.setText(undefined); From d2aba6d889a5695f5579b5fe9fe41c31b020dc1b Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 18 Dec 2023 14:23:07 +0000 Subject: [PATCH 3/3] Fixes for circle-blur and added `setHitDetectionRenderer` --- src/stylefunction.js | 87 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/src/stylefunction.js b/src/stylefunction.js index 436bdee5..fa653c4d 100644 --- a/src/stylefunction.js +++ b/src/stylefunction.js @@ -1175,19 +1175,61 @@ export function stylefunction( circleTranslate[1] + '.' + circleBlur; - text = style.getText(); - style.setText(undefined); - style.setGeometry(undefined); - style.setZIndex(index); - style.setRenderer(([x, y], renderOpts) => { + + const hit_cache_key = cache_key + '_hit'; + + const renderHitDetection = ( + [x, y], + renderOpts, + pixelRatioOverride + ) => { + // NOTE: Hit detection rendering appears to assume pixelRatio of 1 + // Allowing an `pixelRatioOverride` allows us debug by using this + // function in `setRenderer` + const pixelRatio = pixelRatioOverride || 1; + const r = + circleRadius * pixelRatio + + circleStrokeWidth * pixelRatio + + circleBlur * pixelRatio; + const sw = circleStrokeWidth * pixelRatio; + const cb = circleBlur * pixelRatio; + const xo = x + circleTranslate[0] * pixelRatio; + const yo = y + circleTranslate[1] * pixelRatio; + + const w = r * 2 + sw * 2 + cb * 3 + 1; + const h = r * 2 + sw * 2 + cb * 3 + 1; + + let bitmap = iconImageCache[hit_cache_key]; + if (!bitmap) { + const offscreenBuffer = new OffscreenCanvas(w, h); + const ctx = offscreenBuffer.getContext('2d'); + + const ox = w / 2; + const oy = h / 2; + + ctx.save(); + ctx.beginPath(); + ctx.arc(ox, oy, r, 0, 2 * Math.PI); + ctx.fillStyle = 'black'; + ctx.fill(); + ctx.closePath(); + ctx.restore(); + + bitmap = offscreenBuffer.transferToImageBitmap(); + iconImageCache[hit_cache_key] = bitmap; + } + renderOpts.context.drawImage(bitmap, xo - w / 2, yo - h / 2); + }; + + const styleRender = ([x, y], renderOpts) => { const r = circleRadius * renderOpts.pixelRatio; const sw = circleStrokeWidth * renderOpts.pixelRatio; const cb = circleBlur * renderOpts.pixelRatio; const xo = x + circleTranslate[0] * renderOpts.pixelRatio; const yo = y + circleTranslate[1] * renderOpts.pixelRatio; - const w = r * 2 + sw * 2 + sw / 2 + cb * 2; - const h = r * 2 + sw * 2 + sw / 2 + cb * 2; + const w = r * 2 + sw * 2 + cb * 4 + 1; + const h = r * 2 + sw * 2 + cb * 4 + 1; let bitmap = iconImageCache[cache_key]; if (!bitmap) { @@ -1204,23 +1246,34 @@ export function stylefunction( ctx.save(); ctx.beginPath(); ctx.arc(ox, oy, r, 0, 2 * Math.PI); - ctx.fillStyle = circleColor; + ctx.fillStyle = 'pink'; ctx.fill(); + ctx.closePath(); ctx.restore(); - ctx.save(); - ctx.beginPath(); - ctx.arc(ox, oy, r + sw / 2, 0, 2 * Math.PI); - ctx.strokeStyle = circleStrokeColor; - ctx.lineWidth = sw; - ctx.stroke(); - ctx.restore(); - ctx.restore(); + if (circleStrokeWidth > 0) { + ctx.save(); + ctx.beginPath(); + ctx.arc(ox, oy, r + sw / 2, 0, 2 * Math.PI); + ctx.strokeStyle = circleStrokeColor; + ctx.lineWidth = sw; + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + ctx.restore(); + } bitmap = offscreenBuffer.transferToImageBitmap(); iconImageCache[cache_key] = bitmap; } renderOpts.context.drawImage(bitmap, xo - w / 2, yo - h / 2); - }); + }; + + text = style.getText(); + style.setText(undefined); + style.setGeometry(undefined); + style.setZIndex(index); + style.setHitDetectionRenderer(renderHitDetection); + style.setRenderer(styleRender); } let label, font, textLineHeight, textSize, letterSpacing, maxTextWidth;