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