GEOINT: Stabile Flug-/Schiffsdaten ohne Verzögerung oder Verschwinden

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).
Dieser Commit ist enthalten in:
Claude Dev
2026-03-24 10:30:11 +01:00
Ursprung 7f09375aed
Commit 9825f4df48

Datei anzeigen

@@ -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 = {
'<br><span class="geoint-popup-key">SPD</span> ' + (typeof spd === 'number' ? Math.round(spd) + ' kts' : spd) +
'<br><span class="geoint-popup-key">HDG</span> ' + Math.round(heading) + '°' +
'</div>';
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: '<div class="geoint-aircraft" style="transform:rotate(' + heading + 'deg)">' +
'<svg viewBox="0 0 24 24" fill="#00ff88" stroke="#004422" stroke-width="1">' +
'<path d="M12 2L8 10h-4l2 4-2 4h4l4 4 4-4h4l-2-4 2-4h-4z"/>' +
'</svg></div>',
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 = '<div class="geoint-popup">' +
'<strong>' + callsign + '</strong>' +
(typ ? ' <span style="opacity:0.6">(' + typ + ')</span>' : '') +
'<br><span class="geoint-popup-key">ALT</span> ' + (typeof alt === 'number' ? alt.toLocaleString() + ' ft' : alt) +
'<br><span class="geoint-popup-key">SPD</span> ' + (typeof spd === 'number' ? Math.round(spd) + ' kts' : spd) +
'<br><span class="geoint-popup-key">HDG</span> ' + Math.round(heading) + '\u00b0' +
'</div>';
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 = {
'<br><span class="geoint-popup-key">COG</span> ' + Math.round(s.cog || 0) + '\u00b0' +
'<br><span class="geoint-popup-key">NAV</span> ' + navText +
'</div>';
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: '<div class="geoint-ship" style="transform:rotate(' + heading + 'deg)">' +
'<svg viewBox="0 0 24 24" width="10" height="10">' +
'<path d="M12 2l-4 8h-3l3 12h8l3-12h-3z" fill="' + (sog > 0.5 ? '#4499ff' : '#666688') + '" stroke="#223355" stroke-width="1"/>' +
'</svg></div>',
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 = '<div class="geoint-popup">' +
'<strong>MMSI ' + mmsi + '</strong>' +
'<br><span class="geoint-popup-key">SOG</span> ' + sog.toFixed(1) + ' kn' +
'<br><span class="geoint-popup-key">COG</span> ' + Math.round(s.cog || 0) + '\u00b0' +
'<br><span class="geoint-popup-key">NAV</span> ' + navText +
'</div>';
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
// -----------------------------------------------------------------------