175 lines
6.2 KiB
Python
175 lines
6.2 KiB
Python
#!/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"<b>{label}</b><br>"
|
|
f"Oppervlakte: {area_km2:.1f} km²<br>"
|
|
f"Reach factor: {reach:.3f}"
|
|
),
|
|
tooltip=tooltip,
|
|
).add_to(group)
|
|
|
|
|
|
def voeg_pois_toe(group, pois, kleur, icon):
|
|
for poi in pois:
|
|
popup = (
|
|
f"<b>{poi.naam}</b><br>"
|
|
f"Categorie: {poi.categorie}<br>"
|
|
f"Hemelsbreed: {format_afstand(poi.hemelsbreed_m)}<br>"
|
|
f"Fiets: {format_afstand(poi.fiets_afstand_m)} / {format_duur(poi.fiets_duur_s)}<br>"
|
|
f"Auto: {format_afstand(poi.auto_afstand_m)} / {format_duur(poi.auto_duur_s)}<br>"
|
|
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'''
|
|
<div style="position:fixed;top:10px;left:60px;z-index:1000;background:white;padding:10px 15px;border-radius:8px;box-shadow:0 2px 6px rgba(0,0,0,0.3);font-family:sans-serif;font-size:14px;line-height:1.35;">
|
|
<b>📍 Adres Analyse</b><br>
|
|
{locatie["adres"]}<br>
|
|
<span style="color:#2196F3">■</span> 20 min autorijden<br>
|
|
<span style="color:#2E7D32">■</span> 30 min fietsen<br>
|
|
🛒 Supermarkten · 🏫 Scholen
|
|
</div>
|
|
'''
|
|
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)
|