import json import os import random import sys import time import urllib.error import urllib.parse import urllib.request DISTRICTS = [ "Kolhapur", "Sangli", "Satara", "Pune", "Ahmednagar", "Nashik", "Aurangabad", "Thane", "Raigad", "Ratnagiri", ] # Approximate bounding boxes for Maharashtra districts (lon_min, lat_min, lon_max, lat_max) # Used as fallback when Nominatim is rate-limited DISTRICT_BBOXES = { "Kolhapur": (73.6, 16.0, 74.8, 17.2), "Sangli": (73.8, 16.4, 75.0, 17.2), "Satara": (73.3, 17.0, 74.5, 18.0), "Pune": (73.2, 18.0, 74.5, 19.0), "Ahmednagar": (74.0, 18.5, 75.5, 19.5), "Nashik": (73.0, 19.5, 74.5, 20.5), "Aurangabad": (74.5, 19.0, 76.0, 20.0), "Thane": (72.8, 19.0, 73.6, 20.0), "Raigad": (72.8, 18.0, 73.5, 18.8), "Ratnagiri": (72.8, 16.5, 73.8, 17.5), } def _request_json(url: str, timeout: int = 10) -> object: req = urllib.request.Request( url, headers={ "Accept": "application/json", "User-Agent": "wrd-insight-dashboard/1.0 (cache script; contact: local-demo)", }, ) with urllib.request.urlopen(req, timeout=timeout) as resp: body = resp.read().decode("utf-8") return json.loads(body) def fetch_district_geojson_from_nominatim(district: str) -> dict | None: """Try to fetch district boundary from Nominatim. Returns None on any error.""" params = { "county": district, "state": "Maharashtra", "country": "India", "polygon_geojson": 1, "format": "json", } url = "https://nominatim.openstreetmap.org/search?" + urllib.parse.urlencode(params) try: data = _request_json(url, timeout=10) except Exception: return None if isinstance(data, list) and data and isinstance(data[0], dict) and data[0].get("geojson"): geo = data[0]["geojson"] return { "type": "Feature", "properties": {"name": district, "source": "nominatim"}, "geometry": geo, } return None def create_bbox_polygon(district: str) -> dict: """Create a simple rectangular polygon from bounding box as fallback.""" bbox = DISTRICT_BBOXES.get(district) if not bbox: # Default to a placeholder in central Maharashtra bbox = (73.5, 18.0, 75.0, 19.5) lon_min, lat_min, lon_max, lat_max = bbox # Create a simple rectangular polygon (5 points to close the ring) coords = [ [lon_min, lat_min], [lon_max, lat_min], [lon_max, lat_max], [lon_min, lat_max], [lon_min, lat_min], # Close the ring ] return { "type": "Feature", "properties": {"name": district, "source": "bbox_fallback"}, "geometry": {"type": "Polygon", "coordinates": [coords]}, } def main() -> int: repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) out_path = os.path.join(repo_root, "public", "data", "maharashtra-districts.geojson") os.makedirs(os.path.dirname(out_path), exist_ok=True) features: list[dict] = [] nominatim_success = 0 fallback_used = 0 print("Fetching Maharashtra district boundaries...") print("Strategy: Try Nominatim first (quick timeout), use bbox fallback if rate-limited.\n") for district in DISTRICTS: # Try Nominatim with just 1 attempt (quick) feature = fetch_district_geojson_from_nominatim(district) if feature: features.append(feature) nominatim_success += 1 print(f" āœ“ {district}: fetched from Nominatim") else: # Use bounding box fallback immediately feature = create_bbox_polygon(district) features.append(feature) fallback_used += 1 print(f" ā—‹ {district}: using bbox fallback (Nominatim unavailable)") # Minimal delay to be polite (Nominatim asks for 1 req/sec) time.sleep(1.0) # Write the FeatureCollection fc = {"type": "FeatureCollection", "features": features} with open(out_path, "w", encoding="utf-8") as f: json.dump(fc, f, ensure_ascii=False, indent=2) print(f"\nāœ“ Wrote {len(features)} district features to: {out_path}") print(f" - From Nominatim: {nominatim_success}") print(f" - Bbox fallback: {fallback_used}") if fallback_used > 0: print( "\nNote: Some districts use approximate bounding boxes. " "For precise boundaries, re-run when Nominatim is available " "or download a proper Maharashtra district GeoJSON from a trusted source." ) return 0 if __name__ == "__main__": raise SystemExit(main())