119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import type { AisTarget } from '../../../entities/aisTarget/model/types';
|
|
import type { LegacyVesselInfo } from '../../../entities/legacyVessel/model/types';
|
|
import { rgbToHex } from '../../../shared/lib/map/palette';
|
|
import {
|
|
ANCHOR_SPEED_THRESHOLD_KN,
|
|
LEGACY_CODE_COLORS,
|
|
MAP_SELECTED_SHIP_RGB,
|
|
MAP_HIGHLIGHT_SHIP_RGB,
|
|
MAP_DEFAULT_SHIP_RGB,
|
|
} from '../constants';
|
|
import { isFiniteNumber } from './setUtils';
|
|
import { normalizeAngleDeg } from './geometry';
|
|
|
|
export function toValidBearingDeg(value: unknown): number | null {
|
|
if (typeof value !== 'number' || !Number.isFinite(value)) return null;
|
|
if (value === 511) return null;
|
|
if (value < 0) return null;
|
|
if (value >= 360) return null;
|
|
return value;
|
|
}
|
|
|
|
export function isAnchoredShip({
|
|
sog,
|
|
cog,
|
|
heading,
|
|
}: {
|
|
sog: number | null | undefined;
|
|
cog: number | null | undefined;
|
|
heading: number | null | undefined;
|
|
}): boolean {
|
|
if (!isFiniteNumber(sog)) return true;
|
|
if (sog <= ANCHOR_SPEED_THRESHOLD_KN) return true;
|
|
return toValidBearingDeg(cog) == null && toValidBearingDeg(heading) == null;
|
|
}
|
|
|
|
export function getDisplayHeading({
|
|
cog,
|
|
heading,
|
|
offset = 0,
|
|
}: {
|
|
cog: number | null | undefined;
|
|
heading: number | null | undefined;
|
|
offset?: number;
|
|
}) {
|
|
const raw = toValidBearingDeg(cog) ?? toValidBearingDeg(heading) ?? 0;
|
|
return normalizeAngleDeg(raw, offset);
|
|
}
|
|
|
|
export function lightenColor(rgb: [number, number, number], ratio = 0.32) {
|
|
const out = rgb.map((v) => Math.round(v + (255 - v) * ratio) as number) as [number, number, number];
|
|
return out;
|
|
}
|
|
|
|
export function getGlobeBaseShipColor({
|
|
legacy,
|
|
sog,
|
|
}: {
|
|
legacy: string | null;
|
|
sog: number | null;
|
|
}) {
|
|
if (legacy) {
|
|
const rgb = LEGACY_CODE_COLORS[legacy];
|
|
if (rgb) return rgbToHex(lightenColor(rgb, 0.38));
|
|
}
|
|
|
|
// Keep alpha control in icon-opacity only to avoid double-multiplying transparency.
|
|
if (!isFiniteNumber(sog)) return '#64748b';
|
|
if (sog >= 10) return '#94a3b8';
|
|
if (sog >= 1) return '#64748b';
|
|
return '#475569';
|
|
}
|
|
|
|
export function getShipColor(
|
|
t: AisTarget,
|
|
selectedMmsi: number | null,
|
|
legacyShipCode: string | null,
|
|
highlightedMmsis: Set<number>,
|
|
): [number, number, number, number] {
|
|
if (selectedMmsi && t.mmsi === selectedMmsi) {
|
|
return [MAP_SELECTED_SHIP_RGB[0], MAP_SELECTED_SHIP_RGB[1], MAP_SELECTED_SHIP_RGB[2], 255];
|
|
}
|
|
if (highlightedMmsis.has(t.mmsi)) {
|
|
return [MAP_HIGHLIGHT_SHIP_RGB[0], MAP_HIGHLIGHT_SHIP_RGB[1], MAP_HIGHLIGHT_SHIP_RGB[2], 235];
|
|
}
|
|
if (legacyShipCode) {
|
|
const rgb = LEGACY_CODE_COLORS[legacyShipCode];
|
|
if (rgb) return [rgb[0], rgb[1], rgb[2], 235];
|
|
return [245, 158, 11, 235];
|
|
}
|
|
if (!isFiniteNumber(t.sog)) return [MAP_DEFAULT_SHIP_RGB[0], MAP_DEFAULT_SHIP_RGB[1], MAP_DEFAULT_SHIP_RGB[2], 130];
|
|
if (t.sog >= 10) return [148, 163, 184, 185];
|
|
if (t.sog >= 1) return [MAP_DEFAULT_SHIP_RGB[0], MAP_DEFAULT_SHIP_RGB[1], MAP_DEFAULT_SHIP_RGB[2], 175];
|
|
return [71, 85, 105, 165];
|
|
}
|
|
|
|
export function buildGlobeShipFeature(
|
|
t: AisTarget,
|
|
legacy: LegacyVesselInfo | undefined,
|
|
selectedMmsi: number | null,
|
|
highlightedMmsis: Set<number>,
|
|
offset: number,
|
|
) {
|
|
const isSelected = selectedMmsi != null && t.mmsi === selectedMmsi ? 1 : 0;
|
|
const isHighlighted = highlightedMmsis.has(t.mmsi) ? 1 : 0;
|
|
const anchored = isAnchoredShip(t);
|
|
|
|
return {
|
|
mmsi: t.mmsi,
|
|
heading: getDisplayHeading({ cog: t.cog, heading: t.heading, offset }),
|
|
anchored,
|
|
color: getGlobeBaseShipColor({ legacy: legacy?.shipCode ?? null, sog: t.sog ?? null }),
|
|
selected: isSelected,
|
|
highlighted: isHighlighted,
|
|
permitted: legacy ? 1 : 0,
|
|
labelName: (t.name || '').trim() || legacy?.shipNameCn || legacy?.shipNameRoman || '',
|
|
legacyTag: legacy ? `${legacy.permitNo} (${legacy.shipCode})` : '',
|
|
};
|
|
}
|