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

Datei anzeigen

@@ -189,13 +189,55 @@ const VlmUI = {
var resetBtn = document.getElementById('vlm-reset-btn');
if (!resultsEl) return;
// EXIF-Metadaten
var exifHtml = '';
if (data.exif) {
var e = data.exif;
var ep = [];
if (e.has_gps) ep.push('<span style="color:var(--accent)">GPS: ' + e.latitude + ', ' + e.longitude + '</span>');
if (e.altitude) ep.push('Hoehe: ' + e.altitude + 'm');
if (e.camera_model) ep.push(e.camera_make ? e.camera_make + ' ' + e.camera_model : e.camera_model);
if (e.focal_length) ep.push(e.focal_length + 'mm');
if (e.timestamp) ep.push(e.timestamp.replace('T', ' '));
if (e.compass_heading) ep.push('Heading: ' + e.compass_heading + '\u00B0');
if (ep.length > 0) {
exifHtml = '<div class="vlm-exif-card"><div style="font-size:9px;letter-spacing:1.5px;color:#e040fb;margin-bottom:4px">EXIF METADATEN</div>' +
'<div style="font-size:11px;color:var(--text);line-height:1.6">' + ep.join(' &middot; ') + '</div></div>';
if (e.has_gps) {
exifHtml += '<button class="geoint-tool-btn" style="width:100%;margin-top:4px" onclick="Globe.viewer.camera.flyTo({destination:Cesium.Cartesian3.fromDegrees(' + e.longitude + ',' + e.latitude + ',50000),duration:2})">GPS-Position anfliegen</button>';
}
} else {
exifHtml = '<div class="vlm-exif-card"><div style="font-size:9px;letter-spacing:1.5px;color:var(--text-dim);margin-bottom:2px">EXIF</div><div style="font-size:11px;color:var(--text-dim)">Keine Metadaten gefunden</div></div>';
}
}
// Landscape Clues (Reverse Geolocation)
var cluesHtml = '';
if (data.landscape_clues) {
var lc = data.landscape_clues;
var cl = [];
if (lc.vegetation) cl.push('<b>Vegetation:</b> ' + lc.vegetation);
if (lc.soil_color) cl.push('<b>Boden:</b> ' + lc.soil_color);
if (lc.road_markings) cl.push('<b>Strassen:</b> ' + lc.road_markings);
if (lc.architecture_style) cl.push('<b>Architektur:</b> ' + lc.architecture_style);
if (lc.signage_language) cl.push('<b>Schilder:</b> ' + lc.signage_language);
if (lc.vehicle_types) cl.push('<b>Fahrzeuge:</b> ' + lc.vehicle_types);
if (lc.climate_indicators) cl.push('<b>Klima:</b> ' + lc.climate_indicators);
if (lc.sun_shadow_direction) cl.push('<b>Schatten:</b> ' + lc.sun_shadow_direction);
if (cl.length > 0) {
cluesHtml = '<div style="font-size:9px;letter-spacing:1.5px;color:#ff9800;margin:8px 0 4px">LANDSCHAFTSMERKMALE</div>' +
'<div style="font-size:11px;color:var(--text);line-height:1.8">' + cl.join('<br>') + '</div>';
}
}
// Szene
var sceneEl = document.getElementById('vlm-scene');
if (sceneEl) {
var text = data.scene_description || 'Keine Beschreibung';
if (data.terrain) text += ' | Gelände: ' + data.terrain;
if (data.estimated_location_type) text += ' | Region: ' + data.estimated_location_type;
sceneEl.textContent = text;
sceneEl.innerHTML = exifHtml +
'<div style="margin-top:8px">' + (data.scene_description || 'Keine Beschreibung') +
(data.terrain ? ' | Gelaende: ' + data.terrain : '') +
(data.estimated_location_type ? ' | Region: ' + data.estimated_location_type : '') +
'</div>' + cluesHtml;
}
// Objekte