Files
smart-city-digital-twin-mar…/geojson-proxy/geojson_proxy.py
Eric FELIXINE 2377bc07fd Session 2026-05-19: OpenRemote map display investigation, cleanup, fresh install
- Investigated map display issues (agentLink, GeoJSON coords, realm config)
- Cleaned up all dashboards and containers
- Fresh Manager installation (PostgreSQL in recovery)
- Updated TODO.md with current status
- GeoJSON proxy: fixed coordinate order (lon/lat)
- Session resume saved
2026-05-19 16:22:26 -04:00

168 lines
6.2 KiB
Python

#!/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()