#!/usr/bin/env python3 """GeoJSON proxy service for OpenRemote assets map display.""" import json import os import urllib.request import urllib.error 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", "") OR_TOKEN = os.environ.get("OR_TOKEN", "") def get_token(): """Fetch an OpenRemote access token using admin credentials.""" if OR_TOKEN: return OR_TOKEN data = json.dumps({ "username": OR_ADMIN_USER, "password": OR_ADMIN_PASS, "grant_type": "password", "client_id": "openremote" }).encode() req = urllib.request.Request( f"{OR_URL}/auth/realms/master/protocol/openid-connect/token", data=data, headers={"Content-Type": "application/json"}, method="POST" ) resp = urllib.request.urlopen(req, timeout=10) body = json.loads(resp.read()) return body["access_token"] def fetch_assets(token): """Fetch IoT sensor assets from OpenRemote.""" url = f"{OR_URL}/api/master/assets?type=IOTSensor" req = urllib.request.Request( url, headers={ "Authorization": f"Bearer {token}", "Accept": "application/json" }, method="GET" ) resp = urllib.request.urlopen(req, timeout=15) return json.loads(resp.read()) def to_geojson(assets): """Convert OpenRemote assets to a GeoJSON FeatureCollection.""" features = [] for asset in assets: attrs = asset.get("attributes", {}) location = attrs.get("location", {}) if not location: continue value = location.get("value") if not value: continue coords = value.get("coordinates") if not coords or len(coords) < 2: continue # Build properties from asset attributes properties = {} for attr_name, attr_val in attrs.items(): v = attr_val.get("value") if v is not None: properties[attr_name] = v # Ensure key fields are at top level for Mapbox filters properties.setdefault("id", asset.get("id")) properties.setdefault("name", asset.get("name", "")) properties.setdefault("type", asset.get("type", "")) properties.setdefault("sensorType", attrs.get("sensorType", {}).get("value", "")) features.append({ "type": "Feature", "geometry": { "type": "Point", "coordinates": [coords[0], coords[1]] }, "properties": properties }) return { "type": "FeatureCollection", "features": features } class GeoJSONHandler(BaseHTTPRequestHandler): def do_GET(self): path = self.path.split("?")[0] if path == "/geojson": try: token = get_token() assets = fetch_assets(token) geojson = to_geojson(assets) body = json.dumps(geojson).encode() self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") self.send_header("Access-Control-Allow-Headers", "*") 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()