/** * VLM UI: Bildanalyse-Panel mit Upload, Zwei-Stufen-Workflow und Overpass-Kopplung. */ const VlmUI = { _panel: null, _dropZone: null, _currentAnalysis: null, _isAnalyzing: false, init: function() { this._createPanel(); }, show: function() { if (!this._panel) this.init(); this._panel.style.display = 'block'; }, hide: function() { if (this._panel) this._panel.style.display = 'none'; }, _createPanel: function() { var panel = document.getElementById('vlm-panel'); if (!panel) return; this._panel = panel; panel.innerHTML = '
' + '
BILDANALYSE (VLM)
' + '' + '
' + // Drop-Zone '
' + '' + '' + '' + '
Bild hierher ziehen
' + '
oder klicken zum Waehlen (PNG/JPG/WEBP, max 10MB)
' + '
' + '' + // Preview '' + // Loading '' + // Analyse-Ergebnis '' + // Reset ''; this._dropZone = document.getElementById('vlm-drop-zone'); this._setupDragDrop(); }, _setupDragDrop: function() { var zone = this._dropZone; if (!zone) return; var self = this; ['dragenter', 'dragover'].forEach(function(evt) { zone.addEventListener(evt, function(e) { e.preventDefault(); e.stopPropagation(); zone.classList.add('vlm-drop-active'); }); }); ['dragleave', 'drop'].forEach(function(evt) { zone.addEventListener(evt, function(e) { e.preventDefault(); e.stopPropagation(); zone.classList.remove('vlm-drop-active'); }); }); zone.addEventListener('drop', function(e) { var file = e.dataTransfer.files[0]; if (file) self._handleFile(file); }); zone.addEventListener('click', function() { document.getElementById('vlm-file-input').click(); }); }, _onFileSelect: function(input) { if (input.files && input.files[0]) { this._handleFile(input.files[0]); } }, _handleFile: function(file) { // Validierung var allowed = ['image/png', 'image/jpeg', 'image/webp']; if (allowed.indexOf(file.type) === -1) { alert('Ungültiger Dateityp. Erlaubt: PNG, JPG, WEBP'); return; } if (file.size > 10 * 1024 * 1024) { alert('Bild zu gross (max 10MB)'); return; } // Preview this._showPreview(file); // Upload + Analyse this._uploadAndAnalyze(file); }, _showPreview: function(file) { var previewEl = document.getElementById('vlm-preview'); if (!previewEl) return; var reader = new FileReader(); reader.onload = function(e) { previewEl.innerHTML = 'Vorschau' + '
' + file.name + ' (' + (file.size / 1024).toFixed(0) + ' KB)
'; previewEl.style.display = 'block'; }; reader.readAsDataURL(file); }, _getViewportInfo: function() { if (!Globe.viewer) return ''; var cam = Globe.viewer.camera; var carto = cam.positionCartographic; if (!carto) return ''; var lat = Cesium.Math.toDegrees(carto.latitude).toFixed(2); var lon = Cesium.Math.toDegrees(carto.longitude).toFixed(2); var alt = (carto.height / 1000).toFixed(0); return 'Lat ' + lat + ', Lon ' + lon + ', Hoehe ' + alt + ' km'; }, _uploadAndAnalyze: function(file) { this._isAnalyzing = true; this._dropZone.style.display = 'none'; var loadingEl = document.getElementById('vlm-loading'); if (loadingEl) loadingEl.style.display = 'block'; var formData = new FormData(); formData.append('file', file); formData.append('viewport_info', this._getViewportInfo()); var self = this; fetch('/api/vlm/analyze', { method: 'POST', body: formData, }) .then(function(r) { if (r.status === 429) { throw new Error('Eine Analyse laeuft bereits. Bitte warten.'); } if (!r.ok) { return r.json().then(function(d) { throw new Error(d.detail || 'Fehler ' + r.status); }); } return r.json(); }) .then(function(data) { self._currentAnalysis = data; self._showResults(data); }) .catch(function(e) { alert('VLM-Fehler: ' + e.message); self._reset(); }) .finally(function() { self._isAnalyzing = false; if (loadingEl) loadingEl.style.display = 'none'; }); }, _showResults: function(data) { var resultsEl = document.getElementById('vlm-results'); var resetBtn = document.getElementById('vlm-reset-btn'); if (!resultsEl) return; // 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; } // Objekte var objectsEl = document.getElementById('vlm-objects'); if (objectsEl) { var html = ''; var objects = data.objects || []; objects.forEach(function(obj, idx) { var confClass = 'vlm-confidence-' + obj.confidence; var confLabel = { high: 'HIGH', medium: 'MED', low: 'LOW' }[obj.confidence] || '?'; var checked = obj.confidence !== 'low' ? ' checked' : ''; var milBadge = obj.military_relevant ? 'MIL' : ''; html += '
' + '' + '
' + '
' + obj.type.replace(/_/g, ' ') + '
' + '
' + obj.description + '
' + '
' + '' + confLabel + '' + milBadge + '
'; }); if (objects.length === 0) { html = '
Keine Objekte erkannt
'; } objectsEl.innerHTML = html; } resultsEl.style.display = 'block'; if (resetBtn) resetBtn.style.display = 'block'; }, _confirmAndSearch: function() { if (!this._currentAnalysis) return; // Ausgewaehlte Objekte sammeln var objects = this._currentAnalysis.objects || []; var selected = []; objects.forEach(function(obj, idx) { var cb = document.getElementById('vlm-obj-' + idx); if (cb && cb.checked) { selected.push(obj); } }); if (selected.length === 0) { alert('Bitte mindestens ein Objekt auswaehlen'); return; } // BBox vom Viewport var bbox = null; if (Globe.viewer) { var rect = Globe.viewer.camera.computeViewRectangle(); if (rect) { bbox = [ Cesium.Math.toDegrees(rect.south), Cesium.Math.toDegrees(rect.west), Cesium.Math.toDegrees(rect.north), Cesium.Math.toDegrees(rect.east), ]; } } var btn = document.getElementById('vlm-search-btn'); if (btn) { btn.disabled = true; btn.textContent = 'SUCHE LAEUFT...'; } var searchResult = document.getElementById('vlm-search-result'); if (searchResult) { searchResult.style.display = 'block'; searchResult.textContent = 'Generiere Overpass-Queries...'; searchResult.style.color = 'var(--text-dim)'; } // Queries generieren fetch('/api/vlm/generate-queries', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ objects: selected, bbox: bbox }), }) .then(function(r) { if (!r.ok) return r.json().then(function(d) { throw new Error(d.detail || 'Fehler'); }); return r.json(); }) .then(function(data) { if (searchResult) searchResult.textContent = 'Query generiert (' + data.tag_count + ' Tags). Suche OSM-Daten...'; // Overpass-Layer aktivieren und Query ausfuehren var overpassCb = document.getElementById('layer-overpass'); if (overpassCb && !overpassCb.checked) { overpassCb.checked = true; overpassCb.dispatchEvent(new Event('change')); } // Query an Overpass senden OverpassLayer.setColor('#e040fb'); if (typeof OverpassUI !== 'undefined') { OverpassUI.executeQueryDirect(data.query, bbox, '#e040fb'); } if (searchResult) { searchResult.textContent = 'Overpass-Suche gestartet'; searchResult.style.color = 'var(--accent)'; } }) .catch(function(e) { if (searchResult) { searchResult.textContent = 'Fehler: ' + e.message; searchResult.style.color = '#ff5252'; } }) .finally(function() { if (btn) { btn.disabled = false; btn.textContent = 'AUF GLOBE SUCHEN'; } }); }, _reset: function() { this._currentAnalysis = null; this._isAnalyzing = false; if (this._dropZone) this._dropZone.style.display = 'block'; var els = ['vlm-preview', 'vlm-loading', 'vlm-results', 'vlm-search-result']; els.forEach(function(id) { var el = document.getElementById(id); if (el) el.style.display = 'none'; }); var resetBtn = document.getElementById('vlm-reset-btn'); if (resetBtn) resetBtn.style.display = 'none'; var fileInput = document.getElementById('vlm-file-input'); if (fileInput) fileInput.value = ''; var countEl = document.getElementById('count-vlm'); if (countEl) countEl.textContent = '-'; }, };