Keep globe overlays stable and reuse globe layer IDs
This commit is contained in:
부모
e504dbebca
커밋
70dc651230
@ -121,6 +121,26 @@ function getZoneDisplayNameFromProps(props: Record<string, unknown> | null | und
|
|||||||
return ZONE_META[(zoneId as ZoneId)]?.name || `수역 ${zoneId}`;
|
return ZONE_META[(zoneId as ZoneId)]?.name || `수역 ${zoneId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeOrderedPairKey(a: number, b: number) {
|
||||||
|
const left = Math.trunc(Math.min(a, b));
|
||||||
|
const right = Math.trunc(Math.max(a, b));
|
||||||
|
return `${left}-${right}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePairLinkFeatureId(a: number, b: number, suffix?: string) {
|
||||||
|
const pair = makeOrderedPairKey(a, b);
|
||||||
|
return suffix ? `pair-${pair}-${suffix}` : `pair-${pair}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeFcSegmentFeatureId(a: number, b: number, segmentIndex: number) {
|
||||||
|
const pair = makeOrderedPairKey(a, b);
|
||||||
|
return `fc-${pair}-${segmentIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeFleetCircleFeatureId(ownerKey: string) {
|
||||||
|
return `fleet-${ownerKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
const SHIP_ICON_MAPPING = {
|
const SHIP_ICON_MAPPING = {
|
||||||
ship: {
|
ship: {
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -972,20 +992,21 @@ export function Map3D({
|
|||||||
return keys;
|
return keys;
|
||||||
}, [hoveredFleetOwnerKey, hoveredDeckFleetOwnerKey]);
|
}, [hoveredFleetOwnerKey, hoveredDeckFleetOwnerKey]);
|
||||||
|
|
||||||
const reorderGlobeFeatureLayers = useCallback(() => {
|
const reorderGlobeFeatureLayers = useCallback((options?: { shipTop?: boolean }) => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map || projectionRef.current !== "globe") return;
|
if (!map || projectionRef.current !== "globe") return;
|
||||||
if (projectionBusyRef.current) return;
|
if (projectionBusyRef.current) return;
|
||||||
|
|
||||||
|
const shipTop = options?.shipTop === true;
|
||||||
const ordering = [
|
const ordering = [
|
||||||
|
"zones-fill",
|
||||||
|
"zones-line",
|
||||||
|
"zones-label",
|
||||||
"pair-lines-ml",
|
"pair-lines-ml",
|
||||||
"fc-lines-ml",
|
"fc-lines-ml",
|
||||||
"pair-range-ml",
|
"pair-range-ml",
|
||||||
"fleet-circles-ml-fill",
|
"fleet-circles-ml-fill",
|
||||||
"fleet-circles-ml",
|
"fleet-circles-ml",
|
||||||
"zones-fill",
|
|
||||||
"zones-line",
|
|
||||||
"zones-label",
|
|
||||||
"ships-globe-halo",
|
"ships-globe-halo",
|
||||||
"ships-globe-outline",
|
"ships-globe-outline",
|
||||||
"ships-globe",
|
"ships-globe",
|
||||||
@ -999,6 +1020,17 @@ export function Map3D({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!shipTop) return;
|
||||||
|
|
||||||
|
const shipOrdering = ["ships-globe-halo", "ships-globe-outline", "ships-globe"];
|
||||||
|
for (const layerId of shipOrdering) {
|
||||||
|
try {
|
||||||
|
if (map.getLayer(layerId)) map.moveLayer(layerId);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -2405,7 +2437,7 @@ export function Map3D({
|
|||||||
|
|
||||||
// Selection and highlight are now source-data driven.
|
// Selection and highlight are now source-data driven.
|
||||||
bringShipLayersToFront();
|
bringShipLayersToFront();
|
||||||
reorderGlobeFeatureLayers();
|
reorderGlobeFeatureLayers({ shipTop: true });
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2514,12 +2546,7 @@ export function Map3D({
|
|||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
try {
|
try {
|
||||||
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, "visibility", "none");
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (map.getSource(srcId)) map.removeSource(srcId);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -2535,9 +2562,9 @@ export function Map3D({
|
|||||||
|
|
||||||
const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: (pairLinks || []).map((p, idx) => ({
|
features: (pairLinks || []).map((p) => ({
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: `${p.aMmsi}-${p.bMmsi}-${idx}`,
|
id: makePairLinkFeatureId(p.aMmsi, p.bMmsi),
|
||||||
geometry: { type: "LineString", coordinates: [p.from, p.to] },
|
geometry: { type: "LineString", coordinates: [p.from, p.to] },
|
||||||
properties: {
|
properties: {
|
||||||
type: "pair",
|
type: "pair",
|
||||||
@ -2588,12 +2615,18 @@ export function Map3D({
|
|||||||
] as never,
|
] as never,
|
||||||
"line-opacity": 0.9,
|
"line-opacity": 0.9,
|
||||||
},
|
},
|
||||||
} as unknown as LayerSpecification,
|
} as unknown as LayerSpecification,
|
||||||
before,
|
before,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Pair lines layer add failed:", e);
|
console.warn("Pair lines layer add failed:", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map.setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reorderGlobeFeatureLayers();
|
reorderGlobeFeatureLayers();
|
||||||
@ -2604,7 +2637,6 @@ export function Map3D({
|
|||||||
ensure();
|
ensure();
|
||||||
return () => {
|
return () => {
|
||||||
stop();
|
stop();
|
||||||
remove();
|
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
projection,
|
projection,
|
||||||
@ -2626,12 +2658,7 @@ export function Map3D({
|
|||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
try {
|
try {
|
||||||
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, "visibility", "none");
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (map.getSource(srcId)) map.removeSource(srcId);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -2658,7 +2685,7 @@ export function Map3D({
|
|||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: segs.map((s, idx) => ({
|
features: segs.map((s, idx) => ({
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: `fc-${idx}`,
|
id: makeFcSegmentFeatureId(s.fromMmsi ?? -1, s.toMmsi ?? -1, idx),
|
||||||
geometry: { type: "LineString", coordinates: [s.from, s.to] },
|
geometry: { type: "LineString", coordinates: [s.from, s.to] },
|
||||||
properties: {
|
properties: {
|
||||||
type: "fc",
|
type: "fc",
|
||||||
@ -2702,12 +2729,18 @@ export function Map3D({
|
|||||||
"line-width": ["case", ["==", ["get", "highlighted"], 1], 2.0, 1.3] as never,
|
"line-width": ["case", ["==", ["get", "highlighted"], 1], 2.0, 1.3] as never,
|
||||||
"line-opacity": 0.9,
|
"line-opacity": 0.9,
|
||||||
},
|
},
|
||||||
} as unknown as LayerSpecification,
|
} as unknown as LayerSpecification,
|
||||||
before,
|
before,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("FC lines layer add failed:", e);
|
console.warn("FC lines layer add failed:", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map.setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reorderGlobeFeatureLayers();
|
reorderGlobeFeatureLayers();
|
||||||
@ -2718,7 +2751,6 @@ export function Map3D({
|
|||||||
ensure();
|
ensure();
|
||||||
return () => {
|
return () => {
|
||||||
stop();
|
stop();
|
||||||
remove();
|
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
projection,
|
projection,
|
||||||
@ -2741,22 +2773,12 @@ export function Map3D({
|
|||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
try {
|
try {
|
||||||
if (map.getLayer(fillLayerId)) map.removeLayer(fillLayerId);
|
if (map.getLayer(fillLayerId)) map.setLayoutProperty(fillLayerId, "visibility", "none");
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, "visibility", "none");
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (map.getSource(srcId)) map.removeSource(srcId);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (map.getSource(fillSrcId)) map.removeSource(fillSrcId);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -2772,11 +2794,11 @@ export function Map3D({
|
|||||||
|
|
||||||
const fcLine: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
const fcLine: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: (fleetCircles || []).map((c, idx) => {
|
features: (fleetCircles || []).map((c) => {
|
||||||
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: `fleet-${c.ownerKey}-${idx}`,
|
id: makeFleetCircleFeatureId(c.ownerKey),
|
||||||
geometry: { type: "LineString", coordinates: ring },
|
geometry: { type: "LineString", coordinates: ring },
|
||||||
properties: {
|
properties: {
|
||||||
type: "fleet",
|
type: "fleet",
|
||||||
@ -2795,11 +2817,11 @@ export function Map3D({
|
|||||||
|
|
||||||
const fcFill: GeoJSON.FeatureCollection<GeoJSON.Polygon> = {
|
const fcFill: GeoJSON.FeatureCollection<GeoJSON.Polygon> = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: (fleetCircles || []).map((c, idx) => {
|
features: (fleetCircles || []).map((c) => {
|
||||||
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: `fleet-fill-${c.ownerKey}-${idx}`,
|
id: `${makeFleetCircleFeatureId(c.ownerKey)}-fill`,
|
||||||
geometry: { type: "Polygon", coordinates: [ring] },
|
geometry: { type: "Polygon", coordinates: [ring] },
|
||||||
properties: {
|
properties: {
|
||||||
type: "fleet-fill",
|
type: "fleet-fill",
|
||||||
@ -2859,6 +2881,12 @@ export function Map3D({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Fleet circles fill layer add failed:", e);
|
console.warn("Fleet circles fill layer add failed:", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map.setLayoutProperty(fillLayerId, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!map.getLayer(layerId)) {
|
if (!map.getLayer(layerId)) {
|
||||||
@ -2880,6 +2908,12 @@ export function Map3D({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Fleet circles layer add failed:", e);
|
console.warn("Fleet circles layer add failed:", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map.setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reorderGlobeFeatureLayers();
|
reorderGlobeFeatureLayers();
|
||||||
@ -2890,7 +2924,6 @@ export function Map3D({
|
|||||||
ensure();
|
ensure();
|
||||||
return () => {
|
return () => {
|
||||||
stop();
|
stop();
|
||||||
remove();
|
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
projection,
|
projection,
|
||||||
@ -2914,12 +2947,7 @@ export function Map3D({
|
|||||||
|
|
||||||
const remove = () => {
|
const remove = () => {
|
||||||
try {
|
try {
|
||||||
if (map.getLayer(layerId)) map.removeLayer(layerId);
|
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, "visibility", "none");
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (map.getSource(srcId)) map.removeSource(srcId);
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -2952,11 +2980,11 @@ export function Map3D({
|
|||||||
|
|
||||||
const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
const fc: GeoJSON.FeatureCollection<GeoJSON.LineString> = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: ranges.map((c, idx) => {
|
features: ranges.map((c) => {
|
||||||
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
const ring = circleRingLngLat(c.center, c.radiusNm * 1852);
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
id: `pair-range-${idx}`,
|
id: makePairLinkFeatureId(c.aMmsi, c.bMmsi),
|
||||||
geometry: { type: "LineString", coordinates: ring },
|
geometry: { type: "LineString", coordinates: ring },
|
||||||
properties: {
|
properties: {
|
||||||
type: "pair-range",
|
type: "pair-range",
|
||||||
@ -3007,6 +3035,12 @@ export function Map3D({
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Pair range layer add failed:", e);
|
console.warn("Pair range layer add failed:", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
map.setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kickRepaint(map);
|
kickRepaint(map);
|
||||||
@ -3016,7 +3050,6 @@ export function Map3D({
|
|||||||
ensure();
|
ensure();
|
||||||
return () => {
|
return () => {
|
||||||
stop();
|
stop();
|
||||||
remove();
|
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
projection,
|
projection,
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user