""" URA GLS API — Reverse-Engineered from JavaScript source ======================================================== Endpoints and auth flow discovered by auto-reverse from: https://eservice.ura.gov.sg/maps/?service=GLSRELEASE&site=1 Auth flow (from OnemapAPI.js + Env.js): 1. GET eservice.ura.gov.sg/sharedServicesWeb/onemapService/getOnemapToken (JSONP) → returns {token: {token: "...", expiry: ...}} 2. GET eservice.ura.gov.sg/sharedServicesWeb/onemapService/getOnemapLandLotToken (JSONP) → returns {token: {token: "...", expiry: ...}} 3. OneMap APIs use: Authorization: header ArcGIS APIs (maps.ura.gov.sg) — NO auth needed, just geo-blocked to SG IPs. Run from a Singapore IP. """ import json import requests # --------------------------------------------------------------------------- # Config (from Env.js) # --------------------------------------------------------------------------- ESERVICE = "https://eservice.ura.gov.sg" MAP_HOST = "https://maps.ura.gov.sg" ONEMAP = "https://www.onemap.gov.sg" session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": f"{ESERVICE}/maps/", "Origin": ESERVICE, }) def dump(label, resp): print(f"\n{'='*70}") print(f" {label}") print(f" {resp.url}") print(f" Status: {resp.status_code}") print(f"{'='*70}") try: data = resp.json() print(json.dumps(data, indent=2, ensure_ascii=False)[:4000]) return data except Exception: print(resp.text[:2000]) return None # --------------------------------------------------------------------------- # Step 1: Get OneMap tokens (JSONP endpoints from OnemapAPI.js) # --------------------------------------------------------------------------- print("=" * 70) print(" AUTH: Fetching OneMap tokens") print("=" * 70) # These are JSONP endpoints — they wrap the response in a callback function # We need to strip the JSONP wrapper to get the JSON def fetch_jsonp_token(url): """Fetch a JSONP token endpoint and extract the JSON payload.""" resp = session.get(url, timeout=15) text = resp.text.strip() # JSONP format: callbackName({"key":"value"}) # Strip the callback wrapper if text.startswith("("): text = text[1:-1] elif "(" in text: start = text.index("(") + 1 end = text.rindex(")") text = text[start:end] return json.loads(text) try: token_data = fetch_jsonp_token(f"{ESERVICE}/sharedServicesWeb/onemapService/getOnemapToken") onemap_token = token_data.get("token", {}).get("token", "") print(f" OneMap Token: {onemap_token[:50]}...") print(f" Expiry: {token_data.get('token', {}).get('expiry', 'N/A')}") except Exception as e: print(f" Failed to get OneMap token: {e}") onemap_token = "" try: landlot_data = fetch_jsonp_token(f"{ESERVICE}/sharedServicesWeb/onemapService/getOnemapLandLotToken") landlot_token = landlot_data.get("token", {}).get("token", "") print(f" LandLot Token: {landlot_token[:50]}...") except Exception as e: print(f" Failed to get LandLot token: {e}") landlot_token = "" # Set auth header for OneMap requests if onemap_token: session.headers["Authorization"] = onemap_token # --------------------------------------------------------------------------- # Step 2: ArcGIS — URA GLS Sale Sites (the main target) # --------------------------------------------------------------------------- print("\n" + "=" * 70) print(" ARCGIS ENDPOINTS (maps.ura.gov.sg)") print("=" * 70) # Layer metadata — field names, types, etc. dump( "URA Sale Sites — Layer Metadata", session.get(f"{MAP_HOST}/ArcGis/rest/services/lsag/ura_sale_sites/MapServer/0", params={"f": "json"}, timeout=15), ) # Query — get first 5 features with all fields dump( "URA Sale Sites — Sample Data", session.get( f"{MAP_HOST}/ArcGis/rest/services/lsag/ura_sale_sites/MapServer/0/query", params={"where": "1=1", "outFields": "*", "f": "json", "resultRecordCount": "5"}, timeout=15, ), ) # --------------------------------------------------------------------------- # Step 3: ArcGIS — HDB Sale Sites # --------------------------------------------------------------------------- dump( "HDB Sale Sites — Sample Data", session.get( f"{MAP_HOST}/ArcGis/rest/services/lsag/hdb_sale_sites2/MapServer/0/query", params={"where": "1=1", "outFields": "*", "f": "json", "resultRecordCount": "3"}, timeout=15, ), ) # --------------------------------------------------------------------------- # Step 4: ArcGIS — JTC Sale Sites # --------------------------------------------------------------------------- dump( "JTC Sale Sites — Sample Data", session.get( f"{MAP_HOST}/arcgis/rest/services/lsag/jtc_sale_sites/MapServer/0/query", params={"where": "1=1", "outFields": "*", "f": "json", "resultRecordCount": "3"}, timeout=15, ), ) # --------------------------------------------------------------------------- # Step 5: OneMap APIs (with auth token) # --------------------------------------------------------------------------- print("\n" + "=" * 70) print(" ONEMAP APIs (with Authorization header)") print("=" * 70) # Reverse Geocode (Orchard Road) dump( "OneMap Reverse Geocode", session.get( f"{ONEMAP}/api/public/revgeocode", params={"location": "1.3004,103.8460", "addressType": "all", "otherFeatures": "Y"}, timeout=15, ), ) # Land Ownership query if landlot_token: session.headers["Authorization"] = landlot_token dump( "OneMap Land Ownership", session.get( f"{ONEMAP}/api/public/landlotAPI/retrieveLandOwnership", params={"latitude": "1.3004", "longtitude": "103.8460"}, timeout=15, ), ) # Address search if onemap_token: session.headers["Authorization"] = onemap_token dump( "OneMap Address Search", session.get( f"{ONEMAP}/api/common/elastic/search", params={"searchVal": "Orchard Road", "returnGeom": "Y", "getAddrDetails": "Y", "pageNum": "1"}, timeout=15, ), ) # --------------------------------------------------------------------------- # Step 6: URA eService APIs (parking, session-based) # --------------------------------------------------------------------------- print("\n" + "=" * 70) print(" URA eSERVICE APIs") print("=" * 70) dump( "Parking Place List", session.get(f"{ESERVICE}/ecasService/ppInfo/getParkingPlaceList.do", timeout=15), ) dump( "Parking Rate Availability", session.get(f"{ESERVICE}/ecasPPService/ppInfo/getPPCurrentRateAvailability.do", timeout=15), )