Browse Source

fix: rebuild route layers after map style/projection settle

setProjection can wipe custom sources; wait for style ready and
re-add route layers on style.load so edit polylines stay visible.
main
xiaosi 2 weeks ago
parent
commit
f22d49e0aa
  1. 132
      src/views/TasksView/TasksView.vue

132
src/views/TasksView/TasksView.vue

@ -288,6 +288,7 @@ let routeMapInstance = null
let routeMapAttached = false let routeMapAttached = false
let pendingStyleHandler = null let pendingStyleHandler = null
let pendingFitRoute = false let pendingFitRoute = false
let styleRebuildHandler = null
const routeForm = reactive({ name: '', altitude: 50, speed: 10, turnMode: 'auto' }) const routeForm = reactive({ name: '', altitude: 50, speed: 10, turnMode: 'auto' })
const routePoints = ref([]) const routePoints = ref([])
@ -711,6 +712,34 @@ async function waitForMapHostSize(el, tries = 40) {
return el.clientWidth > 0 && el.clientHeight > 0 return el.clientWidth > 0 && el.clientHeight > 0
} }
async function waitForMapStyleReady(map, tries = 40) {
if (!map) return false
for (let i = 0; i < tries; i++) {
try {
if (map.isStyleLoaded?.() && map.loaded?.() !== false) {
// setProjection isStyleLoaded
await new Promise((r) => requestAnimationFrame(r))
if (map.isStyleLoaded?.()) return true
}
} catch (_) {
/* ignore */
}
await new Promise((r) => {
const onIdle = () => {
map.off?.('idle', onIdle)
r()
}
try {
map.once?.('idle', onIdle)
setTimeout(r, 50)
} catch (_) {
setTimeout(r, 50)
}
})
}
return !!map.isStyleLoaded?.()
}
function scheduleRouteSync({ fit = false } = {}) { function scheduleRouteSync({ fit = false } = {}) {
const map = routeMapInstance const map = routeMapInstance
if (!map) return if (!map) return
@ -721,47 +750,18 @@ function scheduleRouteSync({ fit = false } = {}) {
} catch (_) { } catch (_) {
/* ignore */ /* ignore */
} }
// projection/style source
if (!ensureTaskLayers(map)) return
syncRouteLayer({ fit: shouldFit }) syncRouteLayer({ fit: shouldFit })
} }
// rAF hidden / resize + fit
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
run(fit) run(fit)
if (!fit) return if (!fit) return
// ResizeObserver/ fit 0
requestAnimationFrame(() => run(true)) requestAnimationFrame(() => run(true))
}) })
}) })
} }
function onRouteMapClick(e) {
if (!routeMapAttached || !routeMapInstance) return
const hit = routeMapInstance.queryRenderedFeatures(e.point, {
layers: [WP_CIRCLE, WP_LABEL].filter((id) => routeMapInstance.getLayer(id)),
})
if (hit[0]?.properties?.index != null) {
openWaypointEditor(Number(hit[0].properties.index))
return
}
if (!validRouteDefaults()) return
const { lng, lat } = e.lngLat
routePoints.value.push({
longitude: lng,
latitude: lat,
altitude: Number(routeForm.altitude),
speed: Number(routeForm.speed),
turnMode: routeForm.turnMode,
})
routeCursor.value = formatCoord(lng, lat)
routeFormError.value = ''
syncRouteLayer()
}
function onRouteMapMove(e) {
if (!routeMapAttached) return
routeCursor.value = formatCoord(e.lngLat.lng, e.lngLat.lat)
}
async function attachRouteMap() { async function attachRouteMap() {
await nextTick() await nextTick()
const host = routeMapEl.value const host = routeMapEl.value
@ -774,7 +774,6 @@ async function attachRouteMap() {
return return
} }
// display:none 宿
await waitForMapHostSize(host) await waitForMapHostSize(host)
routeMapInstance = map routeMapInstance = map
@ -783,34 +782,65 @@ async function attachRouteMap() {
routeMapAttached = true routeMapAttached = true
routeMapReady.value = false routeMapReady.value = false
// setProjection(mercator) style
await waitForMapStyleReady(map)
const boot = () => { const boot = () => {
pendingStyleHandler = null
if (!routeMapAttached || routeMapInstance !== map) return if (!routeMapAttached || routeMapInstance !== map) return
clearTaskLayers(map) clearTaskLayers(map)
if (!ensureTaskLayers(map)) return if (!ensureTaskLayers(map)) return
map.off('click', onRouteMapClick)
map.off('mousemove', onRouteMapMove)
map.on('click', onRouteMapClick) map.on('click', onRouteMapClick)
map.on('mousemove', onRouteMapMove) map.on('mousemove', onRouteMapMove)
const c = map.getCenter() const c = map.getCenter()
routeCursor.value = formatCoord(c.lng, c.lat) routeCursor.value = formatCoord(c.lng, c.lat)
routeMapReady.value = true routeMapReady.value = true
const shouldFit = pendingFitRoute const shouldFit = pendingFitRoute
pendingFitRoute = false
scheduleRouteSync({ fit: shouldFit })
// pendingFitRoute boot style fit
if (shouldFit) pendingFitRoute = false
scheduleRouteSync({ fit: shouldFit || routePoints.value.length > 0 })
}
if (pendingStyleHandler) {
map.off('load', pendingStyleHandler)
map.off('style.load', pendingStyleHandler)
pendingStyleHandler = null
}
if (styleRebuildHandler) {
map.off('style.load', styleRebuildHandler)
styleRebuildHandler = null
} }
// style /projection线
styleRebuildHandler = () => {
if (!routeMapAttached || routeMapInstance !== map) return
boot()
}
map.on('style.load', styleRebuildHandler)
if (map.isStyleLoaded()) { if (map.isStyleLoaded()) {
boot() boot()
} else { } else {
pendingStyleHandler = boot
pendingStyleHandler = () => {
pendingStyleHandler = null
boot()
}
map.once('load', pendingStyleHandler) map.once('load', pendingStyleHandler)
map.once('style.load', pendingStyleHandler)
} }
} }
function teardownRouteMap() { function teardownRouteMap() {
if (routeMapInstance && pendingStyleHandler) { if (routeMapInstance && pendingStyleHandler) {
routeMapInstance.off('load', pendingStyleHandler) routeMapInstance.off('load', pendingStyleHandler)
routeMapInstance.off('style.load', pendingStyleHandler)
pendingStyleHandler = null pendingStyleHandler = null
} }
if (routeMapInstance && styleRebuildHandler) {
routeMapInstance.off('style.load', styleRebuildHandler)
styleRebuildHandler = null
}
if (routeMapInstance) { if (routeMapInstance) {
routeMapInstance.off('click', onRouteMapClick) routeMapInstance.off('click', onRouteMapClick)
routeMapInstance.off('mousemove', onRouteMapMove) routeMapInstance.off('mousemove', onRouteMapMove)
@ -818,7 +848,6 @@ function teardownRouteMap() {
} }
if (routeMapAttached) { if (routeMapAttached) {
mapHelper.restoreMapContainer() mapHelper.restoreMapContainer()
// globe + pitch 0 3D pitch 45
try { try {
mapHelper.getMap()?.setProjection?.('globe') mapHelper.getMap()?.setProjection?.('globe')
} catch (_) { } catch (_) {
@ -831,6 +860,35 @@ function teardownRouteMap() {
pendingFitRoute = false pendingFitRoute = false
} }
function onRouteMapClick(e) {
if (!routeMapAttached || !routeMapInstance) return
const hit = routeMapInstance.queryRenderedFeatures(e.point, {
layers: [WP_CIRCLE, WP_LABEL].filter((id) => routeMapInstance.getLayer(id)),
})
if (hit[0]?.properties?.index != null) {
openWaypointEditor(Number(hit[0].properties.index))
return
}
if (!validRouteDefaults()) return
const { lng, lat } = e.lngLat
routePoints.value.push({
longitude: lng,
latitude: lat,
altitude: Number(routeForm.altitude),
speed: Number(routeForm.speed),
turnMode: routeForm.turnMode,
})
routeCursor.value = formatCoord(lng, lat)
routeFormError.value = ''
syncRouteLayer()
}
function onRouteMapMove(e) {
if (!routeMapAttached) return
routeCursor.value = formatCoord(e.lngLat.lng, e.lngLat.lat)
}
function openRouteEditor() { function openRouteEditor() {
resetRouteEditor() resetRouteEditor()
routeEditorOpen.value = true routeEditorOpen.value = true

Loading…
Cancel
Save