import os
import gradio as gr
import geopandas as gpd
import folium
import requests
# osmnx opsiyonel (fallback için), yoksa sorun değil
try:
import osmnx as ox
except ImportError:
ox = None
from huggingface_hub import InferenceClient, login
# ==========================================
# HF TOKEN & MODEL
# ==========================================
HF_TOKEN = (os.environ.get("HUGGINGFACE_HUB_TOKEN", "") or "").strip()
if HF_TOKEN:
login(token=HF_TOKEN)
else:
print("UYARI: HF_TOKEN / HUGGINGFACE_HUB_TOKEN bulunamadı, gated modellere erişilemeyebilir.")
#client = InferenceClient(
# model="abacusai/Dracarys-72B-Instruct",
# token=HF_TOKEN if HF_TOKEN else None,
#)
# ==========================================
# ÖNCEDEN HAZIRLANMIŞ OSM VERİSİ
# ==========================================
NEIGH_PATH = "data/neighborhoods.geojson"
POIS_PATH = "data/pois.geojson"
if not (os.path.exists(NEIGH_PATH) and os.path.exists(POIS_PATH)):
print("UYARI: data/ klasöründe neighborhoods.geojson / pois.geojson bulunamadı!")
neighborhoods_gdf = None
pois_gdf = None
else:
neighborhoods_gdf = gpd.read_file(NEIGH_PATH)
pois_gdf = gpd.read_file(POIS_PATH)
print(f"{len(neighborhoods_gdf)} mahalle/semt, {len(pois_gdf)} POI yüklendi.")
DEFAULT_TAGS = {
"amenity": ["school", "pharmacy", "hospital", "restaurant", "cafe", "bank"],
"leisure": ["park", "playground"],
"shop": True,
# >>> ULAŞIM ETİKETLERİ
"highway": ["bus_stop"], # otobüs durakları
"railway": ["station", "halt", "tram_stop"], # tren / metro / tramvay istasyonları
"public_transport": ["stop_position", "platform"], # toplu taşıma durak/istasyonları
}
# ==========================================
# OSM / CBS FONKSİYONLARI
# ==========================================
from shapely.geometry import Point
import math
def get_neighborhood_center(city, district, neighborhood):
"""
Verilen şehir/ilçe/mahalle için poligonu alır ve merkez (centroid) koordinatını döndürür.
(lat, lon) şeklinde WGS84 (EPSG:4326) döner.
"""
gdf = get_neighborhood_gdf(city, district, neighborhood)
if gdf is None or len(gdf) == 0:
return None
# Emin olmak için WGS84'e çevir
try:
gdf_ll = gdf.to_crs(epsg=4326)
except Exception:
gdf_ll = gdf # zaten 4326 ise
poly = gdf_ll.geometry.iloc[0]
c = poly.centroid
return c.y, c.x # (lat, lon)
def _build_nearest_poi_query(lat, lon, key, values, radius):
"""
Overpass için: verilen key + values (örn: amenity=university, shop=supermarket)
etrafında en yakın POI'yi arayan sorguyu üretir.
"""
if isinstance(values, str):
values = [values]
filters = []
for v in values:
filters.append(f' node["{key}"="{v}"](around:{radius},{lat},{lon});')
filters.append(f' way["{key}"="{v}"](around:{radius},{lat},{lon});')
filters.append(f' rel["{key}"="{v}"](around:{radius},{lat},{lon});')
filters_str = "\n".join(filters)
query = f"""
[out:json][timeout:25];
(
{filters_str}
);
out center 1;
"""
return query
def _haversine_m(lat1, lon1, lat2, lon2):
"""
İki nokta arasındaki yaklaşık mesafeyi (metre) hesaplar.
"""
R = 6371000 # Dünya yarıçapı (m)
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lon2 - lon1)
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
def find_nearest_poi_overpass(city, district, neighborhood, key, values, radius=5000):
"""
Mahalle merkezinden belirli bir yarıçap içinde (m cinsinden)
verilen OSM tag'ine göre (örn: amenity, shop) en yakın POI'yi arar.
Örnek:
key="amenity", values=["university", "college"]
key="shop", values=["supermarket", "convenience"]
Dönüş:
None -> bulunamadı
dict -> {"name", "lat", "lon", "tag_key", "tag_value", "distance_m", "distance_km"}
"""
center = get_neighborhood_center(city, district, neighborhood)
if center is None:
return None
lat_c, lon_c = center
query = _build_nearest_poi_query(lat_c, lon_c, key, values, radius)
url = "https://overpass-api.de/api/interpreter"
try:
resp = requests.post(url, data={"data": query}, timeout=30)
resp.raise_for_status()
data = resp.json()
except Exception as e:
print("Overpass nearest POI isteği hatası:", e)
return None
elements = data.get("elements", [])
if not elements:
return None
# İlk element en yakın kabul ediliyor
e = elements[0]
tags = e.get("tags", {}) or {}
name = tags.get("name", "(isimsiz POI)")
# node ise lat/lon direk var; way/rel ise center altında
lat_p = e.get("lat") or (e.get("center") or {}).get("lat")
lon_p = e.get("lon") or (e.get("center") or {}).get("lon")
tag_value = None
if isinstance(values, list):
for v in values:
if tags.get(key) == v:
tag_value = v
break
if tag_value is None:
tag_value = tags.get(key)
else:
tag_value = tags.get(key)
dist_m = None
dist_km = None
if lat_p is not None and lon_p is not None:
dist_m = _haversine_m(lat_c, lon_c, lat_p, lon_p)
dist_km = dist_m / 1000.0
return {
"name": name,
"lat": lat_p,
"lon": lon_p,
"tag_key": key,
"tag_value": tag_value,
"distance_m": dist_m,
"distance_km": dist_km,
}
def answer_nearest_poi(city, district, neighborhood, key, values, human_label=None, radius=5000):
"""
Kullanıcıya gösterilecek generic cevap metni.
human_label: 'üniversite', 'market', 'hastane' gibi daha okunaklı isim.
"""
info = find_nearest_poi_overpass(city, district, neighborhood, key, values, radius=radius)
if info is None:
label = human_label or f"{key}={values}"
return f"{city} / {district} / {neighborhood} çevresinde belirli bir yarıçapta {label} bulunamadı."
name = info["name"]
lat = info["lat"]
lon = info["lon"]
dist_km = info["distance_km"]
label = human_label or info.get("tag_value") or f"{key}={values}"
if lat is not None and lon is not None and dist_km is not None:
return (
f"{city} / {district} / {neighborhood} mahallesi merkezine en yakın {label}: "
f"{name} (yaklaşık {dist_km:.2f} km, konum: {lat:.5f}, {lon:.5f})."
)
else:
return (
f"{city} / {district} / {neighborhood} mahallesi merkezine en yakın {label}: {name}."
)
def nearest_poi_wrapper(city, district, neighborhood, poi_type):
"""
Gradio dropdown'dan seçilen poi_type'ı gerçek tag'lere çeviren yardımcı.
"""
if poi_type == "Üniversite":
key = "amenity"
values = ["university", "college"]
label = "üniversite"
elif poi_type == "Market":
key = "shop"
values = ["supermarket", "convenience", "mall", "department_store"]
label = "market"
elif poi_type == "Hastane":
key = "amenity"
values = "hospital"
label = "hastane"
elif poi_type == "Eczane":
key = "amenity"
values = "pharmacy"
label = "eczane"
elif poi_type == "Park":
key = "leisure"
values = "park"
label = "park"
else:
# varsayılan: üniversite
key = "amenity"
values = ["university", "college"]
label = "üniversite"
return answer_nearest_poi(city, district, neighborhood, key, values, human_label=label)
def get_neighborhood_gdf(city: str, district: str, neighborhood: str):
"""
1) Önce önceden kaydedilmiş GeoJSON'dan arar (city + district + neighborhood).
2) Eğer orada yoksa ve osmnx mevcutsa, OSM'den canlı çeker (fallback).
"""
city = (city or "").strip()
district = (district or "").strip()
neighborhood = (neighborhood or "").strip()
# 1) Precomputed veri
if neighborhoods_gdf is not None:
mask = (
(neighborhoods_gdf["city"] == city)
& (neighborhoods_gdf["district"] == district)
& (neighborhoods_gdf["neighborhood"] == neighborhood)
)
gdf_local = neighborhoods_gdf[mask]
if gdf_local is not None and len(gdf_local) > 0:
return gdf_local
# 2) Fallback: canlı OSM çağrısı
if ox is None:
print("OSM fallback kullanılamıyor: osmnx yüklü değil.")
return None
# İlçe bilgisini de geocode sorgusuna ekliyoruz
query = f"{neighborhood}, {district}, {city}, Türkiye"
print(f"OSM fallback: {query}")
try:
gdf_osm = ox.geocode_to_gdf(query)
except Exception as e:
print("Mahalle geocode hatası (OSM fallback):", e)
return None
if gdf_osm is None or len(gdf_osm) == 0:
return None
gdf_osm = gdf_osm.copy()
gdf_osm["city"] = city
gdf_osm["district"] = district
gdf_osm["neighborhood"] = neighborhood
return gdf_osm
def get_pois_within(gdf, tags=None):
"""
1) Eğer GeoJSON'da bu mahalle için POI varsa, oradan döner.
2) Yoksa ve osmnx mevcutsa, poligon üzerinden OSM'den canlı çeker (fallback).
"""
if gdf is None:
return None
if tags is None:
tags = DEFAULT_TAGS
# 1) Precomputed POI verisi
if (
pois_gdf is not None
and all(col in gdf.columns for col in ["city", "district", "neighborhood"])
):
row = gdf.iloc[0]
city = row["city"]
district = row["district"]
neighborhood = row["neighborhood"]
mask = (
(pois_gdf["city"] == city)
& (pois_gdf["district"] == district)
& (pois_gdf["neighborhood"] == neighborhood)
)
pois_local = pois_gdf[mask]
if pois_local is not None and len(pois_local) > 0:
return pois_local
# 2) Fallback: OSM'den canlı POI çek
if ox is None:
print("OSM POI fallback kullanılamıyor: osmnx yüklü değil.")
return None
try:
polygon = gdf.geometry.iloc[0]
pois_osm = ox.features_from_polygon(polygon, tags)
return pois_osm
except Exception as e:
print("POI hatası (OSM fallback):", e)
return None
def summarize_pois(gdf, pois):
summary = {}
try:
area_m2 = gdf.to_crs(epsg=32636).geometry.iloc[0].area
summary["alan_m2"] = float(area_m2)
summary["alan_km2"] = float(area_m2 / 1_000_000)
except Exception as e:
print("Alan hesaplama hatası:", e)
summary["alan_m2"] = None
summary["alan_km2"] = None
if pois is None or len(pois) == 0:
summary["toplam_poi"] = 0
return summary
summary["toplam_poi"] = int(len(pois))
if "amenity" in pois.columns:
amenity_counts = pois["amenity"].value_counts().to_dict()
for k, v in amenity_counts.items():
summary[f"amenity_{k}"] = int(v)
if "leisure" in pois.columns:
leisure_counts = pois["leisure"].value_counts().to_dict()
for k, v in leisure_counts.items():
summary[f"leisure_{k}"] = int(v)
if "shop" in pois.columns:
shop_counts = pois["shop"].value_counts().to_dict()
for k, v in shop_counts.items():
summary[f"shop_{k}"] = int(v)
# >>> ULAŞIM: highway / railway / public_transport
if "highway" in pois.columns:
hw_counts = pois["highway"].value_counts().to_dict()
for k, v in hw_counts.items():
summary[f"highway_{k}"] = int(v)
if "railway" in pois.columns:
rw_counts = pois["railway"].value_counts().to_dict()
for k, v in rw_counts.items():
summary[f"railway_{k}"] = int(v)
if "public_transport" in pois.columns:
pt_counts = pois["public_transport"].value_counts().to_dict()
for k, v in pt_counts.items():
summary[f"public_transport_{k}"] = int(v)
return summary
def build_poi_names_text(pois, max_per_category=15) -> str:
"""
POI GeoDataFrame'inden okul, eczane, kafe, restoran, market vb.
için isim listeleri çıkarır. LLM bağlamında kullanılacak metni döndürür.
"""
if pois is None or len(pois) == 0:
return "Bu mahalle için isim verisi olan POI bulunamadı.\n"
if "name" not in pois.columns:
return "Bu mahallede POI'ler için 'name' alanı bulunamadı.\n"
lines = []
def add_category(title, mask):
sub = pois[mask]
if sub is None or len(sub) == 0:
return
names = (
sub["name"]
.dropna()
.astype(str)
.str.strip()
)
names = [n for n in names if n]
if not names:
return
unique_names = sorted(set(names))[:max_per_category]
lines.append(f"{title}:")
for n in unique_names:
lines.append(f" - {n}")
lines.append("") # kategori arası boş satır
# Kategoriler
if "amenity" in pois.columns:
amenity = pois["amenity"]
add_category("Okullar", amenity == "school")
add_category("Eczaneler", amenity == "pharmacy")
add_category("Kafeler", amenity == "cafe")
add_category("Restoranlar", amenity == "restaurant")
if "shop" in pois.columns:
shop = pois["shop"]
# İstersen buraya başka shop türleri de ekleyebilirsin
add_category("Marketler", shop.isin(["supermarket", "convenience", "mall", "department_store"]))
if not lines:
return "Bu mahallede adı bilinen POI listesi çıkarılamadı.\n"
return "\n".join(lines)
def build_stats_text(summary: dict) -> str:
if not summary:
return "Veri bulunamadı."
alan = summary.get("alan_km2", 0) or 0.0
toplam_poi = summary.get("toplam_poi", 0)
okul = summary.get("amenity_school", 0)
park = summary.get("leisure_park", 0)
eczane = summary.get("amenity_pharmacy", 0)
cafe = summary.get("amenity_cafe", 0)
restoran = summary.get("amenity_restaurant", 0)
# >>> ULAŞIM SAYILARI
otobus_duragi = summary.get("highway_bus_stop", 0)
tren_istasyonu = summary.get("railway_station", 0) + summary.get("railway_halt", 0)
tramvay_duragi = summary.get("railway_tram_stop", 0)
pt_platform = summary.get("public_transport_platform", 0)
lines = [
f"- Tahmini alan: {alan:.2f} km²",
f"- Toplam POI (ilgi noktası): {toplam_poi}",
f"- Okul sayısı: {okul}",
f"- Park sayısı: {park}",
f"- Eczane sayısı: {eczane}",
f"- Kafe sayısı: {cafe}",
f"- Restoran sayısı: {restoran}",
# >>> ULAŞIM SATIRLARI
f"- Otobüs durağı sayısı: {otobus_duragi}",
f"- Tren/metro istasyonu sayısı: {tren_istasyonu}",
f"- Tramvay durağı sayısı: {tramvay_duragi}",
f"- Toplu taşıma platform/istasyon öğesi: {pt_platform}",
]
return "\n".join(lines)
import requests
import json
import geopandas as gpd
from shapely.geometry import shape
from pathlib import Path
def is_osm_query(message: str) -> bool:
return isinstance(message, str) and message.strip().lower().startswith("/osm")
OSM_PROMPT_PATH = Path("osm_query_system.txt") # veya Path("data/osm_query_system.txt")
try:
with OSM_PROMPT_PATH.open("r", encoding="utf-8") as f:
OSM_QUERY_SYSTEM = f.read()
print(f"OSM system prompt '{OSM_PROMPT_PATH}' dosyasından yüklendi.")
except FileNotFoundError:
print(f"UYARI: {OSM_PROMPT_PATH} bulunamadı, kısa yedek prompt kullanılacak.")
OSM_QUERY_SYSTEM = """
You are an expert in OpenStreetMap and Overpass API.
Produce only valid Overpass QL.
No explanations. No markdown.
Use [out:json][timeout:25]; at top.
End with: out center;
"""
def generate_overpass_query_from_llm(prompt: str, model_name: str) -> str:
"""
Doğal dilde verilen prompt'u kullanarak Overpass QL sorgusu üretir.
Sadece geçerli Overpass QL döndürmeye çalışır, markdown vs. temizler.
"""
prompt = (prompt or "").strip()
if not prompt:
prompt = "Generate an Overpass QL query for my request."
client = InferenceClient(model=model_name, token=HF_TOKEN if HF_TOKEN else None)
messages = [
{"role": "system", "content": OSM_QUERY_SYSTEM},
{"role": "user", "content": prompt},
]
# Streaming'e gerek yok, tek seferde alalım
result = client.chat_completion(
messages=messages,
max_tokens=400,
temperature=0.2,
top_p=0.9,
stream=False,
)
# HF InferenceClient sonucu
query_text = result.choices[0].message.content
# Olası ```ql ``` bloklarını temizle
query_text = query_text.replace("```ql", "").replace("```QL", "").replace("```", "")
return query_text.strip()
def normalize_overpass_query(raw_query: str) -> str:
if not raw_query:
return ""
q = raw_query.strip()
if "{{geocodeArea:" in q:
# Kullanıcıyı uyarmak için
raise ValueError("Overpass API, {{geocodeArea:...}} makrosunu desteklemez. "
"Lütfen bunun yerine area[...] filtresi veya numeric area id kullanın.")
# (senin mevcut temizleme kodların)
for token in ("```ql", "```QL", "```", "", "", "
", ""): q = q.replace(token, "") return q.strip() # ========================================== # MAHALLE KARŞILAŞTIRMA BAĞLAMI # ========================================== def prepare_comparison(city1, district1, neigh1,city2, district2, neigh2): """ Butona basıldığında: - Her iki mahalle için OSM özetini hazırlar - İki metin döndürür - LLM için karşılaştırma bağlamını üretir """ city1 = (city1 or "").strip() district1 = (district1 or "").strip() neigh1 = (neigh1 or "").strip() city2 = (city2 or "").strip() district2 = (district2 or "").strip() neigh2 = (neigh2 or "").strip() if not city1 or not district1 or not city1 or not neigh1 or not district2 or not neigh2: msg = "Şehir, ilçe ve iki mahalle de girilmelidir." return (msg, msg, "", "Harita için yeterli veri yok.") # Varsayılan olarak boş metinler labels1 = "Bu mahalle için isim verisi çıkarılamadı." labels2 = "Bu mahalle için isim verisi çıkarılamadı." # Mahalle 1 gdf1 = get_neighborhood_gdf(city1, district1, neigh1) if gdf1 is None or len(gdf1) == 0: stats1 = f"{city1} / {district1} / {neigh1} için veri bulunamadı." summary1 = None else: pois1 = get_pois_within(gdf1) summary1 = summarize_pois(gdf1, pois1) stats1 = build_stats_text(summary1) # >>> POI isim metni labels1 = build_poi_names_text(pois1) # Mahalle 2 gdf2 = get_neighborhood_gdf(city2, district2, neigh2) if gdf2 is None or len(gdf2) == 0: stats2 = f"{city2} / {district2} / {neigh2} için veri bulunamadı." summary2 = None else: pois2 = get_pois_within(gdf2) summary2 = summarize_pois(gdf2, pois2) stats2 = build_stats_text(summary2) # >>> POI isim metni labels2 = build_poi_names_text(pois2) # LLM bağlamı compare_context_parts = [ f"Şehir: {city1}", "", f"1. Mahalle: {neigh1} (İlçe: {district1})", stats1, "", "1. mahalledeki önemli POI isimleri (okullar, eczaneler, marketler, kafeler vb.):", labels1, "", f"Şehir: {city2}", "", f"2. Mahalle: {neigh2} (İlçe: {district2})", stats2, "", "2. mahalledeki önemli POI isimleri (okullar, eczaneler, marketler, kafeler vb.):", labels2, "", "Bu iki mahalleyi alan, toplam POI sayısı, park, okul, kafe, restoran, eczane sayıları" " ve verilen POI isimleri açısından karşılaştır." " Kullanıcı soru sorarsa, hem sayısal verilere hem de POI isimlerine dayanarak" " açıklayıcı ve dengeli bir karşılaştırma yap." ] compare_context = "\n".join(compare_context_parts) # Harita HTML'i map_html = create_comparison_map(gdf1, gdf2) return stats1, stats2, compare_context, map_html,gdf1, gdf2 def add_poi_markers_to_map(pois, m, layer_prefix="POI"): """ POI GeoDataFrame'ini alır, amenity/leisure/shop/railway/highway/public_transport sütunlarına göre kategorik katmanlar oluşturup haritaya ekler. """ if pois is None or len(pois) == 0: return # Gerekirse WGS84'e (lat/lon) çevir try: if pois.crs is not None and pois.crs.to_epsg() != 4326: pois = pois.to_crs(epsg=4326) except Exception: # CRS yoksa veya hata olursa direkt devam pass # >>> ÖNEMLİ: layer_groups burada tanımlanmalı layer_groups = {} # {kategori_ismi: folium.FeatureGroup} for _, row in pois.iterrows(): geom = row.geometry if geom is None or geom.is_empty: continue # Nokta değilse centroid al try: if geom.geom_type == "Point": lat, lon = geom.y, geom.x else: c = geom.centroid lat, lon = c.y, c.x except Exception: continue amenity = row.get("amenity") leisure = row.get("leisure") shop = row.get("shop") highway = row.get("highway") railway = row.get("railway") public_transport = row.get("public_transport") # Kategori belirle (öncelik: amenity > leisure > shop > railway > highway > public_transport) if isinstance(amenity, str): cat = f"Amenity: {amenity}" elif isinstance(leisure, str): cat = f"Leisure: {leisure}" elif isinstance(shop, str): cat = f"Shop: {shop}" elif isinstance(railway, str): cat = f"Railway: {railway}" elif isinstance(highway, str): cat = f"Highway: {highway}" elif isinstance(public_transport, str): cat = f"PT: {public_transport}" else: cat = "Diğer" layer_name = f"{layer_prefix} - {cat}" if layer_name not in layer_groups: fg = folium.FeatureGroup(name=layer_name, show=True) fg.add_to(m) layer_groups[layer_name] = fg name = row.get("name") popup_items = [] # Önce isim if isinstance(name, str) and name.strip(): popup_items.append(name.strip()) if isinstance(amenity, str): popup_items.append(f"amenity={amenity}") if isinstance(leisure, str): popup_items.append(f"leisure={leisure}") if isinstance(shop, str): popup_items.append(f"shop={shop}") if isinstance(railway, str): popup_items.append(f"railway={railway}") if isinstance(highway, str): popup_items.append(f"highway={highway}") if isinstance(public_transport, str): popup_items.append(f"public_transport={public_transport}") popup_text = ", ".join(popup_items) if popup_items else "POI" folium.CircleMarker( location=[lat, lon], radius=4, popup=popup_text, tooltip=name.strip() if isinstance(name, str) and name.strip() else None, weight=1, fill=True, fill_opacity=0.7, ).add_to(layer_groups[layer_name]) def create_comparison_map(gdf1, gdf2): """ İki mahalle poligonunu tek bir Folium haritasında gösterir. + Her mahalle için POI'leri kategorik katmanlar hâlinde ekler. """ # Hiç veri yoksa if (gdf1 is None or len(gdf1) == 0) and (gdf2 is None or len(gdf2) == 0): return "Harita için yeterli veri yok." # Merkez olarak mevcut bir poligonun centroid'ini al centroid = None if gdf1 is not None and len(gdf1) > 0: centroid = gdf1.geometry.iloc[0].centroid elif gdf2 is not None and len(gdf2) > 0: centroid = gdf2.geometry.iloc[0].centroid if centroid is None: return "Geometri bulunamadı." m = folium.Map(location=[centroid.y, centroid.x], zoom_start=14) # Mahalle 1 pois1 = None if gdf1 is not None and len(gdf1) > 0: name1 = str(gdf1.get("neighborhood", ["Mahalle 1"]).iloc[0]) folium.GeoJson( gdf1.geometry.__geo_interface__, name=name1, style_function=lambda feat: { "color": "red", "fill": False, "weight": 3, }, ).add_to(m) # >>> Mahalle 1 için POI'ler pois1 = get_pois_within(gdf1) add_poi_markers_to_map(pois1, m, layer_prefix=f"{name1} POI") # Mahalle 2 pois2 = None if gdf2 is not None and len(gdf2) > 0: name2 = str(gdf2.get("neighborhood", ["Mahalle 2"]).iloc[0]) folium.GeoJson( gdf2.geometry.__geo_interface__, name=name2, style_function=lambda feat: { "color": "blue", "fill": False, "weight": 3, }, ).add_to(m) # >>> Mahalle 2 için POI'ler pois2 = get_pois_within(gdf2) add_poi_markers_to_map(pois2, m, layer_prefix=f"{name2} POI") folium.LayerControl().add_to(m) return m._repr_html_() def run_overpass_to_map(query: str, previous_elements: list | None, previous_spatial: str | None, layer_color: str, base_gdf1=None, base_gdf2=None): layer_name = "Spatial Query" query = normalize_overpass_query(query) if 'normalize_overpass_query' in globals() else query if not query or not query.strip(): return ( "Overpass sorgusu boş.", previous_spatial or "Geçerli bir Overpass sorgusu sağlanmadı.", previous_elements, ) url = "https://overpass-api.de/api/interpreter" try: resp = requests.post(url, data={"data": query}, timeout=30) resp.raise_for_status() data = resp.json() except Exception as e: print("Overpass isteği hatası:", e) try: print("Overpass response text:", resp.text[:500]) except Exception: pass return ( f"Overpass isteği hatası: {e}", previous_spatial or "Overpass isteğinde hata oluştu, veri yok.", previous_elements, ) new_elements = data.get("elements", []) if not new_elements: return ( "Overpass sonucu: veri bulunamadı.", previous_spatial or "Overpass sonucu: hiç element bulunamadı.", previous_elements, ) # ---- Eski + yeni elementleri birleştir ---- if not previous_elements: all_elements = new_elements else: all_elements = previous_elements + new_elements # ---- Merkez bul (tüm elementler üzerinden) ---- center_lat, center_lon = None, None for el in all_elements: if "lat" in el and "lon" in el: center_lat, center_lon = el["lat"], el["lon"] break if center_lat is None: return ( "Overpass sonucu: nokta verisi yok.", previous_spatial or "Overpass sonucu: nokta verisi bulunamadı.", all_elements, ) # ---- Haritayı çiz ---- # Önce merkez: mümkünse mahallelerden, yoksa Overpass noktalardan if base_gdf1 is not None and len(base_gdf1) > 0: c = base_gdf1.geometry.iloc[0].centroid center_lat, center_lon = c.y, c.x elif base_gdf2 is not None and len(base_gdf2) > 0: c = base_gdf2.geometry.iloc[0].centroid center_lat, center_lon = c.y, c.x # (aksi halde yukarıda all_elements'tan zaten bulduk) m = folium.Map(location=[center_lat, center_lon], zoom_start=14) # 1. mahalleyi tekrar çiz if base_gdf1 is not None and len(base_gdf1) > 0: name1 = str(base_gdf1.get("neighborhood", ["Mahalle 1"]).iloc[0]) folium.GeoJson( base_gdf1.geometry.__geo_interface__, name=name1, style_function=lambda feat: { "color": "red", "fill": False, "weight": 3, }, ).add_to(m) pois1 = get_pois_within(base_gdf1) add_poi_markers_to_map(pois1, m, layer_prefix=f"{name1} POI") # 2. mahalleyi tekrar çiz if base_gdf2 is not None and len(base_gdf2) > 0: name2 = str(base_gdf2.get("neighborhood", ["Mahalle 2"]).iloc[0]) folium.GeoJson( base_gdf2.geometry.__geo_interface__, name=name2, style_function=lambda feat: { "color": "blue", "fill": False, "weight": 3, }, ).add_to(m) pois2 = get_pois_within(base_gdf2) add_poi_markers_to_map(pois2, m, layer_prefix=f"{name2} POI") # Burada tek bir "spatial" katman oluşturuyoruz fg_nodes = folium.FeatureGroup( name=f"{layer_name} - Noktalar", # katman adı show=True ) fg_nodes.add_to(m) fg_ways = folium.FeatureGroup( name=f"{layer_name} - Yollar", show=True ) fg_ways.add_to(m) for el in all_elements: etype = el.get("type") tags = el.get("tags", {}) or {} name = tags.get("name", "") popup_items = [] if name: popup_items.append(name) for k, v in tags.items(): if k != "name": popup_items.append(f"{k}={v}") popup_text = "