""" Refresh the shipped OSRM road-distance matrix for Jatim kabupaten. Usage: python tools/fetch_osrm_distance.py Output: sample_data/road_distance_jatim.csv Calls the public OSRM demo endpoint (router.project-osrm.org) once with a single /table batch — N coordinates produce an N×N matrix. The endpoint is rate-limited and labeled "for evaluation only"; a single batch of <100 coordinates is well within tolerance. For higher cadence, self-host OSRM via Docker with the Indonesia OSM extract. The matrix is precomputed and committed; engine runtime does NOT call this endpoint. Re-run only when: - sample_data/kabupaten_jatim.csv adds/removes/relocates a kabupaten - You want to refresh against newer OSM road graph (post-toll opening, etc.) Source: OpenStreetMap (ODbL license) via Project OSRM. """ from __future__ import annotations import csv import json import sys import time import urllib.parse import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) KAB_CSV = ROOT / "sample_data" / "kabupaten_jatim.csv" OUT_CSV = ROOT / "sample_data" / "road_distance_jatim.csv" def load_kabupaten(): rows = [] with open(KAB_CSV, newline="", encoding="utf-8") as f: r = csv.DictReader(f) for row in r: rows.append({ "id": row["kab_id"], "nama": row["nama"], "lat": float(row["latitude"]), "lon": float(row["longitude"]), }) return rows def fetch_table(kabs, profile: str = "driving"): # OSRM expects lon,lat (NOT lat,lon) coord_str = ";".join(f"{k['lon']},{k['lat']}" for k in kabs) url = ( f"http://router.project-osrm.org/table/v1/{profile}/" f"{urllib.parse.quote(coord_str, safe=',;')}" f"?annotations=distance,duration" ) print(f"[osrm] requesting {len(kabs)} x {len(kabs)} table ({len(url)} char URL)") t = time.time() with urllib.request.urlopen(url, timeout=120) as resp: body = resp.read().decode("utf-8") elapsed = time.time() - t print(f"[osrm] response {len(body)} bytes in {elapsed:.1f}s") data = json.loads(body) if data.get("code") != "Ok": print("[osrm] non-Ok response:", data) sys.exit(1) return data def main(): kabs = load_kabupaten() print(f"[osrm] loaded {len(kabs)} kabupaten") data = fetch_table(kabs) distances = data["distances"] # meters durations = data["durations"] # seconds n = len(kabs) assert len(distances) == n and len(durations) == n n_missing = 0 with open(OUT_CSV, "w", newline="", encoding="utf-8") as f: w = csv.writer(f) w.writerow(["from_id", "from_nama", "to_id", "to_nama", "road_km", "duration_hr"]) for i in range(n): for j in range(n): dist_m = distances[i][j] dur_s = durations[i][j] if dist_m is None or dur_s is None: n_missing += 1 continue w.writerow([ kabs[i]["id"], kabs[i]["nama"], kabs[j]["id"], kabs[j]["nama"], f"{dist_m / 1000:.3f}", f"{dur_s / 3600:.4f}", ]) print(f"[osrm] wrote {n*n - n_missing} pairs -> {OUT_CSV.relative_to(ROOT)}") if n_missing: print(f"[osrm] WARNING: {n_missing} pairs returned None (no route found)") if __name__ == "__main__": main()