Compare commits
4 Commits
5c6fc8c277
...
v2.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 3edea47e88 | |||
| d9da2964dd | |||
| 60596c0510 | |||
| d7ffa382a7 |
+214
-25
@@ -2,19 +2,31 @@
|
|||||||
"""
|
"""
|
||||||
Adres Analyse - Rapport generator
|
Adres Analyse - Rapport generator
|
||||||
Gebruikt PDOK voor geocoding, ORS voor isochronen/routes en PostGIS OSM-data voor voorzieningen.
|
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 json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from voorzieningen import analyse_voorzieningen, format_voorzieningen_markdown
|
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
|
# Config
|
||||||
ORS_BASE = "http://192.168.1.71:9080/ors/v2"
|
ORS_BASE = "http://192.168.1.71:9080/ors/v2"
|
||||||
PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1"
|
PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1"
|
||||||
|
MKDOCS_DIR = "/root/mkdocs/docs/docs/Huizenjacht"
|
||||||
|
|
||||||
def geocode_adres(adres):
|
def geocode_adres(adres):
|
||||||
"""Geocode adres naar coördinaten via PDOK Locatieserver"""
|
"""Geocode adres naar coördinaten via PDOK Locatieserver"""
|
||||||
@@ -80,52 +92,229 @@ def format_oppervlakte(m2):
|
|||||||
else:
|
else:
|
||||||
return f"{m2:.0f} m²"
|
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'''<!DOCTYPE html>
|
||||||
|
<html lang="nl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Isochroon - {adres_naam}</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<style>
|
||||||
|
body {{ margin: 0; padding: 0; font-family: system-ui, -apple-system, sans-serif; }}
|
||||||
|
#map {{ height: 100vh; width: 100vw; }}
|
||||||
|
.legend {{ background: white; padding: 10px; border-radius: 4px; box-shadow: 0 0 15px rgba(0,0,0,0.2); line-height: 1.5; }}
|
||||||
|
.legend i {{ width: 18px; height: 18px; float: left; margin-right: 8px; opacity: 0.7; border: 1px solid #333; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script>
|
||||||
|
var autoGeojson = {json.dumps(auto_iso, ensure_ascii=False)};
|
||||||
|
var fietsGeojson = {json.dumps(fiets_iso, ensure_ascii=False)};
|
||||||
|
var voorzieningen = {json.dumps(poi_json, ensure_ascii=False)};
|
||||||
|
|
||||||
|
var map = L.map('map').setView([{lat}, {lon}], 11);
|
||||||
|
|
||||||
|
L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}}).addTo(map);
|
||||||
|
|
||||||
|
var autoLayer = L.geoJSON(autoGeojson, {{
|
||||||
|
style: {{ color: '#2563eb', weight: 2, fillColor: '#3b82f6', fillOpacity: 0.2 }},
|
||||||
|
onEachFeature: function(feature, layer) {{
|
||||||
|
var props = feature.properties;
|
||||||
|
layer.bindPopup('<b>Auto 20 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km²<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
||||||
|
}}
|
||||||
|
}}).addTo(map);
|
||||||
|
|
||||||
|
var fietsLayer = L.geoJSON(fietsGeojson, {{
|
||||||
|
style: {{ color: '#16a34a', weight: 2, fillColor: '#22c55e', fillOpacity: 0.2 }},
|
||||||
|
onEachFeature: function(feature, layer) {{
|
||||||
|
var props = feature.properties;
|
||||||
|
layer.bindPopup('<b>Fiets 30 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km²<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
||||||
|
}}
|
||||||
|
}}).addTo(map);
|
||||||
|
|
||||||
|
// Adres marker
|
||||||
|
L.marker([{lat}, {lon}]).addTo(map)
|
||||||
|
.bindPopup('<b>{adres_naam}</b>').openPopup();
|
||||||
|
|
||||||
|
// POI markers
|
||||||
|
var supermarktIcon = L.divIcon({{
|
||||||
|
className: 'poi-marker-supermarkt',
|
||||||
|
html: '<div style="background:#2563eb;color:white;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;border:2px solid white;box-shadow:0 1px 4px rgba(0,0,0,0.3)">S</div>',
|
||||||
|
iconSize: [24, 24],
|
||||||
|
iconAnchor: [12, 12],
|
||||||
|
popupAnchor: [0, -14]
|
||||||
|
}});
|
||||||
|
var schoolIcon = L.divIcon({{
|
||||||
|
className: 'poi-marker-school',
|
||||||
|
html: '<div style="background:#ea580c;color:white;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;border:2px solid white;box-shadow:0 1px 4px rgba(0,0,0,0.3)">S</div>',
|
||||||
|
iconSize: [24, 24],
|
||||||
|
iconAnchor: [12, 12],
|
||||||
|
popupAnchor: [0, -14]
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Supermarkten
|
||||||
|
if (voorzieningen.supermarkten) {{
|
||||||
|
voorzieningen.supermarkten.forEach(function(p) {{
|
||||||
|
L.marker([p.lat, p.lon], {{icon: supermarktIcon}}).addTo(map)
|
||||||
|
.bindPopup(
|
||||||
|
'<b>🛒 ' + p.naam + '</b><br>' +
|
||||||
|
'Hemelsbreed: ' + p.hemelsbreed + ' m<br>' +
|
||||||
|
'Fiets: ' + p.fiets + '<br>' +
|
||||||
|
'Auto: ' + p.auto
|
||||||
|
);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Scholen
|
||||||
|
if (voorzieningen.scholen) {{
|
||||||
|
voorzieningen.scholen.forEach(function(p) {{
|
||||||
|
L.marker([p.lat, p.lon], {{icon: schoolIcon}}).addTo(map)
|
||||||
|
.bindPopup(
|
||||||
|
'<b>🏫 ' + p.naam + '</b><br>' +
|
||||||
|
'Hemelsbreed: ' + p.hemelsbreed + ' m<br>' +
|
||||||
|
'Fiets: ' + p.fiets + '<br>' +
|
||||||
|
'Auto: ' + p.auto
|
||||||
|
);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
|
||||||
|
var legend = L.control({{position: 'bottomright'}});
|
||||||
|
legend.onAdd = function(map) {{
|
||||||
|
var div = L.DomUtil.create('div', 'legend');
|
||||||
|
div.innerHTML += '<h4 style="margin:0 0 5px 0">Isochronen</h4>';
|
||||||
|
div.innerHTML += '<i style="background:#3b82f6"></i> Auto 20 min<br>';
|
||||||
|
div.innerHTML += '<i style="background:#22c55e"></i> Fiets 30 min<br>';
|
||||||
|
div.innerHTML += '<i style="background:#ef4444"></i> Adres<br>';
|
||||||
|
div.innerHTML += '<i style="background:#2563eb;border-radius:50%;width:12px;height:12px;border:1px solid #999"></i> Supermarkt<br>';
|
||||||
|
div.innerHTML += '<i style="background:#ea580c;border-radius:50%;width:12px;height:12px;border:1px solid #999"></i> School';
|
||||||
|
return div;
|
||||||
|
}};
|
||||||
|
legend.addTo(map);
|
||||||
|
|
||||||
|
map.fitBounds(autoLayer.getBounds().extend(fietsLayer.getBounds()));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>'''
|
||||||
|
return html
|
||||||
|
|
||||||
def print_isochroon_sectie(titel, icoon, iso_data):
|
def print_isochroon_sectie(titel, icoon, iso_data):
|
||||||
print(f"{icoon} **{titel}**")
|
"""Genereer isochroon sectie als tekst"""
|
||||||
|
lines = []
|
||||||
|
lines.append(f"{icoon} **{titel}**")
|
||||||
if 'features' in iso_data and iso_data['features']:
|
if 'features' in iso_data and iso_data['features']:
|
||||||
props = iso_data['features'][0]['properties']
|
props = iso_data['features'][0]['properties']
|
||||||
area_m2 = props.get('area', 0)
|
area_m2 = props.get('area', 0)
|
||||||
reach = props.get('reachfactor', 0)
|
reach = props.get('reachfactor', 0)
|
||||||
print(f"**Oppervlakte:** {format_oppervlakte(area_m2)}")
|
lines.append(f"**Oppervlakte:** {format_oppervlakte(area_m2)}")
|
||||||
print(f"**Reach factor:** {reach:.3f}")
|
lines.append(f"**Reach factor:** {reach:.3f}")
|
||||||
bbox = iso_data.get('bbox', [])
|
bbox = iso_data.get('bbox', [])
|
||||||
if len(bbox) == 4:
|
if len(bbox) == 4:
|
||||||
print(f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E")
|
lines.append(f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E")
|
||||||
else:
|
else:
|
||||||
print("❌ Geen isochroon data ontvangen")
|
lines.append("❌ Geen isochroon data ontvangen")
|
||||||
print()
|
lines.append("")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
def genereer_rapport(adres_input):
|
def genereer_rapport(adres_input):
|
||||||
"""Genereer volledig adres analyse rapport"""
|
"""Genereer volledig adres analyse rapport"""
|
||||||
print(f"📍 **Adres Analyse Rapport**")
|
lines = []
|
||||||
print(f"Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
print("🔍 Geocoding...")
|
# 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)
|
locatie = geocode_adres(adres_input)
|
||||||
print(f"**Adres:** {locatie['adres']}")
|
lines.append(f"**Adres:** {locatie['adres']}")
|
||||||
print(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}")
|
lines.append(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}")
|
||||||
print()
|
lines.append("")
|
||||||
|
|
||||||
auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20)
|
auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20)
|
||||||
fiets_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'cycling-regular', 30)
|
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("🏙️ **Voorzieningen dichtbij**")
|
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)
|
voorzieningen = analyse_voorzieningen(locatie['lon'], locatie['lat'], limit=5)
|
||||||
print(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten']))
|
lines.append(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten']))
|
||||||
print()
|
lines.append("")
|
||||||
print(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen']))
|
lines.append(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen']))
|
||||||
print()
|
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"
|
output_file = f"/tmp/isochronen_{datetime.now().strftime('%Y%m%d_%H%M%S')}.geojson"
|
||||||
with open(output_file, 'w') as f:
|
with open(output_file, 'w') as f:
|
||||||
json.dump({'auto_20_min': auto_iso, 'fiets_30_min': fiets_iso}, f)
|
json.dump({'auto_20_min': auto_iso, 'fiets_30_min': fiets_iso}, f)
|
||||||
print(f"💾 Isochroondata opgeslagen: `{output_file}`")
|
lines.append("---")
|
||||||
print()
|
lines.append("*Onderdelen: geocoding, isochronen, voorzieningen, interactieve kaart*")
|
||||||
print("---")
|
|
||||||
print("_Onderdelen: geocoding, isochronen, voorzieningen_")
|
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__':
|
if __name__ == '__main__':
|
||||||
adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best"
|
adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best"
|
||||||
|
|||||||
+69
-147
@@ -2,24 +2,19 @@
|
|||||||
"""
|
"""
|
||||||
Adres Analyse - Rapport generator
|
Adres Analyse - Rapport generator
|
||||||
Gebruikt PDOK voor geocoding, ORS voor isochronen/routes en PostGIS OSM-data voor voorzieningen.
|
Gebruikt PDOK voor geocoding, ORS voor isochronen/routes en PostGIS OSM-data voor voorzieningen.
|
||||||
Slaat rapport ook op in Obsidian vault (map: huizenjacht).
|
Slaat rapport en HTML-kaarten op in /root/mkdocs/docs/docs/Huizenjacht/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import re
|
|
||||||
import base64
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import base64
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
try:
|
|
||||||
from playwright.sync_api import sync_playwright
|
|
||||||
HAS_PLAYWRIGHT = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_PLAYWRIGHT = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
HAS_PIL = True
|
HAS_PIL = True
|
||||||
@@ -31,8 +26,7 @@ from voorzieningen import analyse_voorzieningen, format_voorzieningen_markdown
|
|||||||
# Config
|
# Config
|
||||||
ORS_BASE = "http://192.168.1.71:9080/ors/v2"
|
ORS_BASE = "http://192.168.1.71:9080/ors/v2"
|
||||||
PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1"
|
PDOK_BASE = "https://api.pdok.nl/bzk/locatieserver/search/v3_1"
|
||||||
OBSIDIAN_BASE = "http://192.168.1.50:27123"
|
MKDOCS_DIR = "/root/mkdocs/docs/docs/Huizenjacht"
|
||||||
OBSIDIAN_TOKEN = "90ec" + "6de948dc8ef7832fda3516fd900c7a3071dd526edf852ade145aa9daf637"
|
|
||||||
|
|
||||||
def geocode_adres(adres):
|
def geocode_adres(adres):
|
||||||
"""Geocode adres naar coördinaten via PDOK Locatieserver"""
|
"""Geocode adres naar coördinaten via PDOK Locatieserver"""
|
||||||
@@ -98,28 +92,18 @@ def format_oppervlakte(m2):
|
|||||||
else:
|
else:
|
||||||
return f"{m2:.0f} m²"
|
return f"{m2:.0f} m²"
|
||||||
|
|
||||||
def schrijf_naar_obsidian(rapport_text, adres, subpath="huizenjacht", ext=".md"):
|
def schrijf_naar_mkdocs(content, adres, nummer, ext=".md"):
|
||||||
"""Schrijf rapport naar Obsidian vault"""
|
"""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 = re.sub(r'[^a-zA-Z0-9]+', '-', adres)
|
||||||
safe_name = safe_name.strip('-')
|
safe_name = safe_name.strip('-')
|
||||||
filename = f"{subpath}/{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}"
|
filename = f"{nummer}-{safe_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}"
|
||||||
|
filepath = os.path.join(MKDOCS_DIR, filename)
|
||||||
|
|
||||||
content_type = "text/html" if ext == ".html" else "text/markdown"
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
req = urllib.request.Request(
|
f.write(content)
|
||||||
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:
|
return filepath, filename
|
||||||
if resp.status in [200, 204]:
|
|
||||||
return filename
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def genereer_isochroon_html(adres_naam, lat, lon, auto_iso, fiets_iso):
|
def genereer_isochroon_html(adres_naam, lat, lon, auto_iso, fiets_iso):
|
||||||
"""Genereer een zelf-contained HTML kaart met Leaflet"""
|
"""Genereer een zelf-contained HTML kaart met Leaflet"""
|
||||||
@@ -129,10 +113,8 @@ def genereer_isochroon_html(adres_naam, lat, lon, auto_iso, fiets_iso):
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Isochroon - {adres_naam}</title>
|
<title>Isochroon - {adres_naam}</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
|
||||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
|
||||||
<style>
|
<style>
|
||||||
body {{ margin: 0; padding: 0; font-family: system-ui, -apple-system, sans-serif; }}
|
body {{ margin: 0; padding: 0; font-family: system-ui, -apple-system, sans-serif; }}
|
||||||
#map {{ height: 100vh; width: 100vw; }}
|
#map {{ height: 100vh; width: 100vw; }}
|
||||||
@@ -156,7 +138,7 @@ var autoLayer = L.geoJSON(autoGeojson, {{
|
|||||||
style: {{ color: '#2563eb', weight: 2, fillColor: '#3b82f6', fillOpacity: 0.2 }},
|
style: {{ color: '#2563eb', weight: 2, fillColor: '#3b82f6', fillOpacity: 0.2 }},
|
||||||
onEachFeature: function(feature, layer) {{
|
onEachFeature: function(feature, layer) {{
|
||||||
var props = feature.properties;
|
var props = feature.properties;
|
||||||
layer.bindPopup('<b>Auto 20 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km\u00b2<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
layer.bindPopup('<b>Auto 20 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km²<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
||||||
}}
|
}}
|
||||||
}}).addTo(map);
|
}}).addTo(map);
|
||||||
|
|
||||||
@@ -164,7 +146,7 @@ var fietsLayer = L.geoJSON(fietsGeojson, {{
|
|||||||
style: {{ color: '#16a34a', weight: 2, fillColor: '#22c55e', fillOpacity: 0.2 }},
|
style: {{ color: '#16a34a', weight: 2, fillColor: '#22c55e', fillOpacity: 0.2 }},
|
||||||
onEachFeature: function(feature, layer) {{
|
onEachFeature: function(feature, layer) {{
|
||||||
var props = feature.properties;
|
var props = feature.properties;
|
||||||
layer.bindPopup('<b>Fiets 30 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km\u00b2<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
layer.bindPopup('<b>Fiets 30 min</b><br>Oppervlakte: ' + (props.area/1000000).toFixed(1) + ' km²<br>Reach factor: ' + props.reachfactor.toFixed(3));
|
||||||
}}
|
}}
|
||||||
}}).addTo(map);
|
}}).addTo(map);
|
||||||
|
|
||||||
@@ -188,147 +170,87 @@ map.fitBounds(autoLayer.getBounds().extend(fietsLayer.getBounds()));
|
|||||||
</html>'''
|
</html>'''
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def maak_isochroon_screenshot(html_content, width=1280, height=900, resize_width=800, quality=70):
|
def print_isochroon_sectie(titel, icoon, iso_data):
|
||||||
"""Genereer een JPEG screenshot van de HTML kaart, retourneer base64 string"""
|
"""Genereer isochroon sectie als tekst"""
|
||||||
if not HAS_PLAYWRIGHT:
|
lines = []
|
||||||
return None, "Playwright niet geïnstalleerd"
|
lines.append(f"{icoon} **{titel}**")
|
||||||
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']:
|
if 'features' in iso_data and iso_data['features']:
|
||||||
props = iso_data['features'][0]['properties']
|
props = iso_data['features'][0]['properties']
|
||||||
area_m2 = props.get('area', 0)
|
area_m2 = props.get('area', 0)
|
||||||
reach = props.get('reachfactor', 0)
|
reach = props.get('reachfactor', 0)
|
||||||
text = f"**Oppervlakte:** {format_oppervlakte(area_m2)}"
|
lines.append(f"**Oppervlakte:** {format_oppervlakte(area_m2)}")
|
||||||
print(text)
|
lines.append(f"**Reach factor:** {reach:.3f}")
|
||||||
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', [])
|
bbox = iso_data.get('bbox', [])
|
||||||
if len(bbox) == 4:
|
if len(bbox) == 4:
|
||||||
text = f"**Bereik:** {bbox[1]:.3f}°-{bbox[3]:.3f}°N, {bbox[0]:.3f}°-{bbox[2]:.3f}°E"
|
lines.append(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:
|
else:
|
||||||
text = "❌ Geen isochroon data ontvangen"
|
lines.append("❌ Geen isochroon data ontvangen")
|
||||||
print(text)
|
lines.append("")
|
||||||
if rapport_lines is not None:
|
return "\n".join(lines)
|
||||||
rapport_lines.append(text)
|
|
||||||
print()
|
|
||||||
if rapport_lines is not None:
|
|
||||||
rapport_lines.append("")
|
|
||||||
|
|
||||||
def genereer_rapport(adres_input):
|
def genereer_rapport(adres_input):
|
||||||
"""Genereer volledig adres analyse rapport"""
|
"""Genereer volledig adres analyse rapport"""
|
||||||
rapport_lines = []
|
lines = []
|
||||||
|
|
||||||
def print_line(text=""):
|
# Tel bestaande genummerde rapporten voor oplopend nummer
|
||||||
print(text)
|
# Alleen bestanden die beginnen met cijfer-dash (bv. "1-Griendvelden...")
|
||||||
rapport_lines.append(text)
|
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
|
||||||
|
|
||||||
print_line(f"📍 **Adres Analyse Rapport**")
|
lines.append(f"# {nummer}. {adres_input}")
|
||||||
print_line(f"Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}")
|
lines.append(f"*Gegenereerd: {datetime.now().strftime('%d-%m-%Y %H:%M')}*")
|
||||||
print_line()
|
lines.append("")
|
||||||
|
|
||||||
print_line("🔍 Geocoding...")
|
lines.append("## 🔍 Geocoding")
|
||||||
locatie = geocode_adres(adres_input)
|
locatie = geocode_adres(adres_input)
|
||||||
print_line(f"**Adres:** {locatie['adres']}")
|
lines.append(f"**Adres:** {locatie['adres']}")
|
||||||
print_line(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}")
|
lines.append(f"**Coördinaten:** {locatie['lat']:.6f}, {locatie['lon']:.6f}")
|
||||||
print_line()
|
lines.append("")
|
||||||
|
|
||||||
auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20)
|
auto_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'driving-car', 20)
|
||||||
fiets_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'cycling-regular', 30)
|
fiets_iso = haal_isochroon(locatie['lon'], locatie['lat'], 'cycling-regular', 30)
|
||||||
print_isochroon_sectie("Bereikbaarheid (Auto 20 min)", "🚗", auto_iso, rapport_lines)
|
|
||||||
print_isochroon_sectie("Bereikbaarheid (Fiets 30 min)", "🚲", fiets_iso, rapport_lines)
|
|
||||||
|
|
||||||
print_line("🏙️ **Voorzieningen dichtbij**")
|
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)
|
voorzieningen = analyse_voorzieningen(locatie['lon'], locatie['lat'], limit=5)
|
||||||
print_line(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten']))
|
lines.append(format_voorzieningen_markdown("🛒 **Supermarkten**", voorzieningen['supermarkten']))
|
||||||
print_line()
|
lines.append("")
|
||||||
print_line(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen']))
|
lines.append(format_voorzieningen_markdown("🏫 **Scholen**", voorzieningen['scholen']))
|
||||||
print_line()
|
lines.append("")
|
||||||
|
|
||||||
# Genereer interactieve HTML kaart en upload naar Obsidian
|
# Genereer interactieve HTML kaart en sla op in MkDocs
|
||||||
html_content = genereer_isochroon_html(
|
html_content = genereer_isochroon_html(
|
||||||
locatie['adres'], locatie['lat'], locatie['lon'], auto_iso, fiets_iso
|
locatie['adres'], locatie['lat'], locatie['lon'], auto_iso, fiets_iso
|
||||||
)
|
)
|
||||||
html_path = schrijf_naar_obsidian(html_content, locatie['adres'], subpath="huizenjacht", ext=".html")
|
html_filepath, html_filename = schrijf_naar_mkdocs(html_content, locatie['adres'], nummer, 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
|
# Voeg link toe aan markdown rapport (betrouwbaar via directe link)
|
||||||
b64_img, err = maak_isochroon_screenshot(html_content)
|
lines.append("## 🗺️ Interactieve kaart")
|
||||||
if b64_img:
|
lines.append(f"")
|
||||||
print_line("")
|
lines.append(f"**[📍 Bekijk interactieve isochroon kaart]({html_filename})**{{:target=\"_blank\"}}")
|
||||||
print_line("**🗺️ Isochroon kaart (auto + fiets):**")
|
lines.append("")
|
||||||
img_md = f""
|
lines.append("*Klik op de link om de kaart in een nieuw tabblad te openen. Hier kun je zoomen, pannen en de isochronen bekijken.*")
|
||||||
print_line(img_md)
|
lines.append("")
|
||||||
else:
|
|
||||||
print_line(f"⚠️ Kon geen screenshot maken: {err}")
|
|
||||||
|
|
||||||
# Sla ook ruwe geojson op (lokaal, niet in Obsidian)
|
# Ruwe geojson
|
||||||
output_file = f"/tmp/isochronen_{datetime.now().strftime('%Y%m%d_%H%M%S')}.geojson"
|
output_file = f"/tmp/isochronen_{datetime.now().strftime('%Y%m%d_%H%M%S')}.geojson"
|
||||||
with open(output_file, 'w') as f:
|
with open(output_file, 'w') as f:
|
||||||
json.dump({'auto_20_min': auto_iso, 'fiets_30_min': fiets_iso}, f)
|
json.dump({'auto_20_min': auto_iso, 'fiets_30_min': fiets_iso}, f)
|
||||||
print_line(f"💾 Ruwe isochroondata: `{output_file}`")
|
lines.append("---")
|
||||||
print_line()
|
lines.append("*Onderdelen: geocoding, isochronen, voorzieningen, interactieve kaart*")
|
||||||
print_line("---")
|
|
||||||
print_line("_Onderdelen: geocoding, isochronen, voorzieningen_")
|
|
||||||
|
|
||||||
# Schrijf rapport naar Obsidian
|
rapport_text = "\n".join(lines)
|
||||||
print_line()
|
|
||||||
md_path = schrijf_naar_obsidian('\n'.join(rapport_lines), locatie['adres'])
|
# Print naar stdout
|
||||||
if md_path:
|
print(rapport_text)
|
||||||
print_line(f"📝 **Opgeslagen in Obsidian:** `{md_path}`")
|
|
||||||
else:
|
# Sla op in MkDocs
|
||||||
print_line("⚠️ Obsidian upload mislukt")
|
md_filepath, md_filename = schrijf_naar_mkdocs(rapport_text, adres_input, nummer, ext=".md")
|
||||||
|
print(f"\n📝 Rapport opgeslagen: {md_filepath}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best"
|
adres = sys.argv[1] if len(sys.argv) > 1 else "Griendvelden 14 Best"
|
||||||
|
|||||||
+7
-7
@@ -136,13 +136,13 @@ def format_voorzieningen_markdown(titel, pois):
|
|||||||
if not pois:
|
if not pois:
|
||||||
lines.append("Geen voorzieningen gevonden in zoekradius.")
|
lines.append("Geen voorzieningen gevonden in zoekradius.")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
for i, p in enumerate(pois, 1):
|
for p in pois:
|
||||||
lines.extend([
|
lines.append(
|
||||||
f"{i}. **{p.naam}**",
|
f"• **{p.naam}** — "
|
||||||
f" Hemelsbreed: {format_afstand(p.hemelsbreed_m)}",
|
f"Hemelsbreed: {format_afstand(p.hemelsbreed_m)} | "
|
||||||
f" Fiets: {format_afstand(p.fiets_afstand_m)} / {format_duur(p.fiets_duur_s)}",
|
f"Fiets: {format_afstand(p.fiets_afstand_m)} / {format_duur(p.fiets_duur_s)} | "
|
||||||
f" Auto: {format_afstand(p.auto_afstand_m)} / {format_duur(p.auto_duur_s)}",
|
f"Auto: {format_afstand(p.auto_afstand_m)} / {format_duur(p.auto_duur_s)}"
|
||||||
])
|
)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user