Release v2.0 - add Netherlands POI analysis
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user