GEOINT: Globaler Flugverkehr + Schiffsverkehr-Layer
Flugverkehr: Globaler Snapshot ueber 29 Stuetzpunkte weltweit. Backend aggregiert parallel, 30s Cache, kein Flackern (atomarer Swap). Keine regionale Begrenzung mehr. Schiffsverkehr: Neuer Layer via Digitraffic AIS API (kostenlos, kein Key). 18.000+ Schiffe global, 60s Refresh. Blaue Schiffs-Icons mit Heading-Rotation. Popup zeigt MMSI, SOG, COG, Navigationsstatus. Backend: Batch-Fetching mit asyncio.Lock gegen Race Conditions.
Dieser Commit ist enthalten in:
@@ -13,6 +13,8 @@ const GEOINT = {
|
||||
_flightInterval: null,
|
||||
_quakeInterval: null,
|
||||
_gdeltInterval: null,
|
||||
_shipsLayer: null,
|
||||
_shipsInterval: null,
|
||||
_flightFetching: false,
|
||||
_moveHandler: null,
|
||||
_coordControl: null,
|
||||
@@ -121,6 +123,7 @@ const GEOINT = {
|
||||
div.innerHTML =
|
||||
'<h4>GEOINT Layer</h4>' +
|
||||
self._subItemHtml('flights', 'Flugverkehr', 'flights') +
|
||||
self._subItemHtml('ships', 'Schiffsverkehr', 'ships') +
|
||||
self._subItemHtml('quakes', 'Erdbeben', 'quakes') +
|
||||
self._subItemHtml('gdelt', 'Nachrichten', 'gdelt') +
|
||||
'<div class="geoint-sub-separator"></div>' +
|
||||
@@ -134,7 +137,7 @@ const GEOINT = {
|
||||
map.addControl(this._subControl);
|
||||
|
||||
// Event-Listener fuer Sub-Checkboxen
|
||||
['flights', 'quakes', 'gdelt', 'heatmap', 'coords', 'distance'].forEach(function(id) {
|
||||
['flights', 'ships', 'quakes', 'gdelt', 'heatmap', 'coords', 'distance'].forEach(function(id) {
|
||||
var cb = document.getElementById('geoint-sub-' + id);
|
||||
if (cb) {
|
||||
cb.addEventListener('change', function() {
|
||||
@@ -163,7 +166,9 @@ const GEOINT = {
|
||||
this._sublayers[id] = enabled;
|
||||
this._saveState();
|
||||
switch (id) {
|
||||
case 'flights': enabled ? this._startFlights(map) : this._stopFlights(); break;
|
||||
case 'flights': enabled ? this._startFlights(map) : this._stopFlights();
|
||||
this._stopShips(); 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;
|
||||
case 'heatmap': enabled ? this._startHeatmap(map) : this._stopHeatmap(); break;
|
||||
@@ -189,13 +194,8 @@ const GEOINT = {
|
||||
this._flightLayer = L.layerGroup().addTo(map);
|
||||
var self = this;
|
||||
this._fetchFlights(map);
|
||||
this._flightInterval = setInterval(function() { self._fetchFlights(map); }, 30000);
|
||||
// Bei Kartenbewegung neu laden
|
||||
this._moveHandler = function() {
|
||||
clearTimeout(self._moveDebounce);
|
||||
self._moveDebounce = setTimeout(function() { self._fetchFlights(map); }, 2000);
|
||||
};
|
||||
map.on('moveend', this._moveHandler);
|
||||
this._flightInterval = setInterval(function() { self._fetchFlights(map); }, 30000); // 30s global refresh
|
||||
// Globale Daten - kein moveend-Handler noetig
|
||||
},
|
||||
|
||||
_stopFlights() {
|
||||
@@ -205,14 +205,13 @@ const GEOINT = {
|
||||
},
|
||||
|
||||
_fetchFlights(map) {
|
||||
if (this._flightFetching || !map || map.getZoom() < 3) return;
|
||||
if (this._flightFetching || !map) return;
|
||||
this._flightFetching = true;
|
||||
var center = map.getCenter();
|
||||
var self = this;
|
||||
var token = localStorage.getItem('osint_token') || '';
|
||||
var headers = token ? { 'Authorization': 'Bearer ' + token } : {};
|
||||
|
||||
fetch('/api/geoint/flights?lat=' + center.lat.toFixed(2) + '&lon=' + center.lng.toFixed(2) + '&radius=250', { headers: headers })
|
||||
fetch('/api/geoint/flights', { headers: headers })
|
||||
.then(function(r) { return r.ok ? r.json() : { ac: [] }; })
|
||||
.then(function(data) {
|
||||
if (!self._flightLayer) return;
|
||||
@@ -254,6 +253,69 @@ const GEOINT = {
|
||||
.finally(function() { self._flightFetching = false; });
|
||||
},
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Layer: Schiffsverkehr (Digitraffic AIS)
|
||||
// -----------------------------------------------------------------------
|
||||
_startShips(map) {
|
||||
if (this._shipsLayer) return;
|
||||
this._shipsLayer = L.layerGroup().addTo(map);
|
||||
var self = this;
|
||||
this._fetchShips(map);
|
||||
this._shipsInterval = setInterval(function() { self._fetchShips(map); }, 60000); // 60s
|
||||
},
|
||||
|
||||
_stopShips() {
|
||||
if (this._shipsInterval) { clearInterval(this._shipsInterval); this._shipsInterval = null; }
|
||||
if (this._shipsLayer && this._map) { this._map.removeLayer(this._shipsLayer); this._shipsLayer = null; }
|
||||
},
|
||||
|
||||
_fetchShips(map) {
|
||||
if (!map) return;
|
||||
var self = this;
|
||||
var token = localStorage.getItem('osint_token') || '';
|
||||
var headers = token ? { 'Authorization': 'Bearer ' + token } : {};
|
||||
|
||||
fetch('/api/geoint/ships', { headers: headers })
|
||||
.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;
|
||||
var heading = s.heading || s.cog || 0;
|
||||
var sog = s.sog || 0;
|
||||
// Nur Schiffe mit Bewegung oder in Hafennaehe anzeigen
|
||||
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);
|
||||
});
|
||||
// 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); });
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Layer: Erdbeben
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren