feat: Globe-Suche nutzt jetzt EXIF + Region + Overpass kombiniert

- EXIF-GPS: Marker direkt auf Globus + Kamera fliegt hin
- EXIF-GPS als BBox (0.5 Grad Radius) fuer praezisere Overpass-Suche
- Region aus VLM/Landscape Clues als Fallback-BBox
- Statusanzeige zeigt alle genutzten Datenquellen
- Overpass-Ergebnisse werden mit EXIF-Marker kombiniert
Dieser Commit ist enthalten in:
Claude Dev
2026-03-26 09:18:38 +01:00
Ursprung ea436a4338
Commit 446b8b9228

Datei anzeigen

@@ -279,9 +279,10 @@ const VlmUI = {
_confirmAndSearch: function() { _confirmAndSearch: function() {
if (!this._currentAnalysis) return; if (!this._currentAnalysis) return;
var analysis = this._currentAnalysis;
// Ausgewaehlte Objekte sammeln // Ausgewaehlte Objekte sammeln
var objects = this._currentAnalysis.objects || []; var objects = analysis.objects || [];
var selected = []; var selected = [];
objects.forEach(function(obj, idx) { objects.forEach(function(obj, idx) {
var cb = document.getElementById('vlm-obj-' + idx); var cb = document.getElementById('vlm-obj-' + idx);
@@ -295,42 +296,90 @@ const VlmUI = {
return; return;
} }
// BBox: Standard weltweit (null), optional auf Viewport einschraenken // === EXIF-GPS: Marker setzen + dorthin fliegen ===
var exif = analysis.exif;
var hasExifGps = exif && exif.has_gps && exif.latitude && exif.longitude;
if (hasExifGps) {
// EXIF-Position als Marker auf dem Globus
OverpassLayer.start(Globe.viewer);
OverpassLayer.setColor('#e040fb');
var exifNode = {
nodes: [{
id: 'exif-gps',
lat: exif.latitude,
lon: exif.longitude,
tags: {
'source': 'EXIF GPS',
'camera': (exif.camera_make || '') + ' ' + (exif.camera_model || ''),
'timestamp': exif.timestamp || '',
'altitude': exif.altitude ? exif.altitude + 'm' : '',
},
name: 'EXIF GPS-Position',
}],
ways: [],
relations: [],
total: 1,
};
OverpassLayer.render(exifNode);
// Zur EXIF-Position fliegen
Globe.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(exif.longitude, exif.latitude, 50000),
duration: 2,
});
}
// === BBox bestimmen: EXIF-GPS > Viewport-Checkbox > Region ===
var bbox = null; var bbox = null;
var bboxCb = document.getElementById('vlm-use-bbox'); if (hasExifGps) {
if (bboxCb && bboxCb.checked && Globe.viewer) { // 0.5 Grad um EXIF-Position
var rect = Globe.viewer.camera.computeViewRectangle(); bbox = [
if (rect) { exif.latitude - 0.5,
bbox = [ exif.longitude - 0.5,
Cesium.Math.toDegrees(rect.south), exif.latitude + 0.5,
Cesium.Math.toDegrees(rect.west), exif.longitude + 0.5,
Cesium.Math.toDegrees(rect.north), ];
Cesium.Math.toDegrees(rect.east), } else {
]; 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),
];
}
} }
// Kein explizites BBox? Region wird vom Backend aus estimated_location_type abgeleitet
} }
var btn = document.getElementById('vlm-search-btn'); var btn = document.getElementById('vlm-search-btn');
if (btn) { btn.disabled = true; btn.textContent = 'SUCHE LAEUFT...'; } if (btn) { btn.disabled = true; btn.textContent = 'SUCHE LAEUFT...'; }
var searchResult = document.getElementById('vlm-search-result'); var searchResult = document.getElementById('vlm-search-result');
if (searchResult) { searchResult.style.display = 'block'; searchResult.textContent = 'Generiere Overpass-Queries...'; searchResult.style.color = 'var(--text-dim)'; } var statusParts = [];
if (hasExifGps) statusParts.push('EXIF GPS-Marker gesetzt');
if (searchResult) { searchResult.style.display = 'block'; searchResult.textContent = statusParts.join(' | ') + (statusParts.length ? ' | ' : '') + 'Generiere Overpass-Queries...'; searchResult.style.color = 'var(--text-dim)'; }
// 1. Queries generieren // === Overpass-Suche mit allen verfuegbaren Daten ===
var startTime = Date.now(); var startTime = Date.now();
fetch('/api/vlm/generate-queries', { fetch('/api/vlm/generate-queries', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ objects: selected, bbox: bbox, estimated_location_type: VlmUI._currentAnalysis.estimated_location_type || null }), body: JSON.stringify({
objects: selected,
bbox: bbox,
estimated_location_type: analysis.estimated_location_type || null,
}),
}) })
.then(function(r) { .then(function(r) {
if (!r.ok) return r.json().then(function(d) { throw new Error(d.detail || 'Fehler'); }); if (!r.ok) return r.json().then(function(d) { throw new Error(d.detail || 'Fehler'); });
return r.json(); return r.json();
}) })
.then(function(data) { .then(function(data) {
var regionInfo = data.region ? ' (Region: ' + data.region + ')' : ' (weltweit)'; var src = hasExifGps ? 'EXIF BBox' : (data.region ? 'Region: ' + data.region : 'weltweit');
if (searchResult) searchResult.textContent = 'Query generiert (' + data.tag_count + ' Tags)' + regionInfo + '. Suche OSM-Daten...'; if (searchResult) searchResult.textContent = statusParts.join(' | ') + (statusParts.length ? ' | ' : '') + 'Query generiert (' + data.tag_count + ' Tags, ' + src + '). Suche OSM-Daten...';
// 2. Overpass-Query direkt ausfuehren
OverpassLayer.setColor('#e040fb'); OverpassLayer.setColor('#e040fb');
return fetch('/api/overpass/query', { return fetch('/api/overpass/query', {
method: 'POST', method: 'POST',
@@ -343,27 +392,47 @@ const VlmUI = {
return r.json().then(function(d) { throw new Error(d.detail || 'Rate-Limit'); }); return r.json().then(function(d) { throw new Error(d.detail || 'Rate-Limit'); });
} }
if (!r.ok) { if (!r.ok) {
return r.json().then(function(d) { throw new Error(d.detail || 'Overpass-Fehler'); }); return r.text().then(function(txt) {
try { var d = JSON.parse(txt); throw new Error(d.detail || 'Overpass-Fehler'); }
catch(e) { if (e.message && !e.message.startsWith('Unexpected')) throw e; throw new Error('Overpass-Fehler ' + r.status); }
});
} }
return r.json(); return r.json();
}) })
.then(function(data) { .then(function(data) {
var elapsed = ((Date.now() - startTime) / 1000).toFixed(1); var elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
OverpassLayer.render(data); // Bei EXIF-GPS: Overpass-Ergebnisse zu bestehendem Marker hinzufuegen
if (hasExifGps && data.total > 0) {
// Bestehende EXIF-Daten beibehalten, Overpass hinzufuegen
var combined = {
nodes: [{ id: 'exif-gps', lat: exif.latitude, lon: exif.longitude, tags: { 'source': 'EXIF GPS' }, name: 'EXIF GPS-Position' }].concat(data.nodes || []),
ways: data.ways || [],
relations: data.relations || [],
total: 1 + data.total,
};
OverpassLayer.render(combined);
} else if (!hasExifGps) {
OverpassLayer.render(data);
}
if (searchResult) { if (searchResult) {
var text = data.total + ' Orte gefunden (' + elapsed + 's)'; var parts = [];
if (hasExifGps) parts.push('EXIF GPS-Marker');
parts.push(data.total + ' OSM-Orte');
var text = parts.join(' + ') + ' (' + elapsed + 's)';
if (data.cached) text += ' [Cache]'; if (data.cached) text += ' [Cache]';
if (data.truncated) text += ' [Limit erreicht]'; if (data.truncated) text += ' [Limit]';
searchResult.textContent = text; searchResult.textContent = text;
searchResult.style.color = 'var(--accent)'; searchResult.style.color = 'var(--accent)';
} }
var countEl = document.getElementById('count-vlm'); var countEl = document.getElementById('count-vlm');
if (countEl) countEl.textContent = data.total; if (countEl) countEl.textContent = (hasExifGps ? 1 : 0) + data.total;
}) })
.catch(function(e) { .catch(function(e) {
if (searchResult) { if (searchResult) {
searchResult.textContent = 'Fehler: ' + e.message; var prefix = hasExifGps ? 'EXIF GPS-Marker gesetzt | ' : '';
searchResult.style.color = '#ff5252'; searchResult.textContent = prefix + 'Overpass-Fehler: ' + e.message;
searchResult.style.color = hasExifGps ? '#ff9800' : '#ff5252';
} }
}) })
.finally(function() { .finally(function() {