fix(globe): keep deck instance across style reloads

This commit is contained in:
htlee 2026-02-15 12:36:25 +09:00
부모 b0d51a9490
커밋 0172ed6134
2개의 변경된 파일88개의 추가작업 그리고 10개의 파일을 삭제

파일 보기

@ -601,14 +601,21 @@ export function Map3D({
}); });
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
controller.abort(); controller.abort();
if (map) { // If we are unmounting, ensure the globe Deck instance is finalized (style reload would keep it alive).
map.remove(); try {
map = null; globeDeckLayerRef.current?.requestFinalize();
} } catch {
// ignore
}
if (map) {
map.remove();
map = null;
}
if (overlay) { if (overlay) {
overlay.finalize(); overlay.finalize();
overlay = null; overlay = null;
@ -670,6 +677,7 @@ export function Map3D({
const globeLayer = globeDeckLayerRef.current; const globeLayer = globeDeckLayerRef.current;
if (globeLayer && map.getLayer(globeLayer.id)) { if (globeLayer && map.getLayer(globeLayer.id)) {
try { try {
globeLayer.requestFinalize();
map.removeLayer(globeLayer.id); map.removeLayer(globeLayer.id);
} catch { } catch {
// ignore // ignore
@ -728,6 +736,36 @@ export function Map3D({
}; };
}, [baseMap]); }, [baseMap]);
// Globe rendering + bathymetry tuning.
// Some terrain/hillshade/extrusion effects look unstable under globe and can occlude Deck overlays.
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const apply = () => {
if (!map.isStyleLoaded()) return;
const disableBathy3D = projection === "globe" && baseMap === "enhanced";
const vis = disableBathy3D ? "none" : "visible";
for (const id of ["bathymetry-extrusion", "bathymetry-hillshade"]) {
try {
if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", vis);
} catch {
// ignore
}
}
};
if (map.isStyleLoaded()) apply();
map.on("style.load", apply);
return () => {
try {
map.off("style.load", apply);
} catch {
// ignore
}
};
}, [projection, baseMap]);
// seamark toggle // seamark toggle
useEffect(() => { useEffect(() => {
const map = mapRef.current; const map = mapRef.current;

파일 보기

@ -50,6 +50,7 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
private _deckProps: Partial<DeckProps<MatrixView[]>> = {}; private _deckProps: Partial<DeckProps<MatrixView[]>> = {};
private _viewId: string; private _viewId: string;
private _lastMvp: number[] | undefined; private _lastMvp: number[] | undefined;
private _finalizeOnRemove: boolean = false;
constructor(opts: { id: string; viewId: string; deckProps?: Partial<DeckProps<MatrixView[]>> }) { constructor(opts: { id: string; viewId: string; deckProps?: Partial<DeckProps<MatrixView[]>> }) {
this.id = opts.id; this.id = opts.id;
@ -61,6 +62,10 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
return this._deck; return this._deck;
} }
requestFinalize() {
this._finalizeOnRemove = true;
}
setProps(next: Partial<DeckProps<MatrixView[]>>) { setProps(next: Partial<DeckProps<MatrixView[]>>) {
this._deckProps = { ...this._deckProps, ...next }; this._deckProps = { ...this._deckProps, ...next };
if (this._deck) this._deck.setProps(this._deckProps as DeckProps<MatrixView[]>); if (this._deck) this._deck.setProps(this._deckProps as DeckProps<MatrixView[]>);
@ -68,8 +73,22 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
} }
onAdd(map: maplibregl.Map, gl: WebGLRenderingContext | WebGL2RenderingContext): void { onAdd(map: maplibregl.Map, gl: WebGLRenderingContext | WebGL2RenderingContext): void {
this._finalizeOnRemove = false;
this._map = map; this._map = map;
if (this._deck) {
// Re-attached after a style change; keep the existing Deck instance so we don't reuse
// finalized Layer objects (Deck does not allow that).
this._lastMvp = undefined;
this._deck.setProps({
...this._deckProps,
canvas: map.getCanvas(),
// Ensure any pending redraw requests trigger a map repaint again.
_customRender: () => map.triggerRepaint(),
} as DeckProps<MatrixView[]>);
return;
}
const deck = new Deck<MatrixView[]>({ const deck = new Deck<MatrixView[]>({
...this._deckProps, ...this._deckProps,
// Share MapLibre's WebGL context + canvas (single context). // Share MapLibre's WebGL context + canvas (single context).
@ -104,15 +123,36 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
} }
onRemove(): void { onRemove(): void {
this._deck?.finalize(); const deck = this._deck;
this._deck = null; const map = this._map;
this._map = null; this._map = null;
this._lastMvp = undefined; this._lastMvp = undefined;
if (!deck) return;
if (this._finalizeOnRemove) {
deck.finalize();
this._deck = null;
return;
}
// Likely a base style swap; keep Deck instance alive and re-attach in onAdd().
// Disable repaint requests until we get re-attached.
try {
deck.setProps({ _customRender: () => void 0 } as Partial<DeckProps<MatrixView[]>>);
} catch {
// ignore
}
try {
map?.triggerRepaint();
} catch {
// ignore
}
} }
render(_gl: WebGLRenderingContext | WebGL2RenderingContext, options: maplibregl.CustomRenderMethodInput): void { render(_gl: WebGLRenderingContext | WebGL2RenderingContext, options: maplibregl.CustomRenderMethodInput): void {
const deck = this._deck; const deck = this._deck;
if (!deck) return; if (!deck || !deck.isInitialized) return;
// MapLibre gives us a world->clip matrix for the current projection (mercator/globe). // MapLibre gives us a world->clip matrix for the current projection (mercator/globe).
// For globe, this matrix expects unit-sphere world coordinates (see MapLibre's globe transform). // For globe, this matrix expects unit-sphere world coordinates (see MapLibre's globe transform).