Fix globe zones line-width expression and enforce map layer ordering
This commit is contained in:
부모
bb5fd793d8
커밋
e504dbebca
@ -83,6 +83,44 @@ function makeSetSignature(values: Set<number>) {
|
|||||||
return Array.from(values).sort((a, b) => a - b).join(",");
|
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 = {
|
const SHIP_ICON_MAPPING = {
|
||||||
ship: {
|
ship: {
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -933,6 +971,37 @@ export function Map3D({
|
|||||||
if (hoveredDeckFleetOwnerKey) keys.add(hoveredDeckFleetOwnerKey);
|
if (hoveredDeckFleetOwnerKey) keys.add(hoveredDeckFleetOwnerKey);
|
||||||
return keys;
|
return keys;
|
||||||
}, [hoveredFleetOwnerKey, hoveredDeckFleetOwnerKey]);
|
}, [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(
|
const effectiveHoveredPairMmsiSet = useMemo(
|
||||||
() => mergeNumberSets(hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef),
|
() => mergeNumberSets(hoveredPairMmsiSetRef, hoveredDeckPairMmsiSetRef),
|
||||||
[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.
|
// 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.
|
// Kick a few repaints so overlay sources (ships/zones) appear instantly.
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
try {
|
try {
|
||||||
map.resize();
|
map.resize();
|
||||||
@ -1545,7 +1615,7 @@ export function Map3D({
|
|||||||
if (settleCleanup) settleCleanup();
|
if (settleCleanup) settleCleanup();
|
||||||
if (isTransition) setProjectionLoading(false);
|
if (isTransition) setProjectionLoading(false);
|
||||||
};
|
};
|
||||||
}, [projection, clearGlobeNativeLayers, ensureMercatorOverlay, removeLayerIfExists, setProjectionLoading]);
|
}, [projection, clearGlobeNativeLayers, ensureMercatorOverlay, removeLayerIfExists, reorderGlobeFeatureLayers, setProjectionLoading]);
|
||||||
|
|
||||||
// Base map toggle
|
// Base map toggle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1741,6 +1811,19 @@ export function Map3D({
|
|||||||
hoveredZoneId !== null
|
hoveredZoneId !== null
|
||||||
? (["==", ["to-string", ["coalesce", ["get", "zoneId"], ""]], hoveredZoneId] as unknown[])
|
? (["==", ["to-string", ["coalesce", ["get", "zoneId"], ""]], hoveredZoneId] as unknown[])
|
||||||
: false;
|
: 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)) {
|
if (map.getLayer(fillId)) {
|
||||||
try {
|
try {
|
||||||
@ -1772,18 +1855,7 @@ export function Map3D({
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
map.setPaintProperty(
|
map.setPaintProperty(lineId, "line-width", zoneLineWidthExpr);
|
||||||
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),
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -1825,14 +1897,7 @@ export function Map3D({
|
|||||||
"line-opacity": hoveredZoneId
|
"line-opacity": hoveredZoneId
|
||||||
? (["case", zoneMatchExpr, 1, 0.85] as never)
|
? (["case", zoneMatchExpr, 1, 0.85] as never)
|
||||||
: 0.85,
|
: 0.85,
|
||||||
"line-width": hoveredZoneId
|
"line-width": zoneLineWidthExpr,
|
||||||
? ([
|
|
||||||
"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),
|
|
||||||
},
|
},
|
||||||
layout: { visibility },
|
layout: { visibility },
|
||||||
} as unknown as LayerSpecification,
|
} as unknown as LayerSpecification,
|
||||||
@ -1870,6 +1935,7 @@ export function Map3D({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Zones layer setup failed:", e);
|
console.warn("Zones layer setup failed:", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1878,7 +1944,7 @@ export function Map3D({
|
|||||||
return () => {
|
return () => {
|
||||||
stop();
|
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.
|
// 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.
|
// 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 {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2338,6 +2405,7 @@ export function Map3D({
|
|||||||
|
|
||||||
// Selection and highlight are now source-data driven.
|
// Selection and highlight are now source-data driven.
|
||||||
bringShipLayersToFront();
|
bringShipLayersToFront();
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2345,7 +2413,19 @@ export function Map3D({
|
|||||||
return () => {
|
return () => {
|
||||||
stop();
|
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)
|
// Globe ship click selection (MapLibre-native ships layer)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -2516,6 +2596,7 @@ export function Map3D({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2525,7 +2606,16 @@ export function Map3D({
|
|||||||
stop();
|
stop();
|
||||||
remove();
|
remove();
|
||||||
};
|
};
|
||||||
}, [projection, overlays.pairLines, pairLinks, mapSyncEpoch, hoveredShipSignature, hoveredPairSignature, isHighlightedPair]);
|
}, [
|
||||||
|
projection,
|
||||||
|
overlays.pairLines,
|
||||||
|
pairLinks,
|
||||||
|
mapSyncEpoch,
|
||||||
|
hoveredShipSignature,
|
||||||
|
hoveredPairSignature,
|
||||||
|
isHighlightedPair,
|
||||||
|
reorderGlobeFeatureLayers,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
@ -2620,6 +2710,7 @@ export function Map3D({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2629,7 +2720,15 @@ export function Map3D({
|
|||||||
stop();
|
stop();
|
||||||
remove();
|
remove();
|
||||||
};
|
};
|
||||||
}, [projection, overlays.fcLines, fcLinks, mapSyncEpoch, hoveredShipSignature, isHighlightedMmsi]);
|
}, [
|
||||||
|
projection,
|
||||||
|
overlays.fcLines,
|
||||||
|
fcLinks,
|
||||||
|
mapSyncEpoch,
|
||||||
|
hoveredShipSignature,
|
||||||
|
isHighlightedMmsi,
|
||||||
|
reorderGlobeFeatureLayers,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
@ -2783,6 +2882,7 @@ export function Map3D({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reorderGlobeFeatureLayers();
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2802,6 +2902,7 @@ export function Map3D({
|
|||||||
hoveredFleetOwnerKey,
|
hoveredFleetOwnerKey,
|
||||||
isHighlightedFleet,
|
isHighlightedFleet,
|
||||||
isHighlightedMmsi,
|
isHighlightedMmsi,
|
||||||
|
reorderGlobeFeatureLayers,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -2917,7 +3018,17 @@ export function Map3D({
|
|||||||
stop();
|
stop();
|
||||||
remove();
|
remove();
|
||||||
};
|
};
|
||||||
}, [projection, overlays.pairRange, pairLinks, mapSyncEpoch, hoveredShipSignature, hoveredPairSignature, hoveredFleetSignature, isHighlightedPair]);
|
}, [
|
||||||
|
projection,
|
||||||
|
overlays.pairRange,
|
||||||
|
pairLinks,
|
||||||
|
mapSyncEpoch,
|
||||||
|
hoveredShipSignature,
|
||||||
|
hoveredPairSignature,
|
||||||
|
hoveredFleetSignature,
|
||||||
|
isHighlightedPair,
|
||||||
|
reorderGlobeFeatureLayers,
|
||||||
|
]);
|
||||||
|
|
||||||
const shipData = useMemo(() => {
|
const shipData = useMemo(() => {
|
||||||
return targets.filter((t) => isFiniteNumber(t.lat) && isFiniteNumber(t.lon) && isFiniteNumber(t.mmsi));
|
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) {
|
if (zoneLabel) {
|
||||||
const zoneName = zoneLabel || ZONE_META[(String(props.zoneId ?? "") as ZoneId)]?.name || "수역";
|
return { html: `<div style="font-size: 12px; font-family: system-ui;">${zoneLabel}</div>` };
|
||||||
return { html: `<div style="font-size: 12px; font-family: system-ui;">${zoneName}</div>` };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -3174,7 +3284,7 @@ export function Map3D({
|
|||||||
clearMapFleetHoverState();
|
clearMapFleetHoverState();
|
||||||
setHoveredDeckMmsiSet((prev) => (prev.length === 0 ? prev : []));
|
setHoveredDeckMmsiSet((prev) => (prev.length === 0 ? prev : []));
|
||||||
setHoveredDeckPairMmsiSet((prev) => (prev.length === 0 ? prev : []));
|
setHoveredDeckPairMmsiSet((prev) => (prev.length === 0 ? prev : []));
|
||||||
const zoneId = String((props.zoneId ?? "").toString());
|
const zoneId = getZoneIdFromProps(props);
|
||||||
setHoveredZoneId(zoneId || null);
|
setHoveredZoneId(zoneId || null);
|
||||||
} else {
|
} else {
|
||||||
resetGlobeHoverStates();
|
resetGlobeHoverStates();
|
||||||
@ -3761,8 +3871,8 @@ export function Map3D({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const p = obj.properties as { zoneName?: string; zoneLabel?: string } | undefined;
|
const p = obj.properties as Record<string, unknown> | undefined;
|
||||||
const label = p?.zoneName ?? p?.zoneLabel;
|
const label = getZoneDisplayNameFromProps(p);
|
||||||
if (label) return { text: label };
|
if (label) return { text: label };
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user