From c0d98847159998061a85701d915faefd0528b9a2 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Thu, 22 Oct 2026 18:53:57 -0700 Subject: [PATCH] Pick the marker drawn under the tap, label included, and size the info bubble by the icon's real extent --- src/TomTomView.web.tsx | 5 +- src/marker/TomTomMarkerController.ts | 104 +++++++++++++++------------ 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/TomTomView.web.tsx b/src/TomTomView.web.tsx index 93135cd..1089cd1 100644 --- a/src/TomTomView.web.tsx +++ b/src/TomTomView.web.tsx @@ -25,6 +25,7 @@ import { type MarkerAnimationOverlayEntry, type MapViewControllerInterface, mapViewStateInternal, + markerIconExtentPx, } from '@mapconductor/js-sdk-core'; import { TomTomProvider, TomTomConfig } from './TomTomProvider'; import type { TomTomViewStateInterface } from './TomTomViewState'; @@ -267,12 +268,12 @@ function InternalTomTomMapView({ const pos = entry.positionProvider(); const screenOffset = holder.toScreenOffset(pos); const icon = entry.icon; - const iconPixelSize = icon ? icon.iconSize * icon.scale : 0; + const iconPixelSize = markerIconExtentPx(icon); return ( | null { + const tolerance = + pointerType === 'touch' ? MARKER_HIT_RADIUS_TOUCH_PX : MARKER_HIT_RADIUS_MOUSE_PX; + let regular: MarkerHit> | null = null; const map = this.holder.map; const layerId = this.renderer.markerLayer.layerId; if (map.getLayer(layerId)) { @@ -295,57 +303,65 @@ export class TomTomMarkerController extends AbstractMarkerController this.holder.toScreenOffset(p as GeoPoint); + const bounds = markerSearchBounds(position, clickScreen, project); + const candidates = bounds + ? this.markerManager.findMarkersInBounds(bounds).filter(entity => entity.marker !== null) + : []; + regular = pickMarkerHit( + clickScreen, + candidates, + entity => { + const bmp = (entity.state.icon ?? createDefaultIcon()).toBitmapIcon(); + return { + anchorScreen: project(entity.state.position), + width: bmp.size.width, + height: bmp.size.height, + anchor: bmp.anchor, + }; + }, + tolerance, ); } - if (!hit) { - // アイコン画像サイズを考慮したフォールバック。最近傍の通常マーカーを投影し、 - // クリックがそのアイコン画像の矩形(size + anchor + 入力別 tolerance)内なら採用する。 - // queryRenderedFeatures / 固定 tapTolerance box では取りこぼす、大きなアイコン - //(幅広ラベルや大きな画像)でも画像全体でヒットさせる(android の find と同じ判定)。 - const nearest = this.markerManager.findNearest(position); - if (nearest && nearest.marker !== null) { - const bmp = (nearest.state.icon ?? createDefaultIcon()).toBitmapIcon(); - const markerScreen = this.holder.toScreenOffset(nearest.state.position); - if (markerScreen) { - const tol = - pointerType === 'touch' ? MARKER_HIT_RADIUS_TOUCH_PX : MARKER_HIT_RADIUS_MOUSE_PX; - const dx = clickScreen.x - markerScreen.x; - const dy = clickScreen.y - markerScreen.y; - const left = -bmp.anchor.x * bmp.size.width - tol; - const right = (1 - bmp.anchor.x) * bmp.size.width + tol; - const top = -bmp.anchor.y * bmp.size.height - tol; - const bottom = (1 - bmp.anchor.y) * bmp.size.height + tol; - if (dx >= left && dx <= right && dy >= top && dy <= bottom) { - hit = nearest; - } - } - } - } - if (hit) return hit; } - // No regular-marker hit → tiled-marker (raster) geographic-radius hit-test. - const nearest = this.markerManager.findNearest(position); - if (nearest?.marker === null) { - const hitRadius = - pointerType === 'touch' ? MARKER_HIT_RADIUS_TOUCH_PX : MARKER_HIT_RADIUS_MOUSE_PX; - const found = this.tileRenderer?.findNearest(position, hitRadius, zoom); - return found ? (this.markerManager.getEntity(found.id) ?? null) : null; - } - return null; + // タイル化マーカーも同じ規則で比べる(指の下に描かれていれば、通常マーカーの近接より勝つ)。 + const tiledHit = this.tileRenderer?.findHit(position, tolerance, zoom) ?? null; + const tiledEntity = tiledHit ? this.markerManager.getEntity(tiledHit.item.id) : null; + const best = preferMarkerHit>( + regular, + tiledHit && tiledEntity ? { ...tiledHit, item: tiledEntity } : null, + ); + return best?.item ?? null; } + // ── other existing methods ──────────────────────────────────────────────── override async update(state: MarkerState): Promise {