Fix globe zones line-width expression and enforce map layer ordering

This commit is contained in:
htlee 2026-02-15 15:43:36 +09:00
부모 bb5fd793d8
커밋 e504dbebca

파일 보기

@ -83,6 +83,44 @@ function makeSetSignature(values: Set<number>) {
return Array.from(values).sort((a, b) => a - b).join(",");
}
function toTextValue(value: unknown): string {
if (value == null) return "";
return String(value).trim();
}
function getZoneIdFromProps(props: Record<string, unknown> | null | undefined): string {
const safeProps = props || {};
const candidates = [
"zoneId",
"zone_id",
"zoneIdNo",
"zoneKey",
"zoneCode",
"ZONE_ID",
"ZONECODE",
"id",
];
for (const key of candidates) {
const value = toTextValue(safeProps[key]);
if (value) return value;
}
return "";
}
function getZoneDisplayNameFromProps(props: Record<string, unknown> | null | undefined): string {
const safeProps = props || {};
const nameCandidates = ["zoneName", "zoneLabel", "NAME", "name", "ZONE_NM", "label"];
for (const key of nameCandidates) {
const name = toTextValue(safeProps[key]);
if (name) return name;
}
const zoneId = getZoneIdFromProps(safeProps);
if (!zoneId) return "수역";
return ZONE_META[(zoneId as ZoneId)]?.name || `수역 ${zoneId}`;
}
const SHIP_ICON_MAPPING = {
ship: {
x: 0,
@ -933,6 +971,37 @@ export function Map3D({
if (hoveredDeckFleetOwnerKey) keys.add(hoveredDeckFleetOwnerKey);
return keys;
}, [hoveredFleetOwnerKey, hoveredDeckFleetOwnerKey]);
const reorderGlobeFeatureLayers = useCallback(() => {
const map = mapRef.current;
if (!map || projectionRef.current !== "globe") return;
if (projectionBusyRef.current) return;
const ordering = [
"pair-lines-ml",
"fc-lines-ml",
"pair-range-ml",
"fleet-circles-ml-fill",
"fleet-circles-ml",
"zones-fill",
"zones-line",
"zones-label",
"ships-globe-halo",
"ships-globe-outline",
"ships-globe",
];
for (const layerId of ordering) {
try {
if (map.getLayer(layerId)) map.moveLayer(layerId);
} catch {
// ignore
}
}
kickRepaint(map);
}, []);
const effectiveHoveredPairMmsiSet = useMemo(
() => mergeNumberSets(hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef),
[hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef],
@ -1515,6 +1584,7 @@ export function Map3D({
// MapLibre may not schedule a frame immediately after projection swaps if the map is idle.
// Kick a few repaints so overlay sources (ships/zones) appear instantly.
reorderGlobeFeatureLayers();
kickRepaint(map);
try {
map.resize();
@ -1545,7 +1615,7 @@ export function Map3D({
if (settleCleanup) settleCleanup();
if (isTransition) setProjectionLoading(false);
};
}, [projection, clearGlobeNativeLayers, ensureMercatorOverlay, removeLayerIfExists, setProjectionLoading]);
}, [projection, clearGlobeNativeLayers, ensureMercatorOverlay, removeLayerIfExists, reorderGlobeFeatureLayers, setProjectionLoading]);
// Base map toggle
useEffect(() => {
@ -1741,6 +1811,19 @@ export function Map3D({
hoveredZoneId !== null
? (["==", ["to-string", ["coalesce", ["get", "zoneId"], ""]], hoveredZoneId] as unknown[])
: false;
const zoneLineWidthExpr = hoveredZoneId
? ([
"interpolate",
["linear"],
["zoom"],
4,
["case", zoneMatchExpr, 1.6, 0.8],
10,
["case", zoneMatchExpr, 2.0, 1.4],
14,
["case", zoneMatchExpr, 2.8, 2.1],
] as unknown as never)
: (["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.4, 14, 2.1] as never);
if (map.getLayer(fillId)) {
try {
@ -1772,18 +1855,7 @@ export function Map3D({
// ignore
}
try {
map.setPaintProperty(
lineId,
"line-width",
hoveredZoneId
? ([
"case",
zoneMatchExpr,
["interpolate", ["linear"], ["zoom"], 4, 1.6, 10, 2.0, 14, 2.8],
["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.4, 14, 2.1],
] as never)
: (["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.4, 14, 2.1] as never),
);
map.setPaintProperty(lineId, "line-width", zoneLineWidthExpr);
} catch {
// ignore
}
@ -1825,14 +1897,7 @@ export function Map3D({
"line-opacity": hoveredZoneId
? (["case", zoneMatchExpr, 1, 0.85] as never)
: 0.85,
"line-width": hoveredZoneId
? ([
"case",
zoneMatchExpr,
["interpolate", ["linear"], ["zoom"], 4, 1.6, 10, 2.0, 14, 2.8],
["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.4, 14, 2.1],
] as unknown as number[])
: (["interpolate", ["linear"], ["zoom"], 4, 0.8, 10, 1.4, 14, 2.1] as never),
"line-width": zoneLineWidthExpr,
},
layout: { visibility },
} as unknown as LayerSpecification,
@ -1870,6 +1935,7 @@ export function Map3D({
} catch (e) {
console.warn("Zones layer setup failed:", e);
} finally {
reorderGlobeFeatureLayers();
kickRepaint(map);
}
};
@ -1878,7 +1944,7 @@ export function Map3D({
return () => {
stop();
};
}, [zones, overlays.zones, projection, baseMap, hoveredZoneId, mapSyncEpoch]);
}, [zones, overlays.zones, projection, baseMap, hoveredZoneId, mapSyncEpoch, reorderGlobeFeatureLayers]);
// Ships in globe mode: render with MapLibre symbol layers so icons can be aligned to the globe surface.
// Deck IconLayer billboards or uses a fixed plane, which looks like ships are pointing into the sky/ground on globe.
@ -1905,6 +1971,7 @@ export function Map3D({
} catch {
// ignore
}
reorderGlobeFeatureLayers();
kickRepaint(map);
};
@ -2338,6 +2405,7 @@ export function Map3D({
// Selection and highlight are now source-data driven.
bringShipLayersToFront();
reorderGlobeFeatureLayers();
kickRepaint(map);
};
@ -2345,7 +2413,19 @@ export function Map3D({
return () => {
stop();
};
}, [projection, settings.showShips, targets, legacyHits, selectedMmsi, hoveredMmsiSetRef, hoveredFleetMmsiSetRef, hoveredPairMmsiSetRef, isHighlightedMmsi, mapSyncEpoch]);
}, [
projection,
settings.showShips,
targets,
legacyHits,
selectedMmsi,
hoveredMmsiSetRef,
hoveredFleetMmsiSetRef,
hoveredPairMmsiSetRef,
isHighlightedMmsi,
mapSyncEpoch,
reorderGlobeFeatureLayers,
]);
// Globe ship click selection (MapLibre-native ships layer)
useEffect(() => {
@ -2516,6 +2596,7 @@ export function Map3D({
}
}
reorderGlobeFeatureLayers();
kickRepaint(map);
};
@ -2525,7 +2606,16 @@ export function Map3D({
stop();
remove();
};
}, [projection, overlays.pairLines, pairLinks, mapSyncEpoch, hoveredShipSignature, hoveredPairSignature, isHighlightedPair]);
}, [
projection,
overlays.pairLines,
pairLinks,
mapSyncEpoch,
hoveredShipSignature,
hoveredPairSignature,
isHighlightedPair,
reorderGlobeFeatureLayers,
]);
useEffect(() => {
const map = mapRef.current;
@ -2620,6 +2710,7 @@ export function Map3D({
}
}
reorderGlobeFeatureLayers();
kickRepaint(map);
};
@ -2629,7 +2720,15 @@ export function Map3D({
stop();
remove();
};
}, [projection, overlays.fcLines, fcLinks, mapSyncEpoch, hoveredShipSignature, isHighlightedMmsi]);
}, [
projection,
overlays.fcLines,
fcLinks,
mapSyncEpoch,
hoveredShipSignature,
isHighlightedMmsi,
reorderGlobeFeatureLayers,
]);
useEffect(() => {
const map = mapRef.current;
@ -2783,6 +2882,7 @@ export function Map3D({
}
}
reorderGlobeFeatureLayers();
kickRepaint(map);
};
@ -2802,6 +2902,7 @@ export function Map3D({
hoveredFleetOwnerKey,
isHighlightedFleet,
isHighlightedMmsi,
reorderGlobeFeatureLayers,
]);
useEffect(() => {
@ -2917,7 +3018,17 @@ export function Map3D({
stop();
remove();
};
}, [projection, overlays.pairRange, pairLinks, mapSyncEpoch, hoveredShipSignature, hoveredPairSignature, hoveredFleetSignature, isHighlightedPair]);
}, [
projection,
overlays.pairRange,
pairLinks,
mapSyncEpoch,
hoveredShipSignature,
hoveredPairSignature,
hoveredFleetSignature,
isHighlightedPair,
reorderGlobeFeatureLayers,
]);
const shipData = useMemo(() => {
return targets.filter((t) => isFiniteNumber(t.lat) && isFiniteNumber(t.lon) && isFiniteNumber(t.mmsi));
@ -3019,10 +3130,9 @@ export function Map3D({
});
}
const zoneLabel = String((props.zoneLabel ?? props.zoneName ?? "").toString());
const zoneLabel = getZoneDisplayNameFromProps(props);
if (zoneLabel) {
const zoneName = zoneLabel || ZONE_META[(String(props.zoneId ?? "") as ZoneId)]?.name || "수역";
return { html: `<div style="font-size: 12px; font-family: system-ui;">${zoneName}</div>` };
return { html: `<div style="font-size: 12px; font-family: system-ui;">${zoneLabel}</div>` };
}
return null;
@ -3174,7 +3284,7 @@ export function Map3D({
clearMapFleetHoverState();
setHoveredDeckMmsiSet((prev) => (prev.length === 0 ? prev : []));
setHoveredDeckPairMmsiSet((prev) => (prev.length === 0 ? prev : []));
const zoneId = String((props.zoneId ?? "").toString());
const zoneId = getZoneIdFromProps(props);
setHoveredZoneId(zoneId || null);
} else {
resetGlobeHoverStates();
@ -3761,8 +3871,8 @@ export function Map3D({
});
}
const p = obj.properties as { zoneName?: string; zoneLabel?: string } | undefined;
const label = p?.zoneName ?? p?.zoneLabel;
const p = obj.properties as Record<string, unknown> | undefined;
const label = getZoneDisplayNameFromProps(p);
if (label) return { text: label };
return null;
},