diff --git a/Sources/MapConductorForHERE/marker/HereMarkerHitTest.swift b/Sources/MapConductorForHERE/marker/HereMarkerHitTest.swift index 0ba7d16..872b7f9 100644 --- a/Sources/MapConductorForHERE/marker/HereMarkerHitTest.swift +++ b/Sources/MapConductorForHERE/marker/HereMarkerHitTest.swift @@ -1,20 +1,17 @@ import CoreGraphics import Foundation import heresdk -import MapConductorCore +@_spi(MapConductorDriver) import MapConductorCore /// Screen-space marker hit test, shared by the native-marker path /// (`HereMarkerController.hitTest`) and the strategy-marker path /// (`HereMapView`'s coordinator, used for clustering). /// -/// Mirrors `HereMarkerController.find` on Android: the icon's own box expanded by -/// `Settings.Default.tapTolerance` on every side, with overlapping markers broken -/// by distance to the anchor. +/// 選び方はコアの ``MarkerHitTest/pick`` と同じ(android-sdk の `AbstractMarkerController.find` とも +/// 同じ)。指の下にアイコン(ラベル込み)が描かれているマーカーを、アンカーの近い別のマーカーより +/// 優先し、どれにも当たらなければ `Settings.Default.tapTolerance` 以内でアンカーの近いものを選ぶ。 /// -/// This replaced an iOS-only "44pt minimum touch target" floor -/// (`max(iconSize × scale, 44)`), which never actually applied to the default 48pt -/// icon and which refused taps below the marker tip entirely. Measured on device, -/// the box went from 48/0/24/23 pt (up/down/left/right) to 62/13/38/37 pt. +/// HERE のビュー座標は物理ピクセルなので、`pixelScale` で割ってポイントに直してから比べる。 enum HereMarkerHitTest { /// - Parameters: /// - screenPoint: tap location in HERE view coordinates (physical pixels). @@ -26,36 +23,18 @@ enum HereMarkerHitTest { states: [MarkerState], defaultIcon: any MarkerIconProtocol ) -> MarkerState? { - let pixelScale = CGFloat(mapView.pixelScale) - let tolerancePx = Settings.Default.tapTolerance * pixelScale - - var bestState: MarkerState? - var bestDistance = CGFloat.infinity - - for state in states { - guard let p = mapView.geoToViewCoordinates( - geoCoordinates: state.position.toGeoCoordinates() - ) else { continue } - - let icon: any MarkerIconProtocol = state.icon ?? defaultIcon - // Rendered size in physical pixels: iconSize (pts) × scale × pixelScale. - // Icons are square canvases; anchor is normalized (0–1). - let iconPx = icon.iconSize * icon.scale * pixelScale - let renderedPx = iconPx + 2 * tolerancePx - - let left = CGFloat(p.x) - icon.anchor.x * iconPx - tolerancePx - let top = CGFloat(p.y) - icon.anchor.y * iconPx - tolerancePx - let hitRect = CGRect(x: left, y: top, width: renderedPx, height: renderedPx) - - guard hitRect.contains(screenPoint) else { continue } - - // Among overlapping markers prefer the one whose anchor is closest to the tap. - let distance = hypot(screenPoint.x - CGFloat(p.x), screenPoint.y - CGFloat(p.y)) - if distance < bestDistance { - bestDistance = distance - bestState = state - } - } - return bestState + let pixelScale = max(CGFloat(mapView.pixelScale), 1) + let touch = CGPoint(x: screenPoint.x / pixelScale, y: screenPoint.y / pixelScale) + return MarkerHitTest.pick( + touchScreen: touch, + candidates: states, + state: { $0 }, + project: { position in + mapView.geoToViewCoordinates(geoCoordinates: position.toGeoCoordinates()).map { + CGPoint(x: CGFloat($0.x) / pixelScale, y: CGFloat($0.y) / pixelScale) + } + }, + defaultIcon: defaultIcon + ) } }