/**
* 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
'' +
'
' +
'
Analysiere Bild...
' +
'
' +
// Analyse-Ergebnis
'' +
'
' +
'
SZENE
' +
'
' +
'
ERKANNTE OBJEKTE
' +
'
' +
'
' +
'
' +
'
' +
'
' +
// 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 = '
' +
'' + 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: Standard weltweit (null), optional auf Viewport einschraenken
var bbox = null;
var bboxCb = document.getElementById('vlm-use-bbox');
if (bboxCb && bboxCb.checked && 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 = '-';
},
};