""" fetch_riyadh_qol_osm.py ======================== Fetches new QoL POI data for Riyadh from OSM Overpass API. New categories (not in existing model): - gyms / sports centres - pharmacies - universities / colleges - cinemas (Vision 2030 expansion) - coffee shops - libraries - police stations - clinics (not full hospitals) - kindergartens / nurseries - petrol stations (already partially covered, extend) - car dealerships (luxury index) Output: data/raw/riyadh_qol_.csv per category data/raw/riyadh_qol_combined.csv all combined Run: python scripts/fetch_riyadh_qol_osm.py """ import csv import json import time from pathlib import Path import requests BASE = Path(__file__).resolve().parent.parent OUT_DIR = BASE / "data" / "raw" # Riyadh city bounding box (generous) BBOX = "24.4,46.4,25.1,47.0" # south,west,north,east OVERPASS_URL = "https://overpass-api.de/api/interpreter" # OSM queries — each (output_name, overpass_body) QUERIES = { "gyms": '[out:json][timeout:60];(node["leisure"="fitness_centre"]({bbox});way["leisure"="fitness_centre"]({bbox}););out center;', "pharmacies": '[out:json][timeout:60];(node["amenity"="pharmacy"]({bbox});way["amenity"="pharmacy"]({bbox}););out center;', "universities": '[out:json][timeout:60];(node["amenity"="university"]({bbox});way["amenity"="university"]({bbox});relation["amenity"="university"]({bbox});node["amenity"="college"]({bbox});way["amenity"="college"]({bbox}););out center;', "cinemas": '[out:json][timeout:60];(node["amenity"="cinema"]({bbox});way["amenity"="cinema"]({bbox}););out center;', "coffee_shops": '[out:json][timeout:60];(node["amenity"="cafe"]({bbox});way["amenity"="cafe"]({bbox}););out center;', "libraries": '[out:json][timeout:60];(node["amenity"="library"]({bbox});way["amenity"="library"]({bbox}););out center;', "police": '[out:json][timeout:60];(node["amenity"="police"]({bbox});way["amenity"="police"]({bbox}););out center;', "clinics": '[out:json][timeout:60];(node["amenity"="clinic"]({bbox});node["amenity"="doctors"]({bbox});way["amenity"="clinic"]({bbox}););out center;', "kindergartens": '[out:json][timeout:60];(node["amenity"="kindergarten"]({bbox});way["amenity"="kindergarten"]({bbox}););out center;', "sports_centres": '[out:json][timeout:60];(node["leisure"="sports_centre"]({bbox});way["leisure"="sports_centre"]({bbox}););out center;', "swimming_pools": '[out:json][timeout:60];(node["leisure"="swimming_pool"]["access"!="private"]({bbox});way["leisure"="swimming_pool"]["access"!="private"]({bbox}););out center;', "stadiums": '[out:json][timeout:60];(node["leisure"="stadium"]({bbox});way["leisure"="stadium"]({bbox}););out center;', "supermarkets": '[out:json][timeout:60];(node["shop"="supermarket"]({bbox});way["shop"="supermarket"]({bbox}););out center;', "atms": '[out:json][timeout:60];(node["amenity"="atm"]({bbox}););out center;', "car_showrooms": '[out:json][timeout:60];(node["shop"="car"]({bbox});way["shop"="car"]({bbox}););out center;', "post_offices": '[out:json][timeout:60];(node["amenity"="post_office"]({bbox});way["amenity"="post_office"]({bbox}););out center;', } HEADERS = {"User-Agent": "THAMAN-AVM-Research/2.0 (educational project)"} def extract_coords(element: dict) -> tuple[float, float] | None: if element["type"] == "node": return element.get("lat"), element.get("lon") center = element.get("center") if center: return center.get("lat"), center.get("lon") return None, None def fetch_category(name: str, query_template: str) -> list[dict]: query = query_template.replace("{bbox}", BBOX) try: r = requests.post(OVERPASS_URL, data={"data": query}, headers=HEADERS, timeout=90) r.raise_for_status() data = r.json() elements = data.get("elements", []) rows = [] for el in elements: lat, lon = extract_coords(el) if lat is None: continue tags = el.get("tags", {}) rows.append({ "category": name, "lat": lat, "lon": lon, "name_ar": tags.get("name:ar", ""), "name_en": tags.get("name:en", tags.get("name", "")), "osm_id": el.get("id", ""), "osm_type": el["type"], }) return rows except Exception as e: print(f" ERROR {name}: {e}") return [] def main(): all_rows = [] fields = ["category", "lat", "lon", "name_ar", "name_en", "osm_id", "osm_type"] for name, query in QUERIES.items(): print(f"Fetching {name} ...", end=" ", flush=True) rows = fetch_category(name, query) print(f"{len(rows)} POIs") # Save individual file out_path = OUT_DIR / f"riyadh_qol_{name}.csv" with open(out_path, "w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=fields) w.writeheader() w.writerows(rows) all_rows.extend(rows) time.sleep(2) # respect Overpass rate limit # Save combined combined_path = OUT_DIR / "riyadh_qol_combined.csv" with open(combined_path, "w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=fields) w.writeheader() w.writerows(all_rows) print(f"\nTotal: {len(all_rows)} POIs across {len(QUERIES)} categories") print(f"Saved → {combined_path.name}") # Summary from collections import Counter cnt = Counter(r["category"] for r in all_rows) print("\nBreakdown:") for cat, n in cnt.most_common(): print(f" {cat:20s}: {n}") if __name__ == "__main__": main()