#!/usr/bin/env python3 """GeoJSON proxy service for OpenRemote assets map display. Fetches all assets with location from OpenRemote REST API and serves them as GeoJSON. """ import json import os import urllib.request import urllib.error import urllib.parse from http.server import HTTPServer, BaseHTTPRequestHandler OR_URL = os.environ.get("OR_URL", "http://openremote_manager_1:8080") OR_ADMIN_USER = os.environ.get("OR_ADMIN_USER", "admin") OR_ADMIN_PASS = os.environ.get("OR_ADMIN_PASS", "Digitribe972") OR_REALM = os.environ.get("OR_REALM", "master") OR_CLIENT_SECRET = os.environ.get("OR_CLIENT_SECRET", "0oQjzTfiEELYmj5jFwT4iIuWUDtQDvVa") _token_cache = {"token": "", "expires": 0} def get_token(): """Fetch an OpenRemote access token using admin credentials.""" import time if _token_cache["token"] and _token_cache["expires"] > time.time() + 30: return _token_cache["token"] data = urllib.parse.urlencode({ "username": OR_ADMIN_USER, "password": OR_ADMIN_PASS, "grant_type": "password", "client_id": "openremote", "client_secret": OR_CLIENT_SECRET }).encode() req = urllib.request.Request( f"http://openremote-keycloak-1:8080/auth/realms/{OR_REALM}/protocol/openid-connect/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST" ) resp = urllib.request.urlopen(req, timeout=10) body = json.loads(resp.read()) _token_cache["token"] = body["access_token"] _token_cache["expires"] = time.time() + max(body.get("expires_in", 300) - 60, 30) return _token_cache["token"] def fetch_assets(): """Fetch all assets with location from OpenRemote REST API.""" token = get_token() features = [] # Query all assets with location attribute try: # Use the asset query API to get all assets with location query = json.dumps({ "attributes": { "location": { "value": {"$exists": True} } } }).encode() req = urllib.request.Request( f"{OR_URL}/api/{OR_REALM}/asset/query", data=query, headers={ "Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json" }, method="POST" ) with urllib.request.urlopen(req, timeout=30) as r: assets = json.loads(r.read().decode()) if not isinstance(assets, list): assets = [assets] except Exception as e: # Fallback: try to get all assets and filter try: req = urllib.request.Request( f"{OR_URL}/api/{OR_REALM}/asset?limit=100", headers={"Authorization": f"Bearer {token}", "Accept": "application/json"} ) with urllib.request.urlopen(req, timeout=30) as r: assets = json.loads(r.read().decode()) if not isinstance(assets, list): assets = [assets] except Exception as e2: return {"type": "FeatureCollection", "features": [], "error": str(e2)} for asset in assets: try: attrs = asset.get("attributes", {}) location = attrs.get("location", {}) value = location.get("value") if isinstance(location, dict) else None coords = value.get("coordinates") if isinstance(value, dict) else None if not coords or len(coords) < 2: continue props = { "id": asset.get("id"), "name": asset.get("name", ""), "type": asset.get("type", ""), "realm": asset.get("realm", ""), } # Add sensorType for color mapping sensor_type = attrs.get("sensorType", {}) if isinstance(sensor_type, dict): props["sensorType"] = sensor_type.get("value", "") # Add scalar attribute values for attr_name, attr_val in attrs.items(): if isinstance(attr_val, dict): v = attr_val.get("value") if v is not None and not isinstance(v, (dict, list)): props[attr_name] = v # GeoJSON coordinates are [longitude, latitude] features.append({ "type": "Feature", "geometry": {"type": "Point", "coordinates": [coords[1], coords[0]]}, "properties": props }) except Exception: continue return {"type": "FeatureCollection", "features": features} class GeoJSONHandler(BaseHTTPRequestHandler): def do_GET(self): path = self.path.split("?")[0] if path == "/geojson": try: result = fetch_assets() body = json.dumps(result).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) except Exception as e: error_body = json.dumps({"error": str(e)}).encode() self.send_response(500) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Content-Length", str(len(error_body))) self.end_headers() self.wfile.write(error_body) elif path == "/health": body = json.dumps({"status": "ok"}).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) else: self.send_response(404) self.end_headers() def log_message(self, format, *args): print(f"[geojson-proxy] {args[0]}") if __name__ == "__main__": server = HTTPServer(("0.0.0.0", 8080), GeoJSONHandler) print("[geojson-proxy] Listening on 0.0.0.0:8080") server.serve_forever()