fix(map3d): sync mercator restore on globe toggle

This commit is contained in:
htlee 2026-02-15 14:33:50 +09:00
부모 6f7a82af4c
커밋 1225d5c54c

파일 보기

@ -9,7 +9,7 @@ import maplibregl, {
type StyleSpecification,
type VectorSourceSpecification,
} from "maplibre-gl";
import { useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { AisTarget } from "../../entities/aisTarget/model/types";
import type { LegacyVesselInfo } from "../../entities/legacyVessel/model/types";
import type { ZonesGeoJson } from "../../entities/zone/api/useZones";
@ -580,6 +580,67 @@ export function Map3D({
projectionRef.current = projection;
}, [projection]);
const removeLayerIfExists = useCallback((map: maplibregl.Map, layerId: string) => {
try {
if (map.getLayer(layerId)) {
map.removeLayer(layerId);
}
} catch {
// ignore
}
}, []);
const removeSourceIfExists = useCallback((map: maplibregl.Map, sourceId: string) => {
try {
if (map.getSource(sourceId)) {
map.removeSource(sourceId);
}
} catch {
// ignore
}
}, []);
const ensureMercatorOverlay = useCallback(() => {
const map = mapRef.current;
if (!map) return null;
if (overlayRef.current) return overlayRef.current;
try {
const next = new MapboxOverlay({ interleaved: true, layers: [] } as unknown as never);
map.addControl(next);
overlayRef.current = next;
return next;
} catch (e) {
console.warn("Deck overlay create failed:", e);
return null;
}
}, []);
const clearGlobeNativeLayers = useCallback(() => {
const map = mapRef.current;
if (!map) return;
const layerIds = [
"ships-globe-halo",
"ships-globe-outline",
"ships-globe",
"pair-lines-ml",
"fc-lines-ml",
"fleet-circles-ml",
"pair-range-ml",
"deck-globe",
];
for (const id of layerIds) {
removeLayerIfExists(map, id);
}
const sourceIds = ["ships-globe-src", "pair-lines-ml-src", "fc-lines-ml-src", "fleet-circles-ml-src", "pair-range-ml-src"];
for (const id of sourceIds) {
removeSourceIfExists(map, id);
}
}, [removeLayerIfExists, removeSourceIfExists]);
// Init MapLibre + Deck.gl (single WebGL context via MapboxOverlay)
useEffect(() => {
if (!containerRef.current || mapRef.current) return;
@ -758,13 +819,7 @@ export function Map3D({
const disposeGlobeDeckLayer = () => {
const current = globeDeckLayerRef.current;
if (!current) return;
if (map.getLayer(current.id)) {
try {
map.removeLayer(current.id);
} catch {
// ignore
}
}
removeLayerIfExists(map, current.id);
try {
current.requestFinalize();
} catch {
@ -790,8 +845,10 @@ export function Map3D({
if (projection === "globe") {
disposeMercatorOverlay();
clearGlobeNativeLayers();
} else {
disposeGlobeDeckLayer();
clearGlobeNativeLayers();
}
try {
@ -842,15 +899,7 @@ export function Map3D({
// Tear down globe custom layer (if present), restore MapboxOverlay interleaved.
disposeGlobeDeckLayer();
if (!overlayRef.current) {
try {
const next = new MapboxOverlay({ interleaved: true, layers: [] } as unknown as never);
map.addControl(next);
overlayRef.current = next;
} catch (e) {
console.warn("Deck overlay create failed:", e);
}
}
ensureMercatorOverlay();
}
// MapLibre may not schedule a frame immediately after projection swaps if the map is idle.
@ -876,7 +925,7 @@ export function Map3D({
return () => {
cancelled = true;
};
}, [projection]);
}, [projection, clearGlobeNativeLayers, ensureMercatorOverlay, removeLayerIfExists]);
// Base map toggle
useEffect(() => {
@ -1333,7 +1382,7 @@ export function Map3D({
"icon-rotate": ["to-number", ["get", "cog"], 0],
// Keep the icon on the sea surface.
"icon-rotation-alignment": "map",
"icon-pitch-alignment": "viewport",
"icon-pitch-alignment": "map",
},
paint: {
"icon-color": ["coalesce", ["get", "shipColor"], "#64748b"] as never,
@ -1830,7 +1879,19 @@ export function Map3D({
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const deckTarget = projection === "globe" ? globeDeckLayerRef.current : overlayRef.current;
let deckTarget = projection === "globe" ? globeDeckLayerRef.current : overlayRef.current;
if (projection === "mercator") {
if (!deckTarget) deckTarget = ensureMercatorOverlay();
if (!deckTarget) return;
try {
deckTarget.setProps({ layers: [] } as never);
} catch {
// ignore
}
} else if (!deckTarget && projection === "globe") {
return;
}
if (!deckTarget) return;
const overlayParams = projection === "globe" ? GLOBE_OVERLAY_PARAMS : DEPTH_DISABLED_PARAMS;
@ -2068,6 +2129,7 @@ export function Map3D({
fcDashed,
fleetCircles,
mapSyncEpoch,
ensureMercatorOverlay,
]);
return <div ref={containerRef} style={{ width: "100%", height: "100%" }} />;