fix: guard deck layer arrays against null ids
This commit is contained in:
부모
b883c4113b
커밋
ccf3f2361f
@ -195,6 +195,33 @@ function getLayerId(value: unknown): string | null {
|
|||||||
return typeof candidate === "string" ? candidate : null;
|
return typeof candidate === "string" ? candidate : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeDeckLayerList(value: unknown): unknown[] {
|
||||||
|
if (!Array.isArray(value)) return [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const out: unknown[] = [];
|
||||||
|
let dropped = 0;
|
||||||
|
|
||||||
|
for (const layer of value) {
|
||||||
|
const layerId = getLayerId(layer);
|
||||||
|
if (!layerId) {
|
||||||
|
dropped += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seen.has(layerId)) {
|
||||||
|
dropped += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(layerId);
|
||||||
|
out.push(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropped > 0 && import.meta.env.DEV) {
|
||||||
|
console.warn(`Sanitized deck layer list: dropped ${dropped} invalid/duplicate entries`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAngleDeg(value: number, offset = 0): number {
|
function normalizeAngleDeg(value: number, offset = 0): number {
|
||||||
const v = value + offset;
|
const v = value + offset;
|
||||||
return ((v % 360) + 360) % 360;
|
return ((v % 360) + 360) % 360;
|
||||||
@ -3674,8 +3701,10 @@ export function Map3D({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizedLayers = sanitizeDeckLayerList(layers);
|
||||||
|
|
||||||
const deckProps = {
|
const deckProps = {
|
||||||
layers,
|
layers: normalizedLayers,
|
||||||
getTooltip:
|
getTooltip:
|
||||||
projection === "globe"
|
projection === "globe"
|
||||||
? undefined
|
? undefined
|
||||||
@ -3771,8 +3800,40 @@ export function Map3D({
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (projection === "globe") globeDeckLayerRef.current?.setProps(deckProps);
|
const safeDeckProps = { ...deckProps, layers: normalizedLayers };
|
||||||
else overlayRef.current?.setProps(deckProps as unknown as never);
|
const fallbackDeckProps = { ...safeDeckProps, layers: [] as unknown[] };
|
||||||
|
const applyDeckProps = () => {
|
||||||
|
if (projection === "globe") {
|
||||||
|
const target = globeDeckLayerRef.current;
|
||||||
|
if (!target) return;
|
||||||
|
try {
|
||||||
|
target.setProps(safeDeckProps as never);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to apply deck props on globe overlay. Falling back to empty deck layer set.", e);
|
||||||
|
try {
|
||||||
|
target.setProps(fallbackDeckProps as never);
|
||||||
|
} catch {
|
||||||
|
// Ignore secondary failure; rendering will recover on next update.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = overlayRef.current;
|
||||||
|
if (!target) return;
|
||||||
|
try {
|
||||||
|
target.setProps(safeDeckProps as unknown as never);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to apply deck props on mercator overlay. Falling back to empty deck layer set.", e);
|
||||||
|
try {
|
||||||
|
target.setProps(fallbackDeckProps as unknown as never);
|
||||||
|
} catch {
|
||||||
|
// Ignore secondary failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
applyDeckProps();
|
||||||
}, [
|
}, [
|
||||||
projection,
|
projection,
|
||||||
shipData,
|
shipData,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class MatrixView extends View<MatrixViewState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const IDENTITY_4x4: number[] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
const IDENTITY_4x4: number[] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||||
|
type DeckLayerList = NonNullable<DeckProps<MatrixView[]>["layers"]>;
|
||||||
|
|
||||||
function readMat4(m: ArrayLike<number>): number[] {
|
function readMat4(m: ArrayLike<number>): number[] {
|
||||||
const out = new Array<number>(16);
|
const out = new Array<number>(16);
|
||||||
@ -40,6 +41,25 @@ function mat4Changed(a: number[] | undefined, b: ArrayLike<number>): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDeckLayerId(value: unknown): string | null {
|
||||||
|
if (!value || typeof value !== "object") return null;
|
||||||
|
const candidate = (value as { id?: unknown }).id;
|
||||||
|
return typeof candidate === "string" ? candidate : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeDeckLayers(value: unknown): DeckLayerList {
|
||||||
|
if (!Array.isArray(value)) return [] as DeckLayerList;
|
||||||
|
const out: DeckLayerList = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const item of value) {
|
||||||
|
const layerId = getDeckLayerId(item);
|
||||||
|
if (!layerId || seen.has(layerId)) continue;
|
||||||
|
seen.add(layerId);
|
||||||
|
out.push(item as DeckLayerList[number]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface {
|
export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface {
|
||||||
id: string;
|
id: string;
|
||||||
type = "custom" as const;
|
type = "custom" as const;
|
||||||
@ -67,7 +87,8 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProps(next: Partial<DeckProps<MatrixView[]>>) {
|
setProps(next: Partial<DeckProps<MatrixView[]>>) {
|
||||||
this._deckProps = { ...this._deckProps, ...next };
|
const normalized = next.layers ? { ...next, layers: sanitizeDeckLayers(next.layers) } : next;
|
||||||
|
this._deckProps = { ...this._deckProps, ...normalized };
|
||||||
if (this._deck) this._deck.setProps(this._deckProps as DeckProps<MatrixView[]>);
|
if (this._deck) this._deck.setProps(this._deckProps as DeckProps<MatrixView[]>);
|
||||||
this._map?.triggerRepaint();
|
this._map?.triggerRepaint();
|
||||||
}
|
}
|
||||||
@ -80,17 +101,20 @@ export class MaplibreDeckCustomLayer implements maplibregl.CustomLayerInterface
|
|||||||
// Re-attached after a style change; keep the existing Deck instance so we don't reuse
|
// Re-attached after a style change; keep the existing Deck instance so we don't reuse
|
||||||
// finalized Layer objects (Deck does not allow that).
|
// finalized Layer objects (Deck does not allow that).
|
||||||
this._lastMvp = undefined;
|
this._lastMvp = undefined;
|
||||||
this._deck.setProps({
|
const nextDeckProps = {
|
||||||
...this._deckProps,
|
...this._deckProps,
|
||||||
|
layers: sanitizeDeckLayers(this._deckProps.layers),
|
||||||
canvas: map.getCanvas(),
|
canvas: map.getCanvas(),
|
||||||
// Ensure any pending redraw requests trigger a map repaint again.
|
// Ensure any pending redraw requests trigger a map repaint again.
|
||||||
_customRender: () => map.triggerRepaint(),
|
_customRender: () => map.triggerRepaint(),
|
||||||
} as DeckProps<MatrixView[]>);
|
};
|
||||||
|
this._deck.setProps(nextDeckProps as DeckProps<MatrixView[]>);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deck = new Deck<MatrixView[]>({
|
const deck = new Deck<MatrixView[]>({
|
||||||
...this._deckProps,
|
...this._deckProps,
|
||||||
|
layers: sanitizeDeckLayers(this._deckProps.layers),
|
||||||
// Share MapLibre's WebGL context + canvas (single context).
|
// Share MapLibre's WebGL context + canvas (single context).
|
||||||
gl: gl as WebGL2RenderingContext,
|
gl: gl as WebGL2RenderingContext,
|
||||||
canvas: map.getCanvas(),
|
canvas: map.getCanvas(),
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user