fix(map): align prediction vectors with ship course + improve contrast
This commit is contained in:
부모
11aff67a04
커밋
a8aa916076
@ -370,6 +370,15 @@ function normalizeAngleDeg(value: number, offset = 0): number {
|
|||||||
return ((v % 360) + 360) % 360;
|
return ((v % 360) + 360) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toValidBearingDeg(value: unknown): number | null {
|
||||||
|
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
||||||
|
// AIS heading uses 511 as "not available". Some feeds may also use 360 as "not available".
|
||||||
|
if (value === 511) return null;
|
||||||
|
if (value < 0) return null;
|
||||||
|
if (value >= 360) return null;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function getDisplayHeading({
|
function getDisplayHeading({
|
||||||
cog,
|
cog,
|
||||||
heading,
|
heading,
|
||||||
@ -379,8 +388,8 @@ function getDisplayHeading({
|
|||||||
heading: number | null | undefined;
|
heading: number | null | undefined;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
}) {
|
}) {
|
||||||
const raw =
|
// Use COG (0=N, 90=E...) as the primary bearing so ship icons + prediction vectors stay aligned.
|
||||||
isFiniteNumber(heading) && heading >= 0 && heading <= 360 && heading !== 511 ? heading : isFiniteNumber(cog) ? cog : 0;
|
const raw = toValidBearingDeg(cog) ?? toValidBearingDeg(heading) ?? 0;
|
||||||
return normalizeAngleDeg(raw, offset);
|
return normalizeAngleDeg(raw, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1338,7 +1347,9 @@ export function Map3D({
|
|||||||
"zones-fill",
|
"zones-fill",
|
||||||
"zones-line",
|
"zones-line",
|
||||||
"zones-label",
|
"zones-label",
|
||||||
|
"predict-vectors-outline",
|
||||||
"predict-vectors",
|
"predict-vectors",
|
||||||
|
"predict-vectors-hl-outline",
|
||||||
"predict-vectors-hl",
|
"predict-vectors-hl",
|
||||||
"ships-globe-halo",
|
"ships-globe-halo",
|
||||||
"ships-globe-outline",
|
"ships-globe-outline",
|
||||||
@ -2457,7 +2468,9 @@ export function Map3D({
|
|||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
const srcId = "predict-vectors-src";
|
const srcId = "predict-vectors-src";
|
||||||
|
const outlineId = "predict-vectors-outline";
|
||||||
const lineId = "predict-vectors";
|
const lineId = "predict-vectors";
|
||||||
|
const hlOutlineId = "predict-vectors-hl-outline";
|
||||||
const hlId = "predict-vectors-hl";
|
const hlId = "predict-vectors-hl";
|
||||||
|
|
||||||
const ensure = () => {
|
const ensure = () => {
|
||||||
@ -2480,20 +2493,21 @@ export function Map3D({
|
|||||||
if (!isTarget && !isSelected && !isPinnedHighlight) continue;
|
if (!isTarget && !isSelected && !isPinnedHighlight) continue;
|
||||||
|
|
||||||
const sog = isFiniteNumber(t.sog) ? t.sog : null;
|
const sog = isFiniteNumber(t.sog) ? t.sog : null;
|
||||||
const cog =
|
const bearing = toValidBearingDeg(t.cog) ?? toValidBearingDeg(t.heading);
|
||||||
isFiniteNumber(t.cog) ? t.cog : isFiniteNumber(t.heading) ? t.heading : null;
|
if (sog == null || bearing == null) continue;
|
||||||
if (sog == null || cog == null) continue;
|
|
||||||
if (sog < 0.2) continue;
|
if (sog < 0.2) continue;
|
||||||
|
|
||||||
const distM = sog * metersPerSecondPerKnot * horizonSeconds;
|
const distM = sog * metersPerSecondPerKnot * horizonSeconds;
|
||||||
if (!Number.isFinite(distM) || distM <= 0) continue;
|
if (!Number.isFinite(distM) || distM <= 0) continue;
|
||||||
|
|
||||||
const to = destinationPointLngLat([t.lon, t.lat], cog, distM);
|
const to = destinationPointLngLat([t.lon, t.lat], bearing, distM);
|
||||||
|
|
||||||
const rgb = isTarget
|
const baseRgb = isTarget
|
||||||
? LEGACY_CODE_COLORS_RGB[legacy?.shipCode ?? ""] ?? OTHER_AIS_SPEED_RGB.moving
|
? LEGACY_CODE_COLORS_RGB[legacy?.shipCode ?? ""] ?? OTHER_AIS_SPEED_RGB.moving
|
||||||
: OTHER_AIS_SPEED_RGB.moving;
|
: OTHER_AIS_SPEED_RGB.moving;
|
||||||
const alpha = isTarget ? 0.48 : 0.28;
|
const rgb = lightenColor(baseRgb, isTarget ? 0.55 : 0.62);
|
||||||
|
const alpha = isTarget ? 0.72 : 0.52;
|
||||||
|
const alphaHl = isTarget ? 0.92 : 0.84;
|
||||||
const hl = isSelected || isPinnedHighlight ? 1 : 0;
|
const hl = isSelected || isPinnedHighlight ? 1 : 0;
|
||||||
|
|
||||||
features.push({
|
features.push({
|
||||||
@ -2504,10 +2518,11 @@ export function Map3D({
|
|||||||
mmsi: t.mmsi,
|
mmsi: t.mmsi,
|
||||||
minutes: horizonMinutes,
|
minutes: horizonMinutes,
|
||||||
sog,
|
sog,
|
||||||
cog,
|
cog: bearing,
|
||||||
target: isTarget ? 1 : 0,
|
target: isTarget ? 1 : 0,
|
||||||
hl,
|
hl,
|
||||||
color: rgbaCss(rgb, alpha),
|
color: rgbaCss(rgb, alpha),
|
||||||
|
colorHl: rgbaCss(rgb, alphaHl),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2524,7 +2539,7 @@ export function Map3D({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureLayer = (id: string, paint: LayerSpecification["paint"], filter?: unknown[]) => {
|
const ensureLayer = (id: string, paint: LayerSpecification["paint"], filter: unknown[]) => {
|
||||||
if (!map.getLayer(id)) {
|
if (!map.getLayer(id)) {
|
||||||
try {
|
try {
|
||||||
map.addLayer(
|
map.addLayer(
|
||||||
@ -2532,7 +2547,7 @@ export function Map3D({
|
|||||||
id,
|
id,
|
||||||
type: "line",
|
type: "line",
|
||||||
source: srcId,
|
source: srcId,
|
||||||
...(filter ? { filter: filter as never } : {}),
|
filter: filter as never,
|
||||||
layout: {
|
layout: {
|
||||||
visibility,
|
visibility,
|
||||||
"line-cap": "round",
|
"line-cap": "round",
|
||||||
@ -2548,30 +2563,63 @@ export function Map3D({
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
map.setLayoutProperty(id, "visibility", visibility);
|
map.setLayoutProperty(id, "visibility", visibility);
|
||||||
|
map.setFilter(id, filter as never);
|
||||||
|
if (paint && typeof paint === "object") {
|
||||||
|
for (const [key, value] of Object.entries(paint)) {
|
||||||
|
map.setPaintProperty(id, key as never, value as never);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const baseFilter = ["==", ["to-number", ["get", "hl"], 0], 0] as unknown as unknown[];
|
||||||
|
const hlFilter = ["==", ["to-number", ["get", "hl"], 0], 1] as unknown as unknown[];
|
||||||
|
|
||||||
|
// Outline (halo) for readability over bathymetry + seamark textures.
|
||||||
|
ensureLayer(
|
||||||
|
outlineId,
|
||||||
|
{
|
||||||
|
"line-color": "rgba(2,6,23,0.86)",
|
||||||
|
"line-width": 4.8,
|
||||||
|
"line-opacity": 1,
|
||||||
|
"line-blur": 0.2,
|
||||||
|
"line-dasharray": [1.2, 1.8] as never,
|
||||||
|
} as never,
|
||||||
|
baseFilter,
|
||||||
|
);
|
||||||
ensureLayer(
|
ensureLayer(
|
||||||
lineId,
|
lineId,
|
||||||
{
|
{
|
||||||
"line-color": ["coalesce", ["get", "color"], "rgba(148,163,184,0.3)"] as never,
|
"line-color": ["coalesce", ["get", "color"], "rgba(226,232,240,0.62)"] as never,
|
||||||
"line-width": 1.2,
|
"line-width": 2.4,
|
||||||
"line-opacity": 1,
|
"line-opacity": 1,
|
||||||
"line-dasharray": [1.2, 1.8] as never,
|
"line-dasharray": [1.2, 1.8] as never,
|
||||||
} as never,
|
} as never,
|
||||||
|
baseFilter,
|
||||||
|
);
|
||||||
|
ensureLayer(
|
||||||
|
hlOutlineId,
|
||||||
|
{
|
||||||
|
"line-color": "rgba(2,6,23,0.92)",
|
||||||
|
"line-width": 6.4,
|
||||||
|
"line-opacity": 1,
|
||||||
|
"line-blur": 0.25,
|
||||||
|
"line-dasharray": [1.2, 1.8] as never,
|
||||||
|
} as never,
|
||||||
|
hlFilter,
|
||||||
);
|
);
|
||||||
ensureLayer(
|
ensureLayer(
|
||||||
hlId,
|
hlId,
|
||||||
{
|
{
|
||||||
"line-color": ["coalesce", ["get", "color"], "rgba(226,232,240,0.7)"] as never,
|
"line-color": ["coalesce", ["get", "colorHl"], ["get", "color"], "rgba(241,245,249,0.92)"] as never,
|
||||||
"line-width": 2.2,
|
"line-width": 3.6,
|
||||||
"line-opacity": 1,
|
"line-opacity": 1,
|
||||||
"line-dasharray": [1.2, 1.8] as never,
|
"line-dasharray": [1.2, 1.8] as never,
|
||||||
} as never,
|
} as never,
|
||||||
["==", ["to-number", ["get", "hl"], 0], 1] as unknown as unknown[],
|
hlFilter,
|
||||||
);
|
);
|
||||||
|
|
||||||
reorderGlobeFeatureLayers();
|
reorderGlobeFeatureLayers();
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user