- 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
222 Zeilen
13 KiB
JavaScript
222 Zeilen
13 KiB
JavaScript
/**
|
|
* GEOINT Tools: Sonnenstand-Rechner, Hoehenprofil, EXIF-Anzeige im VLM-Workflow.
|
|
*/
|
|
const GeointTools = {
|
|
_panel: null,
|
|
_elevMode: false,
|
|
_elevStart: null,
|
|
_elevHandler: null,
|
|
_elevLine: null,
|
|
_elevLabels: null,
|
|
|
|
// ============================================================
|
|
// Sonnenstand-Rechner
|
|
// ============================================================
|
|
showSunCalc: function() {
|
|
var panel = document.getElementById('geoint-panel');
|
|
if (!panel) return;
|
|
// Default: aktuelle Cursor-Position oder Kartenmitte
|
|
var cam = Globe.viewer.camera.positionCartographic;
|
|
var lat = cam ? Cesium.Math.toDegrees(cam.latitude).toFixed(4) : '50.0';
|
|
var lon = cam ? Cesium.Math.toDegrees(cam.longitude).toFixed(4) : '10.0';
|
|
var now = new Date().toISOString().slice(0, 16);
|
|
|
|
panel.innerHTML =
|
|
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">' +
|
|
'<div style="font-size:11px;font-weight:700;letter-spacing:2px;color:var(--accent)">SONNENSTAND</div>' +
|
|
'<button onclick="GeointTools.closePanel()" style="background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:18px">×</button>' +
|
|
'</div>' +
|
|
'<div style="display:flex;flex-direction:column;gap:6px;font-size:11px">' +
|
|
'<div style="display:flex;gap:6px">' +
|
|
'<div style="flex:1"><label style="color:var(--text-dim)">Breitengrad</label><input type="number" id="sun-lat" value="' + lat + '" step="0.01" class="geoint-input"></div>' +
|
|
'<div style="flex:1"><label style="color:var(--text-dim)">Laengengrad</label><input type="number" id="sun-lon" value="' + lon + '" step="0.01" class="geoint-input"></div>' +
|
|
'</div>' +
|
|
'<div><label style="color:var(--text-dim)">Datum / Uhrzeit (UTC)</label><input type="datetime-local" id="sun-datetime" value="' + now + '" class="geoint-input"></div>' +
|
|
'<button class="overpass-exec-btn" onclick="GeointTools._calcSun()" style="margin-top:4px">BERECHNEN</button>' +
|
|
'</div>' +
|
|
'<div id="sun-result" style="margin-top:10px"></div>';
|
|
panel.style.display = 'block';
|
|
},
|
|
|
|
_calcSun: function() {
|
|
var lat = parseFloat(document.getElementById('sun-lat').value);
|
|
var lon = parseFloat(document.getElementById('sun-lon').value);
|
|
var dt = document.getElementById('sun-datetime').value;
|
|
if (isNaN(lat) || isNaN(lon) || !dt) return;
|
|
|
|
var resultEl = document.getElementById('sun-result');
|
|
resultEl.innerHTML = '<div style="color:var(--text-dim);font-size:11px">Berechne...</div>';
|
|
|
|
fetch('/api/geoint/sun-position', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ latitude: lat, longitude: lon, datetime_iso: dt + ':00Z' }),
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.detail) { resultEl.innerHTML = '<div style="color:#ff5252">' + data.detail + '</div>'; return; }
|
|
var dayIcon = data.is_daylight ? '(Tag)' : '(Nacht)';
|
|
var golden = data.golden_hour ? ' | Golden Hour' : '';
|
|
resultEl.innerHTML =
|
|
'<div class="geoint-result-grid">' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Azimut</span><span class="geoint-value">' + data.azimuth + '°</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Elevation</span><span class="geoint-value">' + data.elevation + '° ' + dayIcon + '</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Schattenrichtung</span><span class="geoint-value">' + data.shadow_direction + '°</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Schattenverhaeltnis</span><span class="geoint-value">' + (data.shadow_ratio !== null ? data.shadow_ratio + 'x Objekthoehe' : 'Kein Schatten') + '</span></div>' +
|
|
(golden ? '<div class="geoint-result-item" style="grid-column:1/-1"><span class="geoint-label" style="color:#ff9800">Golden Hour aktiv</span></div>' : '') +
|
|
'</div>';
|
|
})
|
|
.catch(function(e) { resultEl.innerHTML = '<div style="color:#ff5252">Fehler: ' + e.message + '</div>'; });
|
|
},
|
|
|
|
// ============================================================
|
|
// Hoehenprofil (2-Punkte-Tool)
|
|
// ============================================================
|
|
startElevation: function() {
|
|
if (this._elevMode) { this.stopElevation(); return; }
|
|
this._elevMode = true;
|
|
this._elevStart = null;
|
|
|
|
var panel = document.getElementById('geoint-panel');
|
|
if (panel) {
|
|
panel.innerHTML =
|
|
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">' +
|
|
'<div style="font-size:11px;font-weight:700;letter-spacing:2px;color:var(--accent)">HOEHENPROFIL</div>' +
|
|
'<button onclick="GeointTools.stopElevation()" style="background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:18px">×</button>' +
|
|
'</div>' +
|
|
'<div id="elev-status" style="font-size:12px;color:var(--accent)">Klicke Startpunkt auf den Globus</div>' +
|
|
'<div id="elev-result" style="margin-top:10px"></div>';
|
|
panel.style.display = 'block';
|
|
}
|
|
|
|
// Polyline + Labels fuer Visualisierung
|
|
this._elevLine = Globe.viewer.scene.primitives.add(new Cesium.PolylineCollection());
|
|
this._elevLabels = Globe.viewer.scene.primitives.add(new Cesium.LabelCollection());
|
|
|
|
var self = this;
|
|
this._elevHandler = new Cesium.ScreenSpaceEventHandler(Globe.viewer.scene.canvas);
|
|
this._elevHandler.setInputAction(function(click) {
|
|
var cart = Globe.viewer.scene.pickPosition(click.position);
|
|
if (!cart) {
|
|
var ray = Globe.viewer.scene.camera.getPickRay(click.position);
|
|
cart = Globe.viewer.scene.globe.pick(ray, Globe.viewer.scene);
|
|
}
|
|
if (!cart) return;
|
|
var c = Cesium.Cartographic.fromCartesian(cart);
|
|
var lat = Cesium.Math.toDegrees(c.latitude);
|
|
var lon = Cesium.Math.toDegrees(c.longitude);
|
|
|
|
if (!self._elevStart) {
|
|
self._elevStart = { lat: lat, lon: lon };
|
|
// Marker setzen
|
|
self._elevLabels.add({
|
|
position: cart, text: 'A',
|
|
font: '14px monospace', fillColor: Cesium.Color.fromCssColorString('#00ff88'),
|
|
outlineColor: Cesium.Color.BLACK, outlineWidth: 3,
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
pixelOffset: new Cesium.Cartesian2(0, -14),
|
|
});
|
|
var statusEl = document.getElementById('elev-status');
|
|
if (statusEl) statusEl.textContent = 'Klicke Endpunkt auf den Globus';
|
|
} else {
|
|
// Endpunkt
|
|
self._elevLabels.add({
|
|
position: cart, text: 'B',
|
|
font: '14px monospace', fillColor: Cesium.Color.fromCssColorString('#ff5252'),
|
|
outlineColor: Cesium.Color.BLACK, outlineWidth: 3,
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
pixelOffset: new Cesium.Cartesian2(0, -14),
|
|
});
|
|
// Linie
|
|
var startCart = Cesium.Cartesian3.fromDegrees(self._elevStart.lon, self._elevStart.lat, 100);
|
|
var endCart = Cesium.Cartesian3.fromDegrees(lon, lat, 100);
|
|
self._elevLine.add({
|
|
positions: [startCart, endCart], width: 2,
|
|
material: Cesium.Material.fromType('Color', { color: Cesium.Color.fromCssColorString('#00ff88').withAlpha(0.6) }),
|
|
});
|
|
self._fetchElevation(self._elevStart.lat, self._elevStart.lon, lat, lon);
|
|
if (self._elevHandler) { self._elevHandler.destroy(); self._elevHandler = null; }
|
|
}
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
},
|
|
|
|
_fetchElevation: function(sLat, sLon, eLat, eLon) {
|
|
var statusEl = document.getElementById('elev-status');
|
|
if (statusEl) statusEl.textContent = 'Berechne Hoehenprofil...';
|
|
|
|
fetch('/api/geoint/elevation-profile', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ start_lat: sLat, start_lon: sLon, end_lat: eLat, end_lon: eLon, samples: 80 }),
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.detail) { if (statusEl) statusEl.textContent = 'Fehler: ' + data.detail; return; }
|
|
if (statusEl) statusEl.textContent = '';
|
|
GeointTools._renderElevation(data);
|
|
})
|
|
.catch(function(e) { if (statusEl) statusEl.textContent = 'Fehler: ' + e.message; });
|
|
},
|
|
|
|
_renderElevation: function(data) {
|
|
var resultEl = document.getElementById('elev-result');
|
|
if (!resultEl) return;
|
|
var s = data.stats;
|
|
var los = data.line_of_sight;
|
|
var losColor = los.clear ? '#00ff88' : '#ff5252';
|
|
var losText = los.clear ? 'Frei' : 'Blockiert';
|
|
|
|
// SVG Chart
|
|
var points = data.points;
|
|
var maxElev = s.elevation_max + 10;
|
|
var minElev = Math.max(0, s.elevation_min - 10);
|
|
var range = maxElev - minElev || 1;
|
|
var w = 280, h = 100;
|
|
var pathD = '';
|
|
for (var i = 0; i < points.length; i++) {
|
|
var x = (i / (points.length - 1)) * w;
|
|
var y = h - ((points[i].elevation - minElev) / range) * h;
|
|
pathD += (i === 0 ? 'M' : 'L') + x.toFixed(1) + ',' + y.toFixed(1);
|
|
}
|
|
// LOS line
|
|
var losY1 = h - ((points[0].elevation - minElev) / range) * h;
|
|
var losY2 = h - ((points[points.length - 1].elevation - minElev) / range) * h;
|
|
|
|
resultEl.innerHTML =
|
|
'<svg width="' + w + '" height="' + (h + 20) + '" style="display:block;margin-bottom:8px">' +
|
|
'<defs><linearGradient id="elev-grad" x1="0" y1="0" x2="0" y2="1">' +
|
|
'<stop offset="0" stop-color="#00ff88" stop-opacity="0.3"/>' +
|
|
'<stop offset="1" stop-color="#00ff88" stop-opacity="0.02"/>' +
|
|
'</linearGradient></defs>' +
|
|
'<path d="' + pathD + 'L' + w + ',' + (h + 5) + 'L0,' + (h + 5) + 'Z" fill="url(#elev-grad)" stroke="none"/>' +
|
|
'<path d="' + pathD + '" fill="none" stroke="#00ff88" stroke-width="1.5"/>' +
|
|
'<line x1="0" y1="' + losY1.toFixed(1) + '" x2="' + w + '" y2="' + losY2.toFixed(1) + '" stroke="' + losColor + '" stroke-width="1" stroke-dasharray="4,3" opacity="0.6"/>' +
|
|
'<text x="2" y="' + (h + 14) + '" fill="var(--text-dim)" font-size="9" font-family="monospace">0 km</text>' +
|
|
'<text x="' + (w - 2) + '" y="' + (h + 14) + '" fill="var(--text-dim)" font-size="9" font-family="monospace" text-anchor="end">' + s.distance_km + ' km</text>' +
|
|
'</svg>' +
|
|
'<div class="geoint-result-grid">' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Distanz</span><span class="geoint-value">' + s.distance_km + ' km</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Sichtlinie</span><span class="geoint-value" style="color:' + losColor + '">' + losText + '</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Min / Max</span><span class="geoint-value">' + s.elevation_min + ' / ' + s.elevation_max + ' m</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Hoehendiff.</span><span class="geoint-value">' + s.elevation_diff + ' m</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Anstieg</span><span class="geoint-value">+' + s.total_ascent + ' m</span></div>' +
|
|
'<div class="geoint-result-item"><span class="geoint-label">Abstieg</span><span class="geoint-value">-' + s.total_descent + ' m</span></div>' +
|
|
'</div>' +
|
|
'<button class="overpass-exec-btn" onclick="GeointTools.startElevation()" style="margin-top:8px;font-size:10px;padding:6px">NEUE MESSUNG</button>';
|
|
},
|
|
|
|
stopElevation: function() {
|
|
this._elevMode = false;
|
|
this._elevStart = null;
|
|
if (this._elevHandler) { this._elevHandler.destroy(); this._elevHandler = null; }
|
|
if (this._elevLine && Globe.viewer) { Globe.viewer.scene.primitives.remove(this._elevLine); this._elevLine = null; }
|
|
if (this._elevLabels && Globe.viewer) { Globe.viewer.scene.primitives.remove(this._elevLabels); this._elevLabels = null; }
|
|
this.closePanel();
|
|
},
|
|
|
|
closePanel: function() {
|
|
var panel = document.getElementById('geoint-panel');
|
|
if (panel) { panel.style.display = 'none'; panel.innerHTML = ''; }
|
|
},
|
|
};
|