feat: critical news pins on the osint map
This commit is contained in:
parent
3b4dd49253
commit
496d13b8b0
1 changed files with 68 additions and 1 deletions
|
|
@ -264,6 +264,7 @@
|
|||
.hud-chip .c.fires { background: #fb923c; box-shadow: 0 0 6px #fb923c; }
|
||||
.hud-chip .c.cams { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
|
||||
.hud-chip .c.blips { background: var(--magenta); box-shadow: 0 0 6px var(--magenta); }
|
||||
.hud-chip .c.news { background: var(--magenta); box-shadow: 0 0 6px var(--magenta); border-radius: 1px; transform: rotate(45deg); }
|
||||
.hud-chip.off { opacity: 0.4; }
|
||||
#dvr-bar {
|
||||
position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
|
||||
|
|
@ -319,6 +320,7 @@
|
|||
.lp-dot.fires { background: #fb923c; box-shadow: 0 0 7px #fb923c; }
|
||||
.lp-dot.cams { background: #4ade80; box-shadow: 0 0 7px #4ade80; }
|
||||
.lp-dot.blips { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); }
|
||||
.lp-dot.news { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); border-radius: 1px; transform: rotate(45deg); }
|
||||
.lp-dot.weather { background: var(--amber); box-shadow: 0 0 7px var(--amber); }
|
||||
.lp-dot.flights { background: #7dd3fc; box-shadow: 0 0 7px #7dd3fc; }
|
||||
.lp-dot.vessels { background: #2dd4bf; box-shadow: 0 0 7px #2dd4bf; }
|
||||
|
|
@ -724,6 +726,13 @@
|
|||
</div>
|
||||
<div class="lp-note">Geolocated events from the ingest pipeline. Color = source type.</div>
|
||||
</div>
|
||||
<div class="lp-layer">
|
||||
<div class="lp-row">
|
||||
<label class="lp-name"><input type="checkbox" id="lp-news-on" checked onchange="toggleNewsPins()"> <span class="lp-dot news"></span> Critical News</label>
|
||||
<span class="lp-count" id="lp-news-count">0</span>
|
||||
</div>
|
||||
<div class="lp-note">LLM-estimated locations from the hourly brief. Pins only for critical/high.</div>
|
||||
</div>
|
||||
|
||||
<div class="lp-layer">
|
||||
<div class="lp-row">
|
||||
|
|
@ -804,6 +813,7 @@
|
|||
<div class="hud-chip" id="hud-fires"><span class="c fires"></span>FIRMS <b id="hud-fires-n">—</b></div>
|
||||
<div class="hud-chip" id="hud-cams"><span class="c cams"></span>CAMS <b id="hud-cams-n">—</b></div>
|
||||
<div class="hud-chip" id="hud-blips"><span class="c blips"></span>BLIPS <b id="hud-blips-n">—</b></div>
|
||||
<div class="hud-chip" id="hud-news"><span class="c news"></span>NEWS <b id="hud-news-n">—</b></div>
|
||||
</div>
|
||||
<div id="dvr-bar" class="live">
|
||||
<button class="btn" id="dvr-live" type="button" onclick="dvrGoLive()">LIVE</button>
|
||||
|
|
@ -1204,6 +1214,7 @@ function showView(name) {
|
|||
if (firesOn) loadFires();
|
||||
if (camsOn) loadCams();
|
||||
if (blipsOn) loadBlips();
|
||||
if (newsOn) loadNewsPins();
|
||||
}
|
||||
}
|
||||
if (name === 'news') loadNews(false);
|
||||
|
|
@ -1762,6 +1773,7 @@ async function saveSummaryModel() {
|
|||
let map = null, mapLayer = null, mapInitStarted = false;
|
||||
let firesHeat = null, camsGroup = null, firesOn = false, camsOn = false;
|
||||
let blipsGroup = null, blipsOn = false, blipsSince = '24h';
|
||||
let newsGroup = null, newsOn = false, newsReq = 0;
|
||||
let firesOpacity = 0.65, firesColor = 'brightness', firesSince = '24h';
|
||||
let camsOpacity = 1.0, baseOpacity = 1.0;
|
||||
let camReq = 0, fireReq = 0, blipReq = 0; // stale-response guards (rapid zoom races)
|
||||
|
|
@ -1954,6 +1966,7 @@ async function initMap() {
|
|||
if (firesOn) loadFires();
|
||||
if (camsOn) loadCams();
|
||||
if (blipsOn) loadBlips();
|
||||
if (newsOn) loadNewsPins();
|
||||
refreshLiveOverlays();
|
||||
sendLiveViewport();
|
||||
}, 500);
|
||||
|
|
@ -1965,6 +1978,7 @@ async function initMap() {
|
|||
firesOn = document.getElementById('lp-fires-on').checked;
|
||||
camsOn = document.getElementById('lp-cams-on').checked;
|
||||
blipsOn = document.getElementById('lp-blips-on').checked;
|
||||
newsOn = document.getElementById('lp-news-on').checked;
|
||||
firesSince = document.getElementById('lp-fires-since').value;
|
||||
blipsSince = document.getElementById('lp-blips-since').value;
|
||||
radarOn = document.getElementById('lp-radar-on').checked;
|
||||
|
|
@ -1979,6 +1993,7 @@ async function initMap() {
|
|||
if (firesOn) loadFires();
|
||||
if (camsOn) loadCams();
|
||||
if (blipsOn) loadBlips();
|
||||
if (newsOn) loadNewsPins();
|
||||
refreshLiveOverlays();
|
||||
connectLiveWs();
|
||||
} catch(e) {
|
||||
|
|
@ -2001,8 +2016,9 @@ function syncHud() {
|
|||
set('fires', firesOn ? (hudFiresCount) : '—', firesOn);
|
||||
set('cams', camsOn ? (hudCamsCount) : '—', camsOn);
|
||||
set('blips', blipsOn ? (hudBlipsCount) : '—', blipsOn);
|
||||
set('news', newsOn ? (hudNewsCount) : '—', newsOn);
|
||||
}
|
||||
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null;
|
||||
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null, hudNewsCount = null;
|
||||
|
||||
function setMapAttribution(text) {
|
||||
if (!map) return;
|
||||
|
|
@ -2417,6 +2433,57 @@ async function loadBlips() {
|
|||
}
|
||||
}
|
||||
|
||||
/* ── CRITICAL NEWS (LLM-estimated locations, critical/high only) ── */
|
||||
async function toggleNewsPins() {
|
||||
newsOn = document.getElementById('lp-news-on').checked;
|
||||
if (newsOn) {
|
||||
await loadNewsPins();
|
||||
return;
|
||||
}
|
||||
newsReq++;
|
||||
if (newsGroup && map) { map.removeLayer(newsGroup); newsGroup = null; }
|
||||
hudNewsCount = null;
|
||||
const countEl = document.getElementById('lp-news-count');
|
||||
if (countEl) countEl.textContent = '0';
|
||||
syncHud();
|
||||
}
|
||||
async function loadNewsPins() {
|
||||
if (!map) return;
|
||||
const req = ++newsReq;
|
||||
try {
|
||||
const url = `${API}/api/news/map?bbox=${currentBBox()}&limit=200`;
|
||||
const r = await overlayFetch(url);
|
||||
const data = await r.json();
|
||||
if (req !== newsReq) return; // superseded by a newer pan/zoom
|
||||
const items = Array.isArray(data) ? data : [];
|
||||
if (newsGroup) map.removeLayer(newsGroup);
|
||||
const col = '#ff2e97';
|
||||
const pins = items.filter(it => Number.isFinite(Number(it.lat)) && Number.isFinite(Number(it.lon)));
|
||||
newsGroup = L.layerGroup(pins.map(it => {
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<span style="display:inline-block;width:10px;height:10px;background:${col};box-shadow:0 0 8px ${col},0 0 2px #0f172a;border:1px solid #030609;transform:rotate(45deg);"></span>`,
|
||||
iconSize: [14, 14], iconAnchor: [7, 7],
|
||||
});
|
||||
return L.marker([it.lat, it.lon], { icon })
|
||||
.bindPopup(`<div class="blip-pop">` +
|
||||
`<div class="blip-src">◈ ${esc(it.importance || '')}${it.category ? ' · ' + esc(it.category) : ''}</div>` +
|
||||
`<b>${esc(it.headline || '')}</b>` +
|
||||
`<div class="blip-time">${esc(it.location_name || '')}</div>` +
|
||||
(it.url ? `<a href="${esc(it.url)}" target="_blank" rel="noopener">source ↗</a>` : '') +
|
||||
`</div>`);
|
||||
}));
|
||||
newsGroup.addTo(map);
|
||||
document.getElementById('lp-news-count').textContent = pins.length.toLocaleString();
|
||||
hudNewsCount = pins.length;
|
||||
syncHud();
|
||||
} catch(e) {
|
||||
if (isAbort(e)) return;
|
||||
document.getElementById('map-hint').textContent = `News pins load failed: ${e.message || e}`;
|
||||
console.error('News pins load failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Live overlays (radar / alerts / WFIGS / aircraft / trains / AIS / NHC) ── */
|
||||
function refreshLiveOverlays() {
|
||||
if (!map) return;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue