fix(map): harden globe projection switch and overlay teardown

This commit is contained in:
htlee 2026-02-15 14:27:08 +09:00
부모 15378ed7ff
커밋 0ffadb2e66

파일 보기

@ -126,6 +126,16 @@ function onMapStyleReady(map: maplibregl.Map, callback: () => void) {
}; };
} }
function extractProjectionType(map: maplibregl.Map): MapProjectionId | undefined {
const projection = map.getProjection?.();
if (!projection || typeof projection !== "object") return undefined;
const rawType = (projection as { type?: unknown; name?: unknown }).type ?? (projection as { type?: unknown; name?: unknown }).name;
if (rawType === "globe") return "globe";
if (rawType === "mercator") return "mercator";
return undefined;
}
const DEG2RAD = Math.PI / 180; const DEG2RAD = Math.PI / 180;
const clampNumber = (value: number, minValue: number, maxValue: number) => Math.max(minValue, Math.min(maxValue, value)); const clampNumber = (value: number, minValue: number, maxValue: number) => Math.max(minValue, Math.min(maxValue, value));
@ -724,9 +734,38 @@ export function Map3D({
let retries = 0; let retries = 0;
const maxRetries = 18; const maxRetries = 18;
const getCurrentProjection = () => { const disposeMercatorOverlay = () => {
const projectionConfig = map.getProjection?.(); const current = overlayRef.current;
return projectionConfig && "type" in projectionConfig ? (projectionConfig.type as MapProjectionId | undefined) : undefined; if (!current) return;
try {
map.removeControl(current as never);
} catch {
// ignore
}
try {
current.finalize();
} catch {
// ignore
}
overlayRef.current = null;
};
const disposeGlobeDeckLayer = () => {
const current = globeDeckLayerRef.current;
if (!current) return;
if (map.getLayer(current.id)) {
try {
map.removeLayer(current.id);
} catch {
// ignore
}
}
try {
current.requestFinalize();
} catch {
// ignore
}
globeDeckLayerRef.current = null;
}; };
const syncProjectionAndDeck = () => { const syncProjectionAndDeck = () => {
@ -741,11 +780,21 @@ export function Map3D({
} }
const next = projection; const next = projection;
const currentProjection = extractProjectionType(map);
const shouldSwitchProjection = currentProjection !== next;
try { try {
if (getCurrentProjection() !== next) { if (shouldSwitchProjection) {
map.setProjection({ type: next }); map.setProjection({ type: next });
} }
map.setRenderWorldCopies(next !== "globe"); map.setRenderWorldCopies(next !== "globe");
if (shouldSwitchProjection) {
if (!cancelled && retries < maxRetries) {
retries += 1;
window.requestAnimationFrame(() => syncProjectionAndDeck());
return;
}
}
} catch (e) { } catch (e) {
if (!cancelled && retries < maxRetries) { if (!cancelled && retries < maxRetries) {
retries += 1; retries += 1;
@ -758,25 +807,12 @@ export function Map3D({
const oldOverlay = overlayRef.current; const oldOverlay = overlayRef.current;
if (projection === "globe" && oldOverlay) { if (projection === "globe" && oldOverlay) {
// Globe mode uses custom MapLibre deck layers and should fully replace Mercator overlays. // Globe mode uses custom MapLibre deck layers and should fully replace Mercator overlays.
try { disposeMercatorOverlay();
oldOverlay.finalize();
} catch {
// ignore
}
overlayRef.current = null;
} }
if (projection === "globe") { if (projection === "globe") {
// Ensure any stale layer from old mode is dropped then re-added on this projection. // Start with a clean globe Deck layer state to avoid partially torn-down renders.
if (globeDeckLayerRef.current) { disposeGlobeDeckLayer();
try {
if (map.getLayer(globeDeckLayerRef.current.id)) {
map.removeLayer(globeDeckLayerRef.current.id);
}
} catch {
// ignore
}
}
if (!globeDeckLayerRef.current) { if (!globeDeckLayerRef.current) {
globeDeckLayerRef.current = new MaplibreDeckCustomLayer({ globeDeckLayerRef.current = new MaplibreDeckCustomLayer({
@ -801,15 +837,7 @@ export function Map3D({
} }
} else { } else {
// Tear down globe custom layer (if present), restore MapboxOverlay interleaved. // Tear down globe custom layer (if present), restore MapboxOverlay interleaved.
const globeLayer = globeDeckLayerRef.current; disposeGlobeDeckLayer();
if (globeLayer && map.getLayer(globeLayer.id)) {
try {
globeLayer.requestFinalize();
map.removeLayer(globeLayer.id);
} catch {
// ignore
}
}
if (!overlayRef.current) { if (!overlayRef.current) {
try { try {