From 734c310d2c7608d2dc0d71bcc08df43eaab0ac4e Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 27 Aug 2026 21:23:25 -0400 Subject: [PATCH] perf: abort stale overlay fetches, skip same-cell rebuilds, update markers in place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit moveend aborts in-flight overlay JSON and skips work when the 0.01° cell is unchanged. Live aircraft/trains/vessels diff-update by id. Camera popup HTML is built on open. Alert/perimeter polygons paint on canvas. --- app/static/index.html | 124 ++++++++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index 0b8242a..4012750 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1650,6 +1650,16 @@ let vesselsGroup = null, vesselsOn = false; let stormsGroup = null, stormsOn = true; let overlayReq = {ac:0, trains:0, vessels:0, alerts:0, perim:0, incidents:0, storms:0}; let moveDebounce = null; +let overlayAbort = null; +let lastCell = ''; +function bboxCell() { + if (!map) return ''; + return currentBBox().split(',').map(n => Number(n).toFixed(2)).join(',') + '@' + map.getZoom(); +} +function overlayFetch(url) { + return fetch(url, overlayAbort ? { signal: overlayAbort.signal } : {}); +} +function isAbort(e) { return e && e.name === 'AbortError'; } const pointCanvas = () => L.canvas({ padding: 0.5 }); async function initMap() { @@ -1733,6 +1743,11 @@ async function initMap() { if (camPopupOpen) return; // only the popup's own autopan now if (moveDebounce) clearTimeout(moveDebounce); moveDebounce = setTimeout(() => { + const cell = bboxCell(); + if (cell === lastCell) return; + lastCell = cell; + if (overlayAbort) overlayAbort.abort(); + overlayAbort = new AbortController(); if (firesOn) loadFires(); if (camsOn) loadCams(); if (blipsOn) loadBlips(); @@ -1977,7 +1992,7 @@ async function loadFires() { let url = `${API}/api/fires?bbox=${currentBBox()}&limit=2000&format=heat`; const since = sinceToISO(firesSince); if (since) url += `&since=${encodeURIComponent(since)}`; - const r = await fetch(url); + const r = await overlayFetch(url); const fires = await r.json(); if (req !== fireReq) return; // superseded by a newer pan/zoom if (firesHeat) map.removeLayer(firesHeat); @@ -1994,6 +2009,7 @@ async function loadFires() { `${fires.length.toLocaleString()} fire hotspots in view` + (since ? ` · since ${since.slice(0,16).replace('T',' ')}Z` : ''); } catch(e) { + if (isAbort(e)) return; document.getElementById('map-hint').textContent = `Fires load failed: ${e.message || e}`; console.error('Fires load failed', e); } @@ -2068,7 +2084,7 @@ async function loadCams() { } const req = ++camReq; try { - const r = await fetch(`${API}/api/cameras?bbox=${currentBBox()}&limit=2000`); + const r = await overlayFetch(`${API}/api/cameras?bbox=${currentBBox()}&limit=2000`); const cams = await r.json(); if (req !== camReq) return; // superseded by a newer pan/zoom if (camsGroup) map.removeLayer(camsGroup); @@ -2092,7 +2108,7 @@ async function loadCams() { iconSize: [12, 12], iconAnchor: [6, 6], }); camsGroup.addLayer(L.marker([c.lat, c.lon], { icon }) - .bindPopup(`
` + + .bindPopup(() => `
` + `${esc(c.location_name || 'Open camera')}` + `${c.id ? camThumb(c) : '
no snapshot
'}` + `` + @@ -2113,6 +2129,7 @@ async function loadCams() { document.getElementById('map-hint').textContent = `${cams.length.toLocaleString()} open cameras in view`; } catch(e) { + if (isAbort(e)) return; document.getElementById('map-hint').textContent = `Cameras load failed: ${e.message || e}`; console.error('Cameras load failed', e); } @@ -2148,7 +2165,7 @@ async function loadBlips() { let url = `${API}/api/events?bbox=${currentBBox()}&has_coords=true&limit=500`; const since = sinceToISO(blipsSince); if (since) url += `&since=${encodeURIComponent(since)}`; - const r = await fetch(url); + const r = await overlayFetch(url); const evs = (await r.json()).filter(ev => ev.source_type !== 'camera'); if (req !== blipReq) return; // superseded by a newer pan/zoom if (blipsGroup) map.removeLayer(blipsGroup); @@ -2178,6 +2195,7 @@ async function loadBlips() { `${evs.length.toLocaleString()} event blips in view`; } } catch(e) { + if (isAbort(e)) return; document.getElementById('map-hint').textContent = `Blips load failed: ${e.message || e}`; console.error('Blips load failed', e); } @@ -2289,33 +2307,69 @@ function feedIcon(feed, color, heading) { } return ic; } +function makePointMarker(p, colorFn, feed, renderer) { + if (p.lat == null || p.lon == null) return null; + const col = sanitizeColor(colorFn(p), '#35e0ff'); + if (feed) { + const heading = Number(p.heading); + const icon = feedIcon(feed, col, Number.isNaN(heading) ? null : heading); + return L.marker([p.lat, p.lon], { icon }).bindPopup(() => pointPopup(p)); + } + return L.circleMarker([p.lat, p.lon], { + radius: 5, color: col, fillColor: col, fillOpacity: 0.9, weight: 1, + renderer, + }).bindPopup(() => pointPopup(p)); +} function renderPoints(existing, points, colorFn, cluster, feed) { - if (existing) map.removeLayer(existing); const zoom = map.getZoom(); const useCluster = cluster && (zoom < 7 || points.length > 200); - const group = useCluster - ? L.markerClusterGroup({ maxClusterRadius: 48, showCoverageOnHover: false, spiderfyOnMaxZoom: true, chunkedLoading: true }) - : L.layerGroup(); + const canReuse = existing && map.hasLayer(existing) + && !!existing._osintCluster === !!useCluster + && existing._osintById; + if (!canReuse) { + if (existing) map.removeLayer(existing); + const group = useCluster + ? L.markerClusterGroup({ maxClusterRadius: 48, showCoverageOnHover: false, spiderfyOnMaxZoom: true, chunkedLoading: true }) + : L.layerGroup(); + group._osintCluster = !!useCluster; + group._osintById = new Map(); + const renderer = pointCanvas(); + points.forEach(p => { + const m = makePointMarker(p, colorFn, feed, renderer); + if (!m) return; + group.addLayer(m); + if (p.id != null) group._osintById.set(String(p.id), m); + }); + group.addTo(map); + return group; + } + const group = existing; + const byId = group._osintById; + const next = new Set(); const renderer = pointCanvas(); points.forEach(p => { - if (p.lat == null || p.lon == null) return; + if (p.lat == null || p.lon == null || p.id == null) return; + const id = String(p.id); + next.add(id); const col = sanitizeColor(colorFn(p), '#35e0ff'); - if (feed) { - const heading = Number(p.heading); - const icon = feedIcon(feed, col, Number.isNaN(heading) ? null : heading); - const m = L.marker([p.lat, p.lon], { icon }).bindPopup(pointPopup(p)); - group.addLayer(m); + const m = byId.get(id); + if (m) { + m.setLatLng([p.lat, p.lon]); + if (feed) m.setIcon(feedIcon(feed, col, p.heading)); + else if (m.setStyle) m.setStyle({ color: col, fillColor: col }); } else { - const heading = Number(p.heading); - const m = L.circleMarker([p.lat, p.lon], { - radius: 5, color: col, fillColor: col, fillOpacity: 0.9, weight: 1, - renderer, - }).bindPopup(pointPopup(p)); - if (!Number.isNaN(heading)) m.setStyle({ className: 'hdg' }); - group.addLayer(m); + const nm = makePointMarker(p, colorFn, feed, renderer); + if (!nm) return; + group.addLayer(nm); + byId.set(id, nm); + } + }); + byId.forEach((m, id) => { + if (!next.has(id)) { + group.removeLayer(m); + byId.delete(id); } }); - group.addTo(map); return group; } async function toggleRadar() { @@ -2332,7 +2386,7 @@ async function loadRadar() { if (!map) return; try { if (!radarMeta) { - const r = await fetch(`${API}/api/map/radar`); + const r = await overlayFetch(`${API}/api/map/radar`); radarMeta = await r.json(); addExtraAttrib('Weather data by RainViewer'); addExtraAttrib('Iowa Environmental Mesonet'); @@ -2351,6 +2405,7 @@ async function loadRadar() { radarLayer = L.tileLayer(url, { opacity: radarOpacity, maxZoom: 12, maxNativeZoom: useIem ? 18 : 7, attribution: '' }).addTo(map); } } catch (e) { + if (isAbort(e)) return; console.error('Radar load failed', e); document.getElementById('lp-radar-count').textContent = 'err'; } @@ -2383,12 +2438,13 @@ async function loadWxAlerts() { } const req = ++overlayReq.alerts; try { - const r = await fetch(`${API}/api/weather-alerts?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/weather-alerts?bbox=${currentBBox()}`); const fc = await r.json(); if (req !== overlayReq.alerts) return; wxAlertsGroup = dropLayer(wxAlertsGroup); const feats = fc.features || []; wxAlertsGroup = L.geoJSON(fc, { + renderer: L.canvas({ padding: 0.5 }), style: (f) => ({ color: severityColor((f.properties || {}).severity), weight: 2, fillOpacity: 0.18, @@ -2401,6 +2457,7 @@ async function loadWxAlerts() { document.getElementById('lp-alerts-count').textContent = feats.length.toLocaleString(); addExtraAttrib('NWS / IEM storm-based warnings'); } catch (e) { + if (isAbort(e)) return; console.error('Alerts load failed', e); document.getElementById('lp-alerts-count').textContent = 'err'; } @@ -2419,12 +2476,13 @@ async function loadPerimeters() { } const req = ++overlayReq.perim; try { - const r = await fetch(`${API}/api/fire-perimeters?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/fire-perimeters?bbox=${currentBBox()}`); const fc = await r.json(); if (req !== overlayReq.perim) return; perimGroup = dropLayer(perimGroup); const feats = fc.features || []; perimGroup = L.geoJSON(fc, { + renderer: L.canvas({ padding: 0.5 }), style: (f) => { const acres = Number((f.properties || {}).poly_GISAcres || (f.properties || {}).attr_IncidentSize || 0); return { color: acres > 10000 ? '#ef4444' : '#fb923c', weight: 2, fillOpacity: 0.25, fillColor: '#fb923c' }; @@ -2439,6 +2497,7 @@ async function loadPerimeters() { document.getElementById('lp-perim-count').textContent = feats.length.toLocaleString(); addExtraAttrib('NIFC WFIGS'); } catch (e) { + if (isAbort(e)) return; console.error('Perimeters load failed', e); document.getElementById('lp-perim-count').textContent = 'err'; } @@ -2457,13 +2516,14 @@ async function loadIncidents() { } const req = ++overlayReq.incidents; try { - const r = await fetch(`${API}/api/fire-incidents?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/fire-incidents?bbox=${currentBBox()}`); const pts = await r.json(); if (req !== overlayReq.incidents) return; incidentsGroup = renderPoints(incidentsGroup, pts, () => '#ef4444', false); document.getElementById('lp-incidents-count').textContent = pts.length.toLocaleString(); addExtraAttrib('NIFC WFIGS'); } catch (e) { + if (isAbort(e)) return; console.error('Incidents load failed', e); document.getElementById('lp-incidents-count').textContent = 'err'; } @@ -2481,13 +2541,14 @@ async function loadAircraft() { } const req = ++overlayReq.ac; try { - const r = await fetch(`${API}/api/aircraft?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/aircraft?bbox=${currentBBox()}`); const pts = await r.json(); if (req !== overlayReq.ac) return; acGroup = renderPoints(acGroup, Array.isArray(pts) ? pts : [], p => altColor((p.extra || {}).alt_baro), true, 'ac'); document.getElementById('lp-ac-count').textContent = (pts.length || 0).toLocaleString(); addExtraAttrib('ADSB.lol ODbL'); } catch (e) { + if (isAbort(e)) return; console.error('Aircraft load failed', e); document.getElementById('lp-ac-count').textContent = 'err'; } @@ -2501,13 +2562,14 @@ async function loadTrains() { if (!map) return; const req = ++overlayReq.trains; try { - const r = await fetch(`${API}/api/trains?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/trains?bbox=${currentBBox()}`); const pts = await r.json(); if (req !== overlayReq.trains) return; trainsGroup = renderPoints(trainsGroup, Array.isArray(pts) ? pts : [], p => (p.extra || {}).iconColor || '#c084fc', false, 'train'); document.getElementById('lp-trains-count').textContent = (pts.length || 0).toLocaleString(); addExtraAttrib('Amtraker'); } catch (e) { + if (isAbort(e)) return; console.error('Trains load failed', e); document.getElementById('lp-trains-count').textContent = 'err'; } @@ -2525,7 +2587,7 @@ async function loadVessels() { } const req = ++overlayReq.vessels; try { - const r = await fetch(`${API}/api/vessels?bbox=${currentBBox()}`); + const r = await overlayFetch(`${API}/api/vessels?bbox=${currentBBox()}`); const pts = await r.json(); if (req !== overlayReq.vessels) return; vesselsGroup = renderPoints(vesselsGroup, Array.isArray(pts) ? pts : [], p => { @@ -2535,6 +2597,7 @@ async function loadVessels() { document.getElementById('lp-vessels-count').textContent = (pts.length || 0).toLocaleString(); addExtraAttrib('AISStream'); } catch (e) { + if (isAbort(e)) return; console.error('Vessels load failed', e); document.getElementById('lp-vessels-count').textContent = 'err'; } @@ -2548,13 +2611,14 @@ async function loadStorms() { if (!map) return; const req = ++overlayReq.storms; try { - const r = await fetch(`${API}/api/storms`); + const r = await overlayFetch(`${API}/api/storms`); const pts = await r.json(); if (req !== overlayReq.storms) return; stormsGroup = renderPoints(stormsGroup, Array.isArray(pts) ? pts : [], () => '#f472b6', false); document.getElementById('lp-storms-count').textContent = (pts.length || 0).toLocaleString(); addExtraAttrib('NHC'); } catch (e) { + if (isAbort(e)) return; console.error('Storms load failed', e); document.getElementById('lp-storms-count').textContent = 'err'; }