/** Map CoT / ADS-B entity display: icons and popups. */ export const COT_COLORS = { air: '#60a5fa', helicopter: '#fbbf24', surface: '#38bdf8', ground: '#f59e0b', } export function cotCategory(type) { const t = typeof type === 'string' ? type : '' if (t.startsWith('a-f-A-')) return 'air' if (t.startsWith('a-f-S-')) return 'surface' return 'ground' } /** Whether the entity is a helicopter or fixed-wing aircraft. @returns {'helicopter' | 'fixedWing'} */ export function cotAirIconKind(entity) { const type = entity?.type ?? '' if (type.endsWith('-C-H') || type.endsWith('-M-H')) return 'helicopter' return 'fixedWing' } function iconWrap(heading, inner) { const rotate = Number.isFinite(heading) ? ` style="transform:rotate(${heading}deg)"` : '' return `` } const PLANE_SVG = color => `` const HELI_SVG = color => `` const SHIP_SVG = color => `` const GROUND_SVG = color => `` export function getCotIconHtml(entity) { const category = cotCategory(entity?.type) const heading = Number(entity?.heading) if (category === 'air') { const kind = cotAirIconKind(entity) const color = kind === 'helicopter' ? COT_COLORS.helicopter : COT_COLORS.air const svg = kind === 'helicopter' ? HELI_SVG(color) : PLANE_SVG(color) return { html: iconWrap(heading, svg), className: `cot-entity-${kind}` } } if (category === 'surface') { return { html: iconWrap(heading, SHIP_SVG(COT_COLORS.surface)), className: 'cot-entity-surface' } } return { html: iconWrap(undefined, GROUND_SVG(COT_COLORS.ground)), className: 'cot-entity-ground' } } function msToKnots(ms) { return Number.isFinite(ms) ? Math.round(ms * 1.94384) : null } function metersToFeet(m) { return Number.isFinite(m) ? Math.round(m * 3.28084) : null } function fmtHeading(deg) { return Number.isFinite(deg) ? `${Math.round(deg)}°` : null } function fmtVerticalFpm(ms) { if (!Number.isFinite(ms) || ms === 0) return null const fpm = Math.round(ms * 196.85) return `${fpm > 0 ? '+' : ''}${fpm} fpm` } function icaoFromEntity(entity) { if (entity?.icao) return String(entity.icao).toUpperCase() if (typeof entity?.id === 'string' && entity.id.startsWith('ICAO.')) { return entity.id.slice(5).toUpperCase() } return null } function mmsiFromEntity(entity) { if (entity?.mmsi) return String(entity.mmsi) if (typeof entity?.id === 'string' && entity.id.startsWith('MMSI.')) return entity.id.slice(5) return null } function popupLine(escape, parts) { const line = parts.filter(Boolean).join(' · ') return line ? `