mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-05-20 14:44:51 +02:00
fix(map): Unify label/polygon click paths and fix boundary fitBounds
Fix A: Label click now also queries polygon layers - When clicking park/forest/cemetery labels, also query landuse_park fill layer to get polygon geometry - If polygon found, use it as boundary directly from rendered tiles - Eliminates need for API round-trip for park boundaries Fix B: Boundary fitBounds behavior changed - If boundary EXISTS: ALWAYS fitBounds (zoom in OR out) to show the full boundary - the boundary defines what user should see - If NO boundary: NEVER change zoom for map clicks/label clicks - Search results: fly to center but preserve current zoom level Removed previous z14 cap and zoom-in-only logic - boundaries now always control the camera as expected. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
62669fc7de
commit
1ad43e58cf
1 changed files with 35 additions and 17 deletions
|
|
@ -2233,6 +2233,25 @@ const MapView = forwardRef(function MapView(_, ref) {
|
||||||
featureLat = geom.coordinates[1]
|
featureLat = geom.coordinates[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX A: For park-type features, also query polygon layers to get boundary geometry
|
||||||
|
const parkKinds = ['national_park', 'park', 'cemetery', 'protected_area', 'nature_reserve', 'forest', 'golf_course', 'wood', 'zoo', 'garden']
|
||||||
|
let polygonGeometry = null
|
||||||
|
if (parkKinds.includes(props.kind)) {
|
||||||
|
// Query fill layers at the same point to find the polygon
|
||||||
|
const fillLayers = ['landuse_park', 'landuse_other'].filter(id => map.getLayer(id))
|
||||||
|
if (fillLayers.length > 0) {
|
||||||
|
const fillFeatures = map.queryRenderedFeatures(e.point, { layers: fillLayers })
|
||||||
|
// Find a polygon feature with matching name or at the same location
|
||||||
|
const matchingPolygon = fillFeatures.find(f =>
|
||||||
|
f.properties?.name === props.name ||
|
||||||
|
(f.geometry?.type === 'Polygon' || f.geometry?.type === 'MultiPolygon')
|
||||||
|
)
|
||||||
|
if (matchingPolygon?.geometry) {
|
||||||
|
polygonGeometry = matchingPolygon.geometry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Apply feature state highlight
|
// Apply feature state highlight
|
||||||
const featureId = labelFeature.id ?? props.mvt_id
|
const featureId = labelFeature.id ?? props.mvt_id
|
||||||
const sourceLayer = labelFeature.sourceLayer
|
const sourceLayer = labelFeature.sourceLayer
|
||||||
|
|
@ -2251,6 +2270,11 @@ const MapView = forwardRef(function MapView(_, ref) {
|
||||||
// For feature clicks, don't show pin marker
|
// For feature clicks, don't show pin marker
|
||||||
store.clearClickMarker()
|
store.clearClickMarker()
|
||||||
|
|
||||||
|
// If we found polygon geometry from the fill layer, use it as boundary directly
|
||||||
|
if (polygonGeometry && updateBoundaryRef.current) {
|
||||||
|
updateBoundaryRef.current(polygonGeometry)
|
||||||
|
}
|
||||||
|
|
||||||
store.setSelectedPlace({
|
store.setSelectedPlace({
|
||||||
lat: featureLat,
|
lat: featureLat,
|
||||||
lon: featureLon,
|
lon: featureLon,
|
||||||
|
|
@ -2269,6 +2293,7 @@ const MapView = forwardRef(function MapView(_, ref) {
|
||||||
kind: props.kind || null,
|
kind: props.kind || null,
|
||||||
kind_detail: props.kind_detail || null,
|
kind_detail: props.kind_detail || null,
|
||||||
elevation: props.elevation || null,
|
elevation: props.elevation || null,
|
||||||
|
polygonGeometry: polygonGeometry || null, // Store polygon if found
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2421,19 +2446,9 @@ const MapView = forwardRef(function MapView(_, ref) {
|
||||||
// Validate bounds before fitting
|
// Validate bounds before fitting
|
||||||
if (minLng >= -180 && maxLng <= 180 && minLat >= -90 && maxLat <= 90 &&
|
if (minLng >= -180 && maxLng <= 180 && minLat >= -90 && maxLat <= 90 &&
|
||||||
minLng < maxLng && minLat < maxLat) {
|
minLng < maxLng && minLat < maxLat) {
|
||||||
|
// FIX B: ALWAYS fitBounds when boundary exists - zoom in OR out
|
||||||
|
// The boundary defines what the user should see
|
||||||
const bounds = [[minLng, minLat], [maxLng, maxLat]]
|
const bounds = [[minLng, minLat], [maxLng, maxLat]]
|
||||||
const currentZoom = map.getZoom()
|
|
||||||
const target = map.cameraForBounds(bounds, { padding: 50 })
|
|
||||||
|
|
||||||
// Zoom-in only: allow zoom in to show boundary, never zoom out
|
|
||||||
// - target.zoom > currentZoom → zoom IN to fit (always allowed)
|
|
||||||
// - target.zoom < currentZoom → DON'T zoom out (skip fitBounds)
|
|
||||||
// - target.zoom == currentZoom → pan only (allowed)
|
|
||||||
if (!target || target.zoom < currentZoom) {
|
|
||||||
// Would zoom out — just draw the boundary without moving camera
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
map.fitBounds(bounds, {
|
map.fitBounds(bounds, {
|
||||||
padding: 50,
|
padding: 50,
|
||||||
duration: 700,
|
duration: 700,
|
||||||
|
|
@ -2638,14 +2653,17 @@ const MapView = forwardRef(function MapView(_, ref) {
|
||||||
} else {
|
} else {
|
||||||
lastFlyTargetRef.current = placeKey
|
lastFlyTargetRef.current = placeKey
|
||||||
|
|
||||||
// Only fly to place if it came from search (not map-click which already centered)
|
// FIX B: Camera behavior depends on source and whether boundary exists
|
||||||
|
// - map_click / basemap_label: NO camera movement (boundary fitBounds handles it if exists)
|
||||||
|
// - search results: fly to center, but DON'T change zoom (user chose their zoom)
|
||||||
if (selectedPlace.source !== 'map_click' && selectedPlace.source !== 'basemap_label') {
|
if (selectedPlace.source !== 'map_click' && selectedPlace.source !== 'basemap_label') {
|
||||||
// Only fly IN if below z14. At z14+ do nothing.
|
// Search result - fly to center without changing zoom
|
||||||
|
// Note: if this place has a boundary, the boundary fitBounds will zoom appropriately
|
||||||
const currentZoom = map.getZoom()
|
const currentZoom = map.getZoom()
|
||||||
if (currentZoom < 14) {
|
map.flyTo({ center: [selectedPlace.lon, selectedPlace.lat], zoom: currentZoom, duration: 800 })
|
||||||
map.flyTo({ center: [selectedPlace.lon, selectedPlace.lat], zoom: 14, duration: 800 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// For map_click and basemap_label: do nothing to camera
|
||||||
|
// The boundary fitBounds will handle zooming if a boundary is fetched
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different visual feedback based on mode
|
// Different visual feedback based on mode
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue