import { hostCore, hostState } from "./chunk-KL66PICH.js"; // src/plugins/ais.ts var aisWindow = window; var escapeAisHtml = (input) => hostCore.escapeMapHtml(input); var aisStatus = document.getElementById("ais-status"); var aisMessagesEl = document.getElementById("ais-messages"); var aisFilterInput = document.getElementById("ais-filter"); var aisBarOverlay = document.getElementById("ais-bar-overlay"); var aisChannelSummaryEl = document.getElementById("ais-channel-summary"); var aisVesselCountEl = document.getElementById("ais-vessel-count"); var aisLatestSeenEl = document.getElementById("ais-latest-seen"); var AIS_BAR_WINDOW_MS = 15 * 60 * 1e3; var AIS_DEFAULT_A_HZ = 161975e3; var AIS_CHANNEL_SPACING_HZ = 5e4; var aisFilterText = ""; var aisMessageHistory = []; function currentAisHistoryRetentionMs() { return typeof aisWindow.getDecodeHistoryRetentionMs === "function" ? aisWindow.getDecodeHistoryRetentionMs() : 24 * 60 * 60 * 1e3; } function pruneAisMessageHistory() { const cutoffMs = Date.now() - currentAisHistoryRetentionMs(); aisMessageHistory = aisMessageHistory.filter((msg) => (msg._tsMs ?? 0) >= cutoffMs); } function scheduleAisUi(key, job) { if (typeof aisWindow.trxScheduleUiFrameJob === "function") { aisWindow.trxScheduleUiFrameJob(key, job); return; } job(); } function scheduleAisHistoryRender() { scheduleAisUi("ais-history", () => { renderAisHistory(); }); } function scheduleAisBarUpdate() { scheduleAisUi("ais-bar", () => { updateAisBar(); }); } function formatAisMhz(freqHz) { return `${(freqHz / 1e6).toFixed(3)} MHz`; } function currentAisChannelPlan() { const raw = (document.getElementById("freq")?.value || "").replace(/[^\d]/g, ""); const aHz = raw ? Number(raw) : AIS_DEFAULT_A_HZ; const safeAHz = Number.isFinite(aHz) && aHz > 0 ? aHz : AIS_DEFAULT_A_HZ; return { aHz: safeAHz, bHz: safeAHz + AIS_CHANNEL_SPACING_HZ }; } function aisChannelInfo(channel) { const plan = currentAisChannelPlan(); const ch = (channel ?? "").trim().toUpperCase(); if (ch === "B") { return { label: "AIS-B", badgeClass: "ais-badge ais-badge-channel-b", freqText: formatAisMhz(plan.bHz) }; } return { label: "AIS-A", badgeClass: "ais-badge ais-badge-channel-a", freqText: formatAisMhz(plan.aHz) }; } function aisDisplayName(msg) { return msg.vessel_name || msg.callsign || `MMSI ${msg.mmsi}`; } function aisDisplayNameHtml(msg) { const label = escapeAisHtml(aisDisplayName(msg)); const url = aisWindow.buildAisVesselUrl?.(msg.mmsi) ?? null; if (!url) return label; return `${label}`; } function aisTypeLabel(type) { switch (Number(type)) { case 1: case 2: case 3: return "Class A Position"; case 4: return "Base Station"; case 5: return "Static/Voyage"; case 18: return "Class B Position"; case 19: return "Class B Extended"; case 21: return "Aid to Nav"; case 24: return "Class B Static"; default: return `Type ${type ?? "--"}`; } } function aisAgeText(tsMs) { if (typeof tsMs !== "number" || !Number.isFinite(tsMs)) return "just now"; const deltaMs = Math.max(0, Date.now() - tsMs); const seconds = Math.round(deltaMs / 1e3); if (seconds < 5) return "just now"; if (seconds < 60) return `${seconds}s ago`; const minutes = Math.round(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.round(minutes / 60); return `${hours}h ago`; } function aisMotionText(msg) { const parts = [ msg.sog_knots != null ? `${msg.sog_knots.toFixed(1)} kn` : null, msg.cog_deg != null ? `${msg.cog_deg.toFixed(1)}° COG` : null, msg.heading_deg != null ? `${msg.heading_deg.toFixed(0)}° HDG` : null ].filter(Boolean); return parts.join(" · "); } function aisRouteText(msg) { return [msg.callsign, msg.destination].filter(Boolean).join(" -> "); } function aisDistanceText(msg) { if (hostState.serverLat == null || hostState.serverLon == null || msg.lat == null || msg.lon == null) { return ""; } const distKm = hostCore.haversineKm(hostState.serverLat, hostState.serverLon, msg.lat, msg.lon); if (!Number.isFinite(distKm)) return ""; if (distKm < 1) return `${Math.round(distKm * 1e3)} m from TRX`; return `${distKm.toFixed(1)} km from TRX`; } function aisLatestByVessel(messages) { const byMmsi = /* @__PURE__ */ new Map(); for (const msg of messages) { const key = Number.isFinite(msg.mmsi) ? String(msg.mmsi) : `${msg.channel || "?"}:${msg._tsMs || 0}`; if (!byMmsi.has(key)) byMmsi.set(key, msg); } return Array.from(byMmsi.values()); } function updateAisSummary() { const plan = currentAisChannelPlan(); if (aisChannelSummaryEl) { aisChannelSummaryEl.textContent = `A ${formatAisMhz(plan.aHz)} · B ${formatAisMhz(plan.bHz)}`; } const vessels = aisLatestByVessel(aisMessageHistory); if (aisVesselCountEl) { const count = vessels.length; aisVesselCountEl.textContent = `${count} vessel${count === 1 ? "" : "s"}`; } if (aisLatestSeenEl) { const latest = aisMessageHistory[0]; if (!latest) { aisLatestSeenEl.textContent = "No traffic yet"; } else { const channel = aisChannelInfo(latest.channel); aisLatestSeenEl.textContent = `${channel.label} ${aisAgeText(latest._tsMs)}`; } } } function renderAisRow(msg) { const row = document.createElement("div"); row.className = "ais-message"; const ts = msg._ts || (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); const name = aisDisplayName(msg); const nameHtml = aisDisplayNameHtml(msg); const channel = aisChannelInfo(msg.channel); const motion = aisMotionText(msg); const route = aisRouteText(msg); const distance = aisDistanceText(msg); const pos = msg.lat != null && msg.lon != null ? `${msg.lat.toFixed(4)}, ${msg.lon.toFixed(4)}` : ""; row.dataset.filterText = [ name, msg.mmsi, msg.channel, channel.label, msg.vessel_name, msg.callsign, msg.destination, aisTypeLabel(msg.message_type) ].filter(Boolean).join(" ").toUpperCase(); row.innerHTML = `