fix(map): scale flat icons and prioritize relation layers

This commit is contained in:
htlee 2026-02-15 14:24:00 +09:00
부모 c31d26124c
커밋 15378ed7ff

파일 보기

@ -182,6 +182,11 @@ const DEPTH_DISABLED_PARAMS = {
depthWriteEnabled: false, depthWriteEnabled: false,
} as const; } as const;
const FLAT_SHIP_ICON_SIZE = 19;
const FLAT_SHIP_ICON_SIZE_SELECTED = 28;
const FLAT_LEGACY_HALO_RADIUS = 14;
const FLAT_LEGACY_HALO_RADIUS_SELECTED = 18;
const GLOBE_OVERLAY_PARAMS = { const GLOBE_OVERLAY_PARAMS = {
// In globe mode we want depth-testing against the globe so features on the far side don't draw through. // In globe mode we want depth-testing against the globe so features on the far side don't draw through.
// Still disable depth writes so our overlays don't interfere with each other. // Still disable depth writes so our overlays don't interfere with each other.
@ -717,7 +722,12 @@ export function Map3D({
if (!map) return; if (!map) return;
let cancelled = false; let cancelled = false;
let retries = 0; let retries = 0;
const maxRetries = 6; const maxRetries = 18;
const getCurrentProjection = () => {
const projectionConfig = map.getProjection?.();
return projectionConfig && "type" in projectionConfig ? (projectionConfig.type as MapProjectionId | undefined) : undefined;
};
const syncProjectionAndDeck = () => { const syncProjectionAndDeck = () => {
if (cancelled) return; if (cancelled) return;
@ -732,7 +742,9 @@ export function Map3D({
const next = projection; const next = projection;
try { try {
map.setProjection({ type: next }); if (getCurrentProjection() !== next) {
map.setProjection({ type: next });
}
map.setRenderWorldCopies(next !== "globe"); map.setRenderWorldCopies(next !== "globe");
} catch (e) { } catch (e) {
if (!cancelled && retries < maxRetries) { if (!cancelled && retries < maxRetries) {
@ -781,6 +793,11 @@ export function Map3D({
} catch { } catch {
// ignore // ignore
} }
if (!map.getLayer(layer.id) && !cancelled && retries < maxRetries) {
retries += 1;
window.requestAnimationFrame(() => syncProjectionAndDeck());
return;
}
} }
} else { } else {
// Tear down globe custom layer (if present), restore MapboxOverlay interleaved. // Tear down globe custom layer (if present), restore MapboxOverlay interleaved.
@ -1433,7 +1450,7 @@ export function Map3D({
return; return;
} }
const before = map.getLayer("ships-globe-halo") ? "ships-globe-halo" : undefined; const before = undefined;
if (!map.getLayer(layerId)) { if (!map.getLayer(layerId)) {
try { try {
@ -1525,7 +1542,7 @@ export function Map3D({
return; return;
} }
const before = map.getLayer("ships-globe-halo") ? "ships-globe-halo" : undefined; const before = undefined;
if (!map.getLayer(layerId)) { if (!map.getLayer(layerId)) {
try { try {
@ -1613,7 +1630,7 @@ export function Map3D({
return; return;
} }
const before = map.getLayer("ships-globe-halo") ? "ships-globe-halo" : undefined; const before = undefined;
if (!map.getLayer(layerId)) { if (!map.getLayer(layerId)) {
try { try {
@ -1706,7 +1723,7 @@ export function Map3D({
return; return;
} }
const before = map.getLayer("ships-globe-halo") ? "ships-globe-halo" : undefined; const before = undefined;
if (!map.getLayer(layerId)) { if (!map.getLayer(layerId)) {
try { try {
@ -1805,22 +1822,61 @@ export function Map3D({
); );
} }
if (overlays.fleetCircles && projection !== "globe" && (fleetCircles?.length ?? 0) > 0) { if (settings.showShips && projection !== "globe") {
layers.push( layers.push(
new ScatterplotLayer<FleetCircle>({ new IconLayer<AisTarget>({
id: "fleet-circles", id: "ships",
data: fleetCircles, data: shipData,
pickable: true,
// Keep icons horizontal on the sea surface when view is pitched/rotated.
billboard: false,
parameters: overlayParams,
iconAtlas: "/assets/ship.svg",
iconMapping: SHIP_ICON_MAPPING,
getIcon: () => "ship",
getPosition: (d) =>
[d.lon, d.lat] as [number, number],
getAngle: (d) => (isFiniteNumber(d.cog) ? d.cog : 0),
sizeUnits: "pixels",
getSize: (d) => (selectedMmsi && d.mmsi === selectedMmsi ? FLAT_SHIP_ICON_SIZE_SELECTED : FLAT_SHIP_ICON_SIZE),
getColor: (d) => getShipColor(d, selectedMmsi, legacyHits?.get(d.mmsi)?.shipCode ?? null),
alphaCutoff: 0.05,
updateTriggers: {
getSize: [selectedMmsi],
getColor: [selectedMmsi, legacyHits],
},
}),
);
}
if (settings.showShips && projection !== "globe" && legacyTargets.length > 0) {
layers.push(
new ScatterplotLayer<AisTarget>({
id: "legacy-halo",
data: legacyTargets,
pickable: false, pickable: false,
billboard: false, billboard: false,
// This ring is most prone to z-fighting, so force it into pure painter's-order rendering.
parameters: overlayParams, parameters: overlayParams,
filled: false, filled: false,
stroked: true, stroked: true,
radiusUnits: "meters", radiusUnits: "pixels",
getRadius: (d) => d.radiusNm * 1852, getRadius: (d) =>
(selectedMmsi && d.mmsi === selectedMmsi ? FLAT_LEGACY_HALO_RADIUS_SELECTED : FLAT_LEGACY_HALO_RADIUS),
lineWidthUnits: "pixels", lineWidthUnits: "pixels",
getLineWidth: 1, getLineWidth: 2,
getLineColor: () => [245, 158, 11, 140], getLineColor: (d) => {
getPosition: (d) => d.center, const l = legacyHits?.get(d.mmsi);
const rgb = l ? LEGACY_CODE_COLORS[l.shipCode] : null;
if (!rgb) return [245, 158, 11, 200];
return [rgb[0], rgb[1], rgb[2], 200];
},
getPosition: (d) =>
[d.lon, d.lat] as [number, number],
updateTriggers: {
getRadius: [selectedMmsi],
getLineColor: [legacyHits],
},
}), }),
); );
} }
@ -1878,60 +1934,22 @@ export function Map3D({
); );
} }
if (settings.showShips && projection !== "globe" && legacyTargets.length > 0) { if (overlays.fleetCircles && projection !== "globe" && (fleetCircles?.length ?? 0) > 0) {
layers.push( layers.push(
new ScatterplotLayer<AisTarget>({ new ScatterplotLayer<FleetCircle>({
id: "legacy-halo", id: "fleet-circles",
data: legacyTargets, data: fleetCircles,
pickable: false, pickable: false,
billboard: false, billboard: false,
// This ring is most prone to z-fighting, so force it into pure painter's-order rendering.
parameters: overlayParams, parameters: overlayParams,
filled: false, filled: false,
stroked: true, stroked: true,
radiusUnits: "pixels", radiusUnits: "meters",
getRadius: (d) => (selectedMmsi && d.mmsi === selectedMmsi ? 22 : 16), getRadius: (d) => d.radiusNm * 1852,
lineWidthUnits: "pixels", lineWidthUnits: "pixels",
getLineWidth: 2, getLineWidth: 1,
getLineColor: (d) => { getLineColor: () => [245, 158, 11, 140],
const l = legacyHits?.get(d.mmsi); getPosition: (d) => d.center,
const rgb = l ? LEGACY_CODE_COLORS[l.shipCode] : null;
if (!rgb) return [245, 158, 11, 200];
return [rgb[0], rgb[1], rgb[2], 200];
},
getPosition: (d) =>
[d.lon, d.lat] as [number, number],
updateTriggers: {
getRadius: [selectedMmsi],
getLineColor: [legacyHits],
},
}),
);
}
if (settings.showShips && projection !== "globe") {
layers.push(
new IconLayer<AisTarget>({
id: "ships",
data: shipData,
pickable: true,
// Keep icons horizontal on the sea surface when view is pitched/rotated.
billboard: false,
parameters: overlayParams,
iconAtlas: "/assets/ship.svg",
iconMapping: SHIP_ICON_MAPPING,
getIcon: () => "ship",
getPosition: (d) =>
[d.lon, d.lat] as [number, number],
getAngle: (d) => (isFiniteNumber(d.cog) ? d.cog : 0),
sizeUnits: "pixels",
getSize: (d) => (selectedMmsi && d.mmsi === selectedMmsi ? 34 : 22),
getColor: (d) => getShipColor(d, selectedMmsi, legacyHits?.get(d.mmsi)?.shipCode ?? null),
alphaCutoff: 0.05,
updateTriggers: {
getSize: [selectedMmsi],
getColor: [selectedMmsi, legacyHits],
},
}), }),
); );
} }