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;
|
||||
}
|
||||
|
||||
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({
|
||||
cog,
|
||||
heading,
|
||||
@ -379,8 +388,8 @@ function getDisplayHeading({
|
||||
heading: number | null | undefined;
|
||||
offset?: number;
|
||||
}) {
|
||||
const raw =
|
||||
isFiniteNumber(heading) && heading >= 0 && heading <= 360 && heading !== 511 ? heading : isFiniteNumber(cog) ? cog : 0;
|
||||
// Use COG (0=N, 90=E...) as the primary bearing so ship icons + prediction vectors stay aligned.
|
||||
const raw = toValidBearingDeg(cog) ?? toValidBearingDeg(heading) ?? 0;
|
||||
return normalizeAngleDeg(raw, offset);
|
||||
}
|
||||
|
||||
@ -1329,16 +1338,18 @@ export function Map3D({
|
||||
const fleetFocusLat = fleetFocus?.center?.[1];
|
||||
const fleetFocusZoom = fleetFocus?.zoom;
|
||||
|
||||
const reorderGlobeFeatureLayers = useCallback(() => {
|
||||
const map = mapRef.current;
|
||||
if (!map || projectionRef.current !== "globe") return;
|
||||
if (projectionBusyRef.current) return;
|
||||
const reorderGlobeFeatureLayers = useCallback(() => {
|
||||
const map = mapRef.current;
|
||||
if (!map || projectionRef.current !== "globe") return;
|
||||
if (projectionBusyRef.current) return;
|
||||
|
||||
const ordering = [
|
||||
"zones-fill",
|
||||
"zones-line",
|
||||
"zones-label",
|
||||
"predict-vectors-outline",
|
||||
"predict-vectors",
|
||||
"predict-vectors-hl-outline",
|
||||
"predict-vectors-hl",
|
||||
"ships-globe-halo",
|
||||
"ships-globe-outline",
|
||||
@ -2457,7 +2468,9 @@ export function Map3D({
|
||||
if (!map) return;
|
||||
|
||||
const srcId = "predict-vectors-src";
|
||||
const outlineId = "predict-vectors-outline";
|
||||
const lineId = "predict-vectors";
|
||||
const hlOutlineId = "predict-vectors-hl-outline";
|
||||
const hlId = "predict-vectors-hl";
|
||||
|
||||
const ensure = () => {
|
||||
@ -2480,20 +2493,21 @@ export function Map3D({
|
||||
if (!isTarget && !isSelected && !isPinnedHighlight) continue;
|
||||
|
||||
const sog = isFiniteNumber(t.sog) ? t.sog : null;
|
||||
const cog =
|
||||
isFiniteNumber(t.cog) ? t.cog : isFiniteNumber(t.heading) ? t.heading : null;
|
||||
if (sog == null || cog == null) continue;
|
||||
const bearing = toValidBearingDeg(t.cog) ?? toValidBearingDeg(t.heading);
|
||||
if (sog == null || bearing == null) continue;
|
||||
if (sog < 0.2) continue;
|
||||
|
||||
const distM = sog * metersPerSecondPerKnot * horizonSeconds;
|
||||
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
|
||||
: 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;
|
||||
|
||||
features.push({
|
||||
@ -2504,10 +2518,11 @@ export function Map3D({
|
||||
mmsi: t.mmsi,
|
||||
minutes: horizonMinutes,
|
||||
sog,
|
||||
cog,
|
||||
cog: bearing,
|
||||
target: isTarget ? 1 : 0,
|
||||
hl,
|
||||
color: rgbaCss(rgb, alpha),
|
||||
colorHl: rgbaCss(rgb, alphaHl),
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -2524,7 +2539,7 @@ export function Map3D({
|
||||
return;
|
||||
}
|
||||
|
||||
const ensureLayer = (id: string, paint: LayerSpecification["paint"], filter?: unknown[]) => {
|
||||
const ensureLayer = (id: string, paint: LayerSpecification["paint"], filter: unknown[]) => {
|
||||
if (!map.getLayer(id)) {
|
||||
try {
|
||||
map.addLayer(
|
||||
@ -2532,7 +2547,7 @@ export function Map3D({
|
||||
id,
|
||||
type: "line",
|
||||
source: srcId,
|
||||
...(filter ? { filter: filter as never } : {}),
|
||||
filter: filter as never,
|
||||
layout: {
|
||||
visibility,
|
||||
"line-cap": "round",
|
||||
@ -2548,30 +2563,63 @@ export function Map3D({
|
||||
} else {
|
||||
try {
|
||||
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 {
|
||||
// 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(
|
||||
lineId,
|
||||
{
|
||||
"line-color": ["coalesce", ["get", "color"], "rgba(148,163,184,0.3)"] as never,
|
||||
"line-width": 1.2,
|
||||
"line-color": ["coalesce", ["get", "color"], "rgba(226,232,240,0.62)"] as never,
|
||||
"line-width": 2.4,
|
||||
"line-opacity": 1,
|
||||
"line-dasharray": [1.2, 1.8] 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(
|
||||
hlId,
|
||||
{
|
||||
"line-color": ["coalesce", ["get", "color"], "rgba(226,232,240,0.7)"] as never,
|
||||
"line-width": 2.2,
|
||||
"line-color": ["coalesce", ["get", "colorHl"], ["get", "color"], "rgba(241,245,249,0.92)"] as never,
|
||||
"line-width": 3.6,
|
||||
"line-opacity": 1,
|
||||
"line-dasharray": [1.2, 1.8] as never,
|
||||
} as never,
|
||||
["==", ["to-number", ["get", "hl"], 0], 1] as unknown as unknown[],
|
||||
hlFilter,
|
||||
);
|
||||
|
||||
reorderGlobeFeatureLayers();
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user