#!/usr/bin/env python3 """ Genereer interactieve kaart voor adres analyse. Toont isochronen plus dichtstbijzijnde supermarkten en scholen uit lokale PostGIS OSM-data. """ import json import sys import urllib.parse import urllib.request import folium from folium.plugins import MarkerCluster from voorzieningen import analyse_voorzieningen, format_afstand, format_duur ORS_BASE = "http://192.168.1.71:9080/ors/v2" PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1" def geocode_adres(adres): params = urllib.parse.urlencode({ "q": adres, "rows": 5, "fl": "centroide_ll,weergavenaam,postcode,woonplaatsnaam,type,huisnummer", }) url = f"{PDOK_BASE}/free?{params}" with urllib.request.urlopen(url, timeout=10) as resp: data = json.loads(resp.read()) docs = data.get("response", {}).get("docs", []) if not docs: raise ValueError(f"Adres niet gevonden: {adres}") doc = next((d for d in docs if d.get("type") == "adres"), docs[0]) point = doc["centroide_ll"].replace("POINT(", "").replace(")", "").split() lon, lat = float(point[0]), float(point[1]) return { "adres": doc.get("weergavenaam", adres), "postcode": doc.get("postcode", ""), "woonplaats": doc.get("woonplaatsnaam", ""), "lat": lat, "lon": lon, } def haal_isochroon(lon, lat, profiel, minuten): payload = json.dumps({ "locations": [[lon, lat]], "range": [minuten * 60], "attributes": ["area", "reachfactor", "total_pop"], }).encode() req = urllib.request.Request( f"{ORS_BASE}/isochrones/{profiel}", data=payload, headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(req, timeout=60) as resp: return json.loads(resp.read()) def polygon_bounds(feature_collection): bbox = feature_collection.get("bbox", []) if len(bbox) == 4: return [[bbox[1], bbox[0]], [bbox[3], bbox[2]]] bounds = [] for feature in feature_collection.get("features", []): coords = feature.get("geometry", {}).get("coordinates", [[]])[0] bounds.extend([[lat, lon] for lon, lat in coords]) return bounds def voeg_isochroon_toe(group, iso, label, kleur, tooltip): for feature in iso.get("features", []): coords = feature["geometry"]["coordinates"][0] props = feature.get("properties", {}) area_km2 = props.get("area", 0) / 1_000_000 reach = props.get("reachfactor", 0) folium.Polygon( locations=[[lat, lon] for lon, lat in coords], color=kleur, weight=3, fill=True, fill_color=kleur, fill_opacity=0.18, popup=( f"{label}
" f"Oppervlakte: {area_km2:.1f} km²
" f"Reach factor: {reach:.3f}" ), tooltip=tooltip, ).add_to(group) def voeg_pois_toe(group, pois, kleur, icon): for poi in pois: popup = ( f"{poi.naam}
" f"Categorie: {poi.categorie}
" f"Hemelsbreed: {format_afstand(poi.hemelsbreed_m)}
" f"Fiets: {format_afstand(poi.fiets_afstand_m)} / {format_duur(poi.fiets_duur_s)}
" f"Auto: {format_afstand(poi.auto_afstand_m)} / {format_duur(poi.auto_duur_s)}
" f"Bron: {poi.bron}" ) folium.Marker( [poi.lat, poi.lon], popup=popup, tooltip=f"{poi.categorie}: {poi.naam}", icon=folium.Icon(color=kleur, icon=icon, prefix="fa"), ).add_to(group) def genereer_kaart(adres, output_file="/root/adres-analyse/kaart.html"): locatie = geocode_adres(adres) lat, lon = locatie["lat"], locatie["lon"] auto_iso = haal_isochroon(lon, lat, "driving-car", 20) fiets_iso = haal_isochroon(lon, lat, "cycling-regular", 30) voorzieningen = analyse_voorzieningen(lon, lat, limit=5) m = folium.Map(location=[lat, lon], zoom_start=11, tiles="OpenStreetMap") folium.Marker( [lat, lon], popup=locatie["adres"], tooltip=locatie["adres"], icon=folium.Icon(color="red", icon="home"), ).add_to(m) auto_group = folium.FeatureGroup(name="Isochroon: 20 min auto", show=True) fiets_group = folium.FeatureGroup(name="Isochroon: 30 min fiets", show=True) super_group = MarkerCluster(name="Supermarkten", show=True) school_group = MarkerCluster(name="Scholen", show=True) voeg_isochroon_toe(auto_group, auto_iso, "Auto 20 min", "#2196F3", "Bereikbaar in 20 min (auto)") voeg_isochroon_toe(fiets_group, fiets_iso, "Fiets 30 min", "#2E7D32", "Bereikbaar in 30 min (fiets)") voeg_pois_toe(super_group, voorzieningen["supermarkten"], "orange", "shopping-cart") voeg_pois_toe(school_group, voorzieningen["scholen"], "purple", "graduation-cap") auto_group.add_to(m) fiets_group.add_to(m) super_group.add_to(m) school_group.add_to(m) folium.LayerControl(collapsed=False).add_to(m) all_bounds = polygon_bounds(auto_iso) + polygon_bounds(fiets_iso) all_bounds += [[p.lat, p.lon] for groep in voorzieningen.values() for p in groep] if all_bounds: m.fit_bounds(all_bounds) title_html = f'''
📍 Adres Analyse
{locatie["adres"]}
■ 20 min autorijden
■ 30 min fietsen
🛒 Supermarkten · 🏫 Scholen
''' m.get_root().html.add_child(folium.Element(title_html)) m.save(output_file) print(f"Kaart gegenereerd: {output_file}") print(f"Adres: {locatie['adres']}") print(f"Coördinaten: {lat}, {lon}") print(f"Supermarkten: {len(voorzieningen['supermarkten'])}") print(f"Scholen: {len(voorzieningen['scholen'])}") if __name__ == "__main__": adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best" output = sys.argv[2] if len(sys.argv) > 2 else "/root/adres-analyse/kaart.html" genereer_kaart(adres, output)