- Basemap: src/static/basemap/ping-basin.pmtiles, a Protomaps extract of the Ping basin (zoom 0-12, 15 MB, OpenStreetMap data), drawn by protomaps-leaflet 5.1.0 (unpkg, SRI) in its light/dark style with labels in the page language. It replaces the dimmed OSM raster tiles, whose baked-in labels were the noisiest part of the screen; OSM stays as the fallback. The browser reads the file with range requests (ServeDir answers 206; dev_proxy.py now does too). The map stays within the basin. - Basin stations: a second list next to the RID gauges and a map layer of diamonds, from /api/hii/waterlevel/latest (falling back to /sensors/thaiwater), coloured by ThaiWater's bank-height classes (over the bank = danger, 70-100 % = watch). Gauges RID already lists appear once. The panel shows level (m MSL), % of bank height, distance to and height of the bank top, gauge reading and agency; selection in the URL (#hii-<id>). - custom-markers.json as pins from zoom 12, with the flood zone they are in. Refresh instructions for the basemap in src/static/basemap/README.md.
90 lines
3.6 KiB
Python
90 lines
3.6 KiB
Python
"""Serve the working-copy dashboard locally with API calls proxied to the
|
|
live server, so browser-side changes can be checked against real data
|
|
before deploy. Usage: python scripts/dev_proxy.py [port]
|
|
|
|
/ is src/static/dashboard.html, /static/... any file in src/static (e.g. the
|
|
console preview at /static/console.html), everything else the live server."""
|
|
|
|
import http.server
|
|
import mimetypes
|
|
import os
|
|
import re
|
|
import sys
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
UPSTREAM = "https://water.buildfor.life"
|
|
STATIC = Path(__file__).resolve().parents[1] / "src" / "static"
|
|
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
if self.path == "/" or self.path.startswith("/?"):
|
|
body = (STATIC / "dashboard.html").read_bytes()
|
|
self._send(200, "text/html; charset=utf-8", body)
|
|
return
|
|
# Local overrides for endpoints not yet deployed: DEV_PROXY_LOCAL=/api/x=file.json,...
|
|
for pair in filter(None, os.environ.get("DEV_PROXY_LOCAL", "").split(",")):
|
|
prefix, file = pair.split("=", 1)
|
|
if self.path.split("?")[0] == prefix:
|
|
self._send(200, "application/json", Path(file).read_bytes())
|
|
return
|
|
if self.path.startswith("/static/"):
|
|
f = STATIC / self.path[len("/static/") :].split("?")[0]
|
|
if f.is_file():
|
|
ctype = (
|
|
"application/json"
|
|
if f.suffix in (".json", ".geojson")
|
|
else mimetypes.guess_type(f.name)[0] or "application/octet-stream"
|
|
)
|
|
if ctype.startswith("text/"):
|
|
ctype += "; charset=utf-8"
|
|
body = f.read_bytes()
|
|
# Byte ranges, like the server's ServeDir: PMTiles reads the
|
|
# basemap in pieces
|
|
m = re.fullmatch(r"bytes=(\d+)-(\d*)", self.headers.get("Range", ""))
|
|
if m:
|
|
start = int(m.group(1))
|
|
end = min(int(m.group(2) or len(body) - 1), len(body) - 1)
|
|
extra = {"Content-Range": f"bytes {start}-{end}/{len(body)}"}
|
|
self._send(206, ctype, body[start : end + 1], extra)
|
|
return
|
|
self._send(200, ctype, body)
|
|
return
|
|
try:
|
|
req = urllib.request.Request(
|
|
UPSTREAM + self.path,
|
|
headers={
|
|
"User-Agent": "Mozilla/5.0 (dev_proxy; +https://buildfor.life)",
|
|
"Accept": "application/json",
|
|
},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=60) as r:
|
|
self._send(
|
|
r.status,
|
|
r.headers.get("Content-Type", "application/json"),
|
|
r.read(),
|
|
)
|
|
except urllib.error.HTTPError as e:
|
|
self._send(e.code, "application/json", e.read())
|
|
|
|
def _send(self, code, ctype, body, extra=None):
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", ctype)
|
|
self.send_header("Accept-Ranges", "bytes")
|
|
for name, value in (extra or {}).items():
|
|
self.send_header(name, value)
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.send_header("Cache-Control", "no-store")
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, *a):
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765
|
|
print(f"http://localhost:{port}/ (API -> {UPSTREAM})")
|
|
http.server.ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
|