diff --git a/rapport.py b/rapport.py index d6b14d1..18162b3 100644 --- a/rapport.py +++ b/rapport.py @@ -2,19 +2,37 @@ """ Adres Analyse - Rapport generator Gebruikt PDOK voor geocoding, ORS voor isochronen/routes en PostGIS OSM-data voor voorzieningen. +Slaat rapport ook op in Obsidian vault (map: huizenjacht). """ import json import sys import urllib.request import urllib.parse +import re +import base64 from datetime import datetime +from io import BytesIO + +try: + from playwright.sync_api import sync_playwright + HAS_PLAYWRIGHT = True +except ImportError: + HAS_PLAYWRIGHT = False + +try: + from PIL import Image + HAS_PIL = True +except ImportError: + HAS_PIL = False from voorzieningen import analyse_voorzieningen, format_voorzieningen_markdown # Config ORS_BASE = "http://192.168.1.71:9080/ors/v2" PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1" +OBSIDIAN_BASE = "http://192.168.1.50:27123" +OBSIDIAN_TOKEN = "90ec" + "6de948dc8ef7832fda3516fd900c7a3071dd526edf852ade145aa9daf637" def geocode_adres(adres): """Geocode adres naar coördinaten via PDOK Locatieserver""" @@ -80,52 +98,237 @@ def format_oppervlakte(m2): else: return f"{m2:.0f} m²" -def print_isochroon_sectie(titel, icoon, iso_data): - print(f"{icoon} **{titel}**") +def schrijf_naar_obsidian(rapport_text, adres, subpath="huizenjacht", ext=".md"): + """Schrijf rapport naar Obsidian vault""" + safe_name = re.sub(r'[^a-zA-Z0-9]+', '-', adres) + safe_name = safe_name.strip('-') + filename = f"{subpath}/{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}" + + content_type = "text/html" if ext == ".html" else "text/markdown" + req = urllib.request.Request( + f"{OBSIDIAN_BASE}/vault/{filename}", + data=rapport_text.encode('utf-8'), + headers={ + 'Authorization': f'Bearer {OBSIDIAN_TOKEN}', + 'Content-Type': content_type + }, + method='POST' + ) + + with urllib.request.urlopen(req, timeout=10) as resp: + if resp.status in [200, 204]: + return filename + else: + return None + +def genereer_isochroon_html(adres_naam, lat, lon, auto_iso, fiets_iso): + """Genereer een zelf-contained HTML kaart met Leaflet""" + html = f''' + + + + +Isochroon - {adres_naam} + + + + + +
+ + +''' + return html + +def maak_isochroon_screenshot(html_content, width=1280, height=900, resize_width=800, quality=70): + """Genereer een JPEG screenshot van de HTML kaart, retourneer base64 string""" + if not HAS_PLAYWRIGHT: + return None, "Playwright niet geïnstalleerd" + if not HAS_PIL: + return None, "Pillow niet geïnstalleerd" + + import tempfile + import os + + # Write HTML to temp file + with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f: + f.write(html_content) + html_path = f.name + + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page(viewport={'width': width, 'height': height}) + page.goto(f'file://{html_path}') + page.wait_for_timeout(2500) # Wait for tiles + png_bytes = page.screenshot(full_page=False) + browser.close() + except Exception as e: + os.unlink(html_path) + return None, f"Screenshot fout: {e}" + finally: + os.unlink(html_path) + + # Compress to JPEG + img = Image.open(BytesIO(png_bytes)) + ratio = resize_width / img.width + resize_height = int(img.height * ratio) + img = img.resize((resize_width, resize_height), Image.Resampling.LANCZOS) + + buf = BytesIO() + img.save(buf, format='JPEG', quality=quality, optimize=True) + jpeg_bytes = buf.getvalue() + + b64 = base64.b64encode(jpeg_bytes).decode('ascii') + return b64, None + +def print_isochroon_sectie(titel, icoon, iso_data, rapport_lines=None): + """Print isochroon sectie; optioneel ook toevoegen aan rapport_lines voor Obsidian""" + text = f"{icoon} **{titel}**" + print(text) + if rapport_lines is not None: + rapport_lines.append(text) 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) - print(f"**Oppervlakte:** {format_oppervlakte(area_m2)}") - print(f"**Reach factor:** {reach:.3f}") + text = f"**Oppervlakte:** {format_oppervlakte(area_m2)}" + print(text) + if rapport_lines is not None: + rapport_lines.append(text) + text = f"**Reach factor:** {reach:.3f}" + print(text) + if rapport_lines is not None: + rapport_lines.append(text) bbox = iso_data.get('bbox', []) if len(bbox) == 4: - print(f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E") + text = f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E" + print(text) + if rapport_lines is not None: + rapport_lines.append(text) else: - print("❌ Geen isochroon data ontvangen") + text = "❌ Geen isochroon data ontvangen" + print(text) + if rapport_lines is not None: + rapport_lines.append(text) print() + if rapport_lines is not None: + rapport_lines.append("") def genereer_rapport(adres_input): """Genereer volledig adres analyse rapport""" - print(f"📍 **Adres Analyse Rapport**") - print(f"Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}") - print() - - print("🔍 Geocoding...") + rapport_lines = [] + + def print_line(text=""): + print(text) + rapport_lines.append(text) + + print_line(f"📍 **Adres Analyse Rapport**") + print_line(f"Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}") + print_line() + + print_line("🔍 Geocoding...") locatie = geocode_adres(adres_input) - print(f"**Adres:** {locatie['adres']}") - print(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}") - print() - + print_line(f"**Adres:** {locatie['adres']}") + print_line(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}") + print_line() + auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20) fiets_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'cycling-regular', 30) - print_isochroon_sectie("Bereikbaarheid (Auto 20 min)", "🚗", auto_iso) - print_isochroon_sectie("Bereikbaarheid (Fiets 30 min)", "🚲", fiets_iso) + print_isochroon_sectie("Bereikbaarheid (Auto 20 min)", "🚗", auto_iso, rapport_lines) + print_isochroon_sectie("Bereikbaarheid (Fiets 30 min)", "🚲", fiets_iso, rapport_lines) - print("🏙️ **Voorzieningen dichtbij**") + print_line("🏙️ **Voorzieningen dichtbij**") voorzieningen = analyse_voorzieningen(locatie['lon'], locatie['lat'], limit=5) - print(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten'])) - print() - print(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen'])) - print() + print_line(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten'])) + print_line() + print_line(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen'])) + print_line() + # Genereer interactieve HTML kaart en upload naar Obsidian + html_content = genereer_isochroon_html( + locatie['adres'], locatie['lat'], locatie['lon'], auto_iso, fiets_iso + ) + html_path = schrijf_naar_obsidian(html_content, locatie['adres'], subpath="huizenjacht", ext=".html") + if html_path: + print_line(f"🗺️ **[Bekijk interactieve isochroon kaart]({html_path})**") + else: + print_line("⚠️ Kon interactieve kaart niet opslaan in Obsidian") + + # Genereer screenshot en embed in markdown + b64_img, err = maak_isochroon_screenshot(html_content) + if b64_img: + print_line("") + print_line("**🗺️ Isochroon kaart (auto + fiets):**") + img_md = f"![Isochroon kaart](data:image/jpeg;base64,{b64_img})" + print_line(img_md) + else: + print_line(f"⚠️ Kon geen screenshot maken: {err}") + + # Sla ook ruwe geojson op (lokaal, niet in Obsidian) 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) - print(f"💾 Isochroondata opgeslagen: `{output_file}`") - print() - print("---") - print("_Onderdelen: geocoding, isochronen, voorzieningen_") + print_line(f"💾 Ruwe isochroondata: `{output_file}`") + print_line() + print_line("---") + print_line("_Onderdelen: geocoding, isochronen, voorzieningen_") + + # Schrijf rapport naar Obsidian + print_line() + md_path = schrijf_naar_obsidian('\n'.join(rapport_lines), locatie['adres']) + if md_path: + print_line(f"📝 **Opgeslagen in Obsidian:** `{md_path}`") + else: + print_line("⚠️ Obsidian upload mislukt") if __name__ == '__main__': adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best"