#!/usr/bin/env python3 """ Adres Analyse - Rapport generator Gebruikt PDOK voor geocoding, ORS voor isochronen/routes en PostGIS OSM-data voor voorzieningen. Slaat rapport en HTML-kaarten op in /root/mkdocs/docs/docs/Huizenjacht/ """ import json import sys import os import re import urllib.request import urllib.parse from datetime import datetime import base64 from io import BytesIO try: from PIL import Image HAS_PIL = True except ImportError: HAS_PIL = False from voorzieningen import analyse_voorzieningen, format_voorzieningen_markdown, format_afstand, format_duur # Config ORS_BASE = "http://192.168.1.71:9080/ors/v2" PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1" MKDOCS_DIR = "/root/mkdocs/docs/docs/Huizenjacht" def geocode_adres(adres): """Geocode adres naar coördinaten via PDOK Locatieserver""" 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()) if not data['response']['docs']: raise ValueError(f"Adres niet gevonden: {adres}") doc = None for d in data['response']['docs']: if d.get('type') == 'adres': doc = d break if not doc: doc = data['response']['docs'][0] point = doc['centroide_ll'] coords = point.replace('POINT(', '').replace(')', '').split() lon, lat = float(coords[0]), float(coords[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='driving-car', minuten=20): """Haal isochroon op van ORS""" url = f"{ORS_BASE}/isochrones/{profiel}" payload = { "locations": [[lon, lat]], "range": [minuten * 60], "attributes": ["area", "reachfactor", "total_pop"] } req = urllib.request.Request( url, data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json'}, method='POST' ) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read()) def format_oppervlakte(m2): """Converteer m² naar leesbaar formaat""" if m2 >= 1_000_000: return f"{m2/1_000_000:.1f} km²" elif m2 >= 10_000: return f"{m2/10_000:.1f} ha" else: return f"{m2:.0f} m²" def schrijf_naar_mkdocs(content, adres, nummer, ext=".md"): """Schrijf content naar MkDocs docs map als bestand met oplopend nummer""" os.makedirs(MKDOCS_DIR, exist_ok=True) safe_name = re.sub(r'[^a-zA-Z0-9]+', '-', adres) safe_name = safe_name.strip('-') filename = f"{nummer}-{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}" filepath = os.path.join(MKDOCS_DIR, filename) with open(filepath, 'w', encoding='utf-8') as f: f.write(content) return filepath, filename def genereer_isochroon_html(adres_naam, lat, lon, auto_iso, fiets_iso, voorzieningen=None): """Genereer een zelf-contained HTML kaart met Leaflet + POI markers""" # Bouw JSON voor POI markers poi_json = {"supermarkten": [], "scholen": []} if voorzieningen: for s in voorzieningen.get("supermarkten", []): poi_json["supermarkten"].append({ "naam": s.naam, "lat": s.lat, "lon": s.lon, "hemelsbreed": round(s.hemelsbreed_m), "fiets": f"{format_afstand(s.fiets_afstand_m)} / {format_duur(s.fiets_duur_s)}" if s.fiets_afstand_m else "n.b.", "auto": f"{format_afstand(s.auto_afstand_m)} / {format_duur(s.auto_duur_s)}" if s.auto_afstand_m else "n.b.", }) for s in voorzieningen.get("scholen", []): poi_json["scholen"].append({ "naam": s.naam, "lat": s.lat, "lon": s.lon, "hemelsbreed": round(s.hemelsbreed_m), "fiets": f"{format_afstand(s.fiets_afstand_m)} / {format_duur(s.fiets_duur_s)}" if s.fiets_afstand_m else "n.b.", "auto": f"{format_afstand(s.auto_afstand_m)} / {format_duur(s.auto_duur_s)}" if s.auto_afstand_m else "n.b.", }) html = f''' Isochroon - {adres_naam}
''' return html def print_isochroon_sectie(titel, icoon, iso_data): """Genereer isochroon sectie als tekst""" lines = [] lines.append(f"{icoon} **{titel}**") if 'features' in iso_data and iso_data['features']: props = iso_data['features'][0]['properties'] area_m2 = props.get('area', 0) reach = props.get('reachfactor', 0) lines.append(f"**Oppervlakte:** {format_oppervlakte(area_m2)}") lines.append(f"**Reach factor:** {reach:.3f}") bbox = iso_data.get('bbox', []) if len(bbox) == 4: lines.append(f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E") else: lines.append("❌ Geen isochroon data ontvangen") lines.append("") return "\n".join(lines) def genereer_rapport(adres_input): """Genereer volledig adres analyse rapport""" lines = [] # Tel bestaande genummerde rapporten voor oplopend nummer # Alleen bestanden die beginnen met cijfer-dash (bv. "1-Griendvelden...") alle_files = os.listdir(MKDOCS_DIR) if os.path.exists(MKDOCS_DIR) else [] genummerde_files = [f for f in alle_files if f.endswith('.md') and re.match(r'^\d+-', f)] nummer = len(genummerde_files) + 1 lines.append(f"# {nummer}. {adres_input}") lines.append(f"*Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}*") lines.append("") lines.append("## 🔍 Geocoding") locatie = geocode_adres(adres_input) lines.append(f"**Adres:** {locatie['adres']}") lines.append(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}") lines.append("") auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20) fiets_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'cycling-regular', 30) lines.append("## 🚗 Bereikbaarheid") lines.append(print_isochroon_sectie("Auto (20 min)", "🚗", auto_iso)) lines.append(print_isochroon_sectie("Fiets (30 min)", "🚲", fiets_iso)) lines.append("## 🏙️ Voorzieningen dichtbij") voorzieningen = analyse_voorzieningen(locatie['lon'], locatie['lat'], limit=5) lines.append(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten'])) lines.append("") lines.append(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen'])) lines.append("") # Genereer interactieve HTML kaart en sla op in MkDocs html_content = genereer_isochroon_html( locatie['adres'], locatie['lat'], locatie['lon'], auto_iso, fiets_iso, voorzieningen=voorzieningen ) html_filepath, html_filename = schrijf_naar_mkdocs(html_content, locatie['adres'], nummer, ext=".html") # Voeg link toe aan markdown rapport (betrouwbaar via directe link) lines.append("## 🗺️ Interactieve kaart") lines.append(f"") lines.append(f"**[📍 Bekijk interactieve isochroon kaart]({html_filename})**{{:target=\"_blank\"}}") lines.append("") lines.append("*Klik op de link om de kaart in een nieuw tabblad te openen. Hier kun je zoomen, pannen en de isochronen bekijken.*") lines.append("") # Ruwe geojson output_file = f"/tmp/isochronen_{datetime.now().strftime('%Y%m%d_%H%M%S')}.geojson" with open(output_file, 'w') as f: json.dump({'auto_20_min': auto_iso, 'fiets_30_min': fiets_iso}, f) lines.append("---") lines.append("*Onderdelen: geocoding, isochronen, voorzieningen, interactieve kaart*") rapport_text = "\n".join(lines) # Print naar stdout print(rapport_text) # Sla op in MkDocs md_filepath, md_filename = schrijf_naar_mkdocs(rapport_text, adres_input, nummer, ext=".md") print(f"\n📝 Rapport opgeslagen: {md_filepath}") if __name__ == '__main__': adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best" genereer_rapport(adres)