From 9825f4df48b4ba2711d2636abe2e8e1c0786c7b4 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Tue, 24 Mar 2026 10:30:11 +0100 Subject: [PATCH] =?UTF-8?q?GEOINT:=20Stabile=20Flug-/Schiffsdaten=20ohne?= =?UTF-8?q?=20Verz=C3=B6gerung=20oder=20Verschwinden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architektur umgebaut: Daten werden global gecacht, Rendering ist client-seitig. Nur sichtbare Marker werden gerendert (bounds-Filter). Bei moveend (Zoom/Pan) wird sofort aus dem Cache neu gerendert (300ms), kein neuer API-Call noetig. API-Refresh bleibt 30s/60s im Hintergrund. Fix: fehlender break im flights switch-case (ships wurden gestoppt). --- src/static/js/geoint.js | 115 ++++++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 28 deletions(-) diff --git a/src/static/js/geoint.js b/src/static/js/geoint.js index 1979acc..a071950 100644 --- a/src/static/js/geoint.js +++ b/src/static/js/geoint.js @@ -14,6 +14,8 @@ const GEOINT = { _quakeInterval: null, _gdeltInterval: null, _shipsLayer: null, + _flightsData: null, + _shipsData: null, _shipsInterval: null, _flightFetching: false, _moveHandler: null, @@ -166,8 +168,7 @@ const GEOINT = { this._sublayers[id] = enabled; this._saveState(); switch (id) { - case 'flights': enabled ? this._startFlights(map) : this._stopFlights(); - this._stopShips(); break; + case 'flights': enabled ? this._startFlights(map) : this._stopFlights(); break; case 'ships': enabled ? this._startShips(map) : this._stopShips(); break; case 'quakes': enabled ? this._startQuakes(map) : this._stopQuakes(); break; case 'gdelt': enabled ? this._startGdelt(map) : this._stopGdelt(); break; @@ -195,7 +196,15 @@ const GEOINT = { var self = this; this._fetchFlights(map); this._flightInterval = setInterval(function() { self._fetchFlights(map); }, 30000); // 30s global refresh - // Globale Daten - kein moveend-Handler noetig + // Bei Kartenbewegung neu rendern (client-seitig aus Cache) + this._moveHandler = function() { + clearTimeout(self._moveDebounce); + self._moveDebounce = setTimeout(function() { + self._renderFlights(map); + self._renderShips(map); + }, 300); + }; + map.on('moveend', this._moveHandler); }, _stopFlights() { @@ -215,11 +224,9 @@ const GEOINT = { .then(function(r) { return r.ok ? r.json() : { ac: [] }; }) .then(function(data) { if (!self._flightLayer) return; - // Neue Marker in temporaerem Layer bauen, dann atomar swappen - var newLayer = L.layerGroup(); - var ac = data.ac || data.aircraft || []; - ac.slice(0, 500).forEach(function(a) { - if (!a.lat || !a.lon) return; + self._flightsData = data.ac || data.aircraft || []; + self._renderFlights(map); + }) var heading = a.track || a.true_heading || 0; var icon = L.divIcon({ className: '', @@ -241,18 +248,45 @@ const GEOINT = { '
SPD ' + (typeof spd === 'number' ? Math.round(spd) + ' kts' : spd) + '
HDG ' + Math.round(heading) + '°' + ''; - L.marker([a.lat, a.lon], { icon: icon }).bindPopup(popup, { className: 'geoint-leaflet-popup' }).addTo(newLayer); - }); - // Atomar swappen: alte Marker entfernen, neue hinzufuegen - if (self._map && self._flightLayer) { - self._map.removeLayer(self._flightLayer); - self._flightLayer = newLayer.addTo(self._map); - } - }) .catch(function(e) { if (typeof DEV_MODE !== 'undefined' && DEV_MODE) console.warn('GEOINT flights:', e); }) .finally(function() { self._flightFetching = false; }); }, + _renderFlights(map) { + if (!map || !this._flightLayer || !this._flightsData) return; + var newLayer = L.layerGroup(); + var bounds = map.getBounds(); + this._flightsData.forEach(function(a) { + if (!a.lat || !a.lon || !bounds.contains([a.lat, a.lon])) return; + var heading = a.track || a.true_heading || 0; + var icon = L.divIcon({ + className: '', + html: '
' + + '' + + '' + + '
', + iconSize: [14, 14], + iconAnchor: [7, 7], + }); + var callsign = (a.flight || a.callsign || a.hex || '???').trim(); + var alt = a.alt_baro || a.altitude || '?'; + var spd = a.gs || a.ground_speed || '?'; + var typ = a.t || a.type || ''; + var popup = '
' + + '' + callsign + '' + + (typ ? ' (' + typ + ')' : '') + + '
ALT ' + (typeof alt === 'number' ? alt.toLocaleString() + ' ft' : alt) + + '
SPD ' + (typeof spd === 'number' ? Math.round(spd) + ' kts' : spd) + + '
HDG ' + Math.round(heading) + '\u00b0' + + '
'; + L.marker([a.lat, a.lon], { icon: icon }).bindPopup(popup, { className: 'geoint-leaflet-popup' }).addTo(newLayer); + }); + if (this._map) { + this._map.removeLayer(this._flightLayer); + this._flightLayer = newLayer.addTo(this._map); + } + }, + // ----------------------------------------------------------------------- // Layer: Schiffsverkehr (Digitraffic AIS) @@ -280,10 +314,9 @@ const GEOINT = { .then(function(r) { return r.ok ? r.json() : { ships: [] }; }) .then(function(data) { if (!self._shipsLayer) return; - var newLayer = L.layerGroup(); - var ships = data.ships || []; - ships.forEach(function(s) { - if (!s.lat || !s.lon) return; + self._shipsData = data.ships || []; + self._renderShips(map); + }) var heading = s.heading || s.cog || 0; var sog = s.sog || 0; // Nur Schiffe mit Bewegung oder in Hafennaehe anzeigen @@ -305,17 +338,43 @@ const GEOINT = { '
COG ' + Math.round(s.cog || 0) + '\u00b0' + '
NAV ' + navText + ''; - L.marker([s.lat, s.lon], { icon: icon }).bindPopup(popup, { className: 'geoint-leaflet-popup' }).addTo(newLayer); - }); - // Atomar swappen - if (self._map && self._shipsLayer) { - self._map.removeLayer(self._shipsLayer); - self._shipsLayer = newLayer.addTo(self._map); - } - }) .catch(function(e) { if (typeof DEV_MODE !== 'undefined' && DEV_MODE) console.warn('GEOINT ships:', e); }); }, + _renderShips(map) { + if (!map || !this._shipsLayer || !this._shipsData) return; + var newLayer = L.layerGroup(); + var bounds = map.getBounds(); + this._shipsData.forEach(function(s) { + if (!s.lat || !s.lon || !bounds.contains([s.lat, s.lon])) return; + var heading = s.heading || s.cog || 0; + var sog = s.sog || 0; + var icon = L.divIcon({ + className: '', + html: '
' + + '' + + '' + + '
', + iconSize: [10, 10], + iconAnchor: [5, 5], + }); + var mmsi = s.mmsi || '?'; + var navLabels = {0:'Motorbetrieb', 1:'Vor Anker', 2:'Nicht steuerbar', 3:'Eingeschraenkt', 5:'Festgemacht', 7:'Fischfang', 8:'Unter Segel'}; + var navText = navLabels[s.navStat] || 'Status ' + s.navStat; + var popup = '
' + + 'MMSI ' + mmsi + '' + + '
SOG ' + sog.toFixed(1) + ' kn' + + '
COG ' + Math.round(s.cog || 0) + '\u00b0' + + '
NAV ' + navText + + '
'; + L.marker([s.lat, s.lon], { icon: icon }).bindPopup(popup, { className: 'geoint-leaflet-popup' }).addTo(newLayer); + }); + if (this._map) { + this._map.removeLayer(this._shipsLayer); + this._shipsLayer = newLayer.addTo(this._map); + } + }, + // ----------------------------------------------------------------------- // Layer: Erdbeben // -----------------------------------------------------------------------