Fix: Region-BBox aus VLM-Analyse + generische Tags filtern
Wenn keine Viewport-BBox gesetzt ist, nutzt der Query-Generator die VLM-Regionsschaetzung (z.B. Europa, Naher Osten) als grobe BBox. 70+ Regionen gemappt (DE/EN). Generische OSM-Tags (building, highway, landuse) werden gefiltert um Overpass-Timeouts zu vermeiden. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
113
src/data_vlm.py
113
src/data_vlm.py
@@ -110,6 +110,87 @@ _OBJECT_TO_OVERPASS = {
|
|||||||
"bunker": '["military"="bunker"]',
|
"bunker": '["military"="bunker"]',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Regionen -> BBox Mapping (grob) ---
|
||||||
|
_REGION_BBOX = {
|
||||||
|
# Europa
|
||||||
|
"europa": (-10, 34, 40, 72),
|
||||||
|
"europe": (-10, 34, 40, 72),
|
||||||
|
"westeuropa": (-10, 36, 15, 60),
|
||||||
|
"western europe": (-10, 36, 15, 60),
|
||||||
|
"osteuropa": (15, 42, 45, 62),
|
||||||
|
"eastern europe": (15, 42, 45, 62),
|
||||||
|
"nordeuropa": (5, 54, 30, 72),
|
||||||
|
"northern europe": (5, 54, 30, 72),
|
||||||
|
"suedeuropa": (-10, 34, 30, 48),
|
||||||
|
"southern europe": (-10, 34, 30, 48),
|
||||||
|
"mitteleuropa": (5, 45, 20, 56),
|
||||||
|
"central europe": (5, 45, 20, 56),
|
||||||
|
"deutschland": (5.8, 47.2, 15.1, 55.1),
|
||||||
|
"germany": (5.8, 47.2, 15.1, 55.1),
|
||||||
|
# Naher Osten
|
||||||
|
"naher osten": (25, 12, 65, 42),
|
||||||
|
"middle east": (25, 12, 65, 42),
|
||||||
|
"near east": (25, 12, 65, 42),
|
||||||
|
"persischer golf": (45, 22, 60, 32),
|
||||||
|
"persian gulf": (45, 22, 60, 32),
|
||||||
|
"levante": (34, 29, 42, 37),
|
||||||
|
"levant": (34, 29, 42, 37),
|
||||||
|
# Asien
|
||||||
|
"ostasien": (100, 20, 150, 55),
|
||||||
|
"east asia": (100, 20, 150, 55),
|
||||||
|
"suedasien": (60, 5, 100, 38),
|
||||||
|
"south asia": (60, 5, 100, 38),
|
||||||
|
"suedostasien": (95, -10, 140, 25),
|
||||||
|
"southeast asia": (95, -10, 140, 25),
|
||||||
|
"zentralasien": (50, 35, 80, 55),
|
||||||
|
"central asia": (50, 35, 80, 55),
|
||||||
|
"asien": (60, -10, 150, 55),
|
||||||
|
"asia": (60, -10, 150, 55),
|
||||||
|
# Afrika
|
||||||
|
"nordafrika": (-15, 15, 40, 38),
|
||||||
|
"north africa": (-15, 15, 40, 38),
|
||||||
|
"westafrika": (-20, 0, 15, 25),
|
||||||
|
"west africa": (-20, 0, 15, 25),
|
||||||
|
"ostafrika": (25, -12, 52, 18),
|
||||||
|
"east africa": (25, -12, 52, 18),
|
||||||
|
"suedliches afrika": (10, -35, 42, -5),
|
||||||
|
"southern africa": (10, -35, 42, -5),
|
||||||
|
"afrika": (-20, -35, 52, 38),
|
||||||
|
"africa": (-20, -35, 52, 38),
|
||||||
|
# Amerika
|
||||||
|
"nordamerika": (-170, 15, -50, 72),
|
||||||
|
"north america": (-170, 15, -50, 72),
|
||||||
|
"usa": (-125, 24, -66, 50),
|
||||||
|
"united states": (-125, 24, -66, 50),
|
||||||
|
"mittelamerika": (-120, 7, -60, 25),
|
||||||
|
"central america": (-120, 7, -60, 25),
|
||||||
|
"suedamerika": (-82, -56, -34, 13),
|
||||||
|
"south america": (-82, -56, -34, 13),
|
||||||
|
# Ozeanien
|
||||||
|
"ozeanien": (110, -50, 180, 0),
|
||||||
|
"oceania": (110, -50, 180, 0),
|
||||||
|
"australien": (112, -44, 154, -10),
|
||||||
|
"australia": (112, -44, 154, -10),
|
||||||
|
# Arktis/Antarktis
|
||||||
|
"arktis": (-180, 65, 180, 90),
|
||||||
|
"arctic": (-180, 65, 180, 90),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_region_bbox(region_text):
|
||||||
|
"""Versucht aus dem VLM-Regionstext eine BBox abzuleiten."""
|
||||||
|
if not region_text:
|
||||||
|
return None
|
||||||
|
text = region_text.lower().strip()
|
||||||
|
# Exakter Match
|
||||||
|
if text in _REGION_BBOX:
|
||||||
|
return _REGION_BBOX[text]
|
||||||
|
# Teilstring-Match (z.B. "vermutlich Europa" -> "europa")
|
||||||
|
for key, bbox in _REGION_BBOX.items():
|
||||||
|
if key in text:
|
||||||
|
return bbox
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _resize_image(input_path: str, output_path: str):
|
def _resize_image(input_path: str, output_path: str):
|
||||||
"""Skaliert Bild auf max _MAX_IMAGE_DIMENSION px."""
|
"""Skaliert Bild auf max _MAX_IMAGE_DIMENSION px."""
|
||||||
@@ -251,6 +332,7 @@ def _ext(content_type: str) -> str:
|
|||||||
class QueryGenRequest(BaseModel):
|
class QueryGenRequest(BaseModel):
|
||||||
objects: list[dict]
|
objects: list[dict]
|
||||||
bbox: list[float] | None = None
|
bbox: list[float] | None = None
|
||||||
|
estimated_location_type: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@router.post("/vlm/generate-queries")
|
@router.post("/vlm/generate-queries")
|
||||||
@@ -259,9 +341,21 @@ async def generate_queries(req: QueryGenRequest):
|
|||||||
if not req.objects:
|
if not req.objects:
|
||||||
raise HTTPException(400, "Keine Objekte angegeben")
|
raise HTTPException(400, "Keine Objekte angegeben")
|
||||||
|
|
||||||
|
# BBox bestimmen: explizit > Region > weltweit
|
||||||
|
bbox = req.bbox
|
||||||
|
region_used = None
|
||||||
|
if not bbox and req.estimated_location_type:
|
||||||
|
region_bbox = _resolve_region_bbox(req.estimated_location_type)
|
||||||
|
if region_bbox:
|
||||||
|
bbox = [region_bbox[1], region_bbox[0], region_bbox[3], region_bbox[2]] # S,W,N,E
|
||||||
|
region_used = req.estimated_location_type
|
||||||
|
|
||||||
bbox_str = ""
|
bbox_str = ""
|
||||||
if req.bbox and len(req.bbox) == 4:
|
if bbox and len(bbox) == 4:
|
||||||
bbox_str = f"({req.bbox[0]},{req.bbox[1]},{req.bbox[2]},{req.bbox[3]})"
|
bbox_str = f"({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]})"
|
||||||
|
|
||||||
|
if not bbox_str:
|
||||||
|
logger.warning("Overpass-Query ohne BBox - kann bei generischen Tags Timeout verursachen")
|
||||||
|
|
||||||
fragments = []
|
fragments = []
|
||||||
used_types = set()
|
used_types = set()
|
||||||
@@ -278,10 +372,19 @@ async def generate_queries(req: QueryGenRequest):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Fallback: OSM-Tags aus VLM-Ergebnis verwenden
|
# Fallback: OSM-Tags aus VLM-Ergebnis verwenden
|
||||||
|
# Generische Tags ignorieren (zu viele Treffer, Overpass Timeout)
|
||||||
|
_GENERIC_KEYS = {
|
||||||
|
"building", "highway", "landuse", "natural", "waterway",
|
||||||
|
"surface", "barrier", "wall", "fence", "roof",
|
||||||
|
"addr:street", "addr:city", "addr:housenumber",
|
||||||
|
"name", "source", "type",
|
||||||
|
}
|
||||||
osm_tags = obj.get("osm_tags", [])
|
osm_tags = obj.get("osm_tags", [])
|
||||||
for tag_str in osm_tags:
|
for tag_str in osm_tags:
|
||||||
if "=" in tag_str and tag_str not in used_types:
|
if "=" in tag_str and tag_str not in used_types:
|
||||||
key, val = tag_str.split("=", 1)
|
key, val = tag_str.split("=", 1)
|
||||||
|
if key in _GENERIC_KEYS:
|
||||||
|
continue # Zu generisch, ueberspringen
|
||||||
frag = f'["{key}"="{val}"]'
|
frag = f'["{key}"="{val}"]'
|
||||||
fragments.append(f' node{frag}{bbox_str};')
|
fragments.append(f' node{frag}{bbox_str};')
|
||||||
fragments.append(f' way{frag}{bbox_str};')
|
fragments.append(f' way{frag}{bbox_str};')
|
||||||
@@ -293,8 +396,12 @@ async def generate_queries(req: QueryGenRequest):
|
|||||||
|
|
||||||
query = "[out:json][timeout:30];\n(\n" + "\n".join(fragments) + "\n);\nout center body;"
|
query = "[out:json][timeout:30];\n(\n" + "\n".join(fragments) + "\n);\nout center body;"
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
"query": query,
|
"query": query,
|
||||||
"object_count": len(req.objects),
|
"object_count": len(req.objects),
|
||||||
"tag_count": len(used_types),
|
"tag_count": len(used_types),
|
||||||
}
|
}
|
||||||
|
if region_used:
|
||||||
|
result["region"] = region_used
|
||||||
|
logger.info(f"VLM Query: Region '{region_used}' als BBox verwendet")
|
||||||
|
return result
|
||||||
|
|||||||
@@ -272,14 +272,15 @@ const VlmUI = {
|
|||||||
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 }),
|
body: JSON.stringify({ objects: selected, bbox: bbox, estimated_location_type: self._currentAnalysis.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) {
|
||||||
if (searchResult) searchResult.textContent = 'Query generiert (' + data.tag_count + ' Tags). Suche OSM-Daten...';
|
var regionInfo = data.region ? ' (Region: ' + data.region + ')' : ' (weltweit)';
|
||||||
|
if (searchResult) searchResult.textContent = 'Query generiert (' + data.tag_count + ' Tags)' + regionInfo + '. Suche OSM-Daten...';
|
||||||
|
|
||||||
// 2. Overpass-Query direkt ausfuehren
|
// 2. Overpass-Query direkt ausfuehren
|
||||||
OverpassLayer.setColor('#e040fb');
|
OverpassLayer.setColor('#e040fb');
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren