feat: GEOINT-Toolkit mit 6 neuen Features

- EXIF-Extraktion: Automatische GPS/Kamera/Zeitstempel-Analyse bei Bildupload
- Sonnenstand-Rechner: Azimut, Elevation, Schattenverhaeltnis fuer beliebige Position/Zeit
- Reverse Geolocation: Erweiterte VLM-Analyse mit Landschaftsmerkmalen (Vegetation, Architektur, Strassen, Schilder)
- Nachtlichter: NASA VIIRS Black Marble Layer
- Hoehenprofil: Interaktives 2-Punkte-Tool mit SVG-Chart und Sichtlinienanalyse
- Funkmasten: Mobilfunkinfrastruktur via Overpass (zoomabhaengig)

Backend: data_geoint.py (EXIF, Sun, Elevation, Celltowers)
Frontend: GEOINT Tools Section im Layer Panel
Dieser Commit ist enthalten in:
Claude Dev
2026-03-26 08:58:05 +01:00
Ursprung 1b74c95bac
Commit c7cb19d584
10 geänderte Dateien mit 965 neuen und 4 gelöschten Zeilen

120
static/js/layers/celltowers.js Normale Datei
Datei anzeigen

@@ -0,0 +1,120 @@
/**
* Funkmasten-Layer: Mobilfunkmasten aus OpenStreetMap via Overpass.
*/
const CelltowersLayer = {
_viewer: null,
_points: null,
_labels: null,
_count: 0,
_interval: null,
_lastBbox: null,
start: function(viewer) {
if (this._points) return;
this._viewer = viewer;
this._points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());
this._labels = viewer.scene.primitives.add(new Cesium.LabelCollection());
this._fetch();
var self = this;
this._interval = setInterval(function() { self._fetchIfMoved(); }, 8000);
},
stop: function() {
if (this._interval) { clearInterval(this._interval); this._interval = null; }
if (this._points && this._viewer) { this._viewer.scene.primitives.remove(this._points); this._points = null; }
if (this._labels && this._viewer) { this._viewer.scene.primitives.remove(this._labels); this._labels = null; }
this._count = 0;
this._lastBbox = null;
var c = document.getElementById('count-celltowers');
if (c) c.textContent = '-';
},
_getBbox: function() {
var rect = this._viewer.camera.computeViewRectangle();
if (!rect) return null;
return {
south: Cesium.Math.toDegrees(rect.south),
west: Cesium.Math.toDegrees(rect.west),
north: Cesium.Math.toDegrees(rect.north),
east: Cesium.Math.toDegrees(rect.east),
};
},
_fetchIfMoved: function() {
var bbox = this._getBbox();
if (!bbox) return;
// Nur bei deutlicher Bewegung neu laden
if (this._lastBbox) {
var dLat = Math.abs(bbox.south - this._lastBbox.south) + Math.abs(bbox.north - this._lastBbox.north);
var dLon = Math.abs(bbox.west - this._lastBbox.west) + Math.abs(bbox.east - this._lastBbox.east);
if (dLat + dLon < 0.5) return;
}
// Nur bei Zoom nah genug (< ~500 km Sichtfeld)
var cam = this._viewer.camera.positionCartographic;
if (cam && cam.height > 600000) return;
this._fetch();
},
_fetch: function() {
var bbox = this._getBbox();
if (!bbox) return;
// Viewport zu gross -> nicht laden
if (Math.abs(bbox.north - bbox.south) > 5 || Math.abs(bbox.east - bbox.west) > 5) {
var s = document.getElementById('status-celltowers');
if (s) { s.textContent = 'Naeher heranzoomen'; s.classList.add('active'); }
return;
}
this._lastBbox = bbox;
var self = this;
fetch('/api/geoint/celltowers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(bbox),
})
.then(function(r) { return r.json(); })
.then(function(data) {
self._render(data.towers || []);
})
.catch(function() {});
},
_render: function(towers) {
if (!this._points) return;
this._points.removeAll();
this._labels.removeAll();
var color = Cesium.Color.fromCssColorString('#e040fb');
var colorDim = color.withAlpha(0.6);
for (var i = 0; i < towers.length; i++) {
var t = towers[i];
this._points.add({
position: Cesium.Cartesian3.fromDegrees(t.lon, t.lat, 0),
pixelSize: 6,
color: color,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 1,
});
var label = t.operator || t.name || '';
if (label && towers.length < 200) {
this._labels.add({
position: Cesium.Cartesian3.fromDegrees(t.lon, t.lat, 0),
text: label,
font: '9px monospace',
fillColor: colorDim,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(6, -4),
scale: 0.6,
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 100000),
});
}
}
this._count = towers.length;
var c = document.getElementById('count-celltowers');
if (c) c.textContent = this._count.toLocaleString('de-DE');
var s = document.getElementById('status-celltowers');
if (s) { s.textContent = ''; s.classList.remove('active'); }
},
};