From c557572d24feec4aca8bb8ad2336752864334749 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Fri, 18 Sep 2026 23:29:37 +0800 Subject: [PATCH] feat: add Kyushu S-52 PBF comparison web --- web/kyushu_compare.html | 227 +++++++++++++++++++++++++++++++++++ web/kyushu_compare_server.py | 111 +++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 web/kyushu_compare.html create mode 100644 web/kyushu_compare_server.py diff --git a/web/kyushu_compare.html b/web/kyushu_compare.html new file mode 100644 index 0000000..3f9738e --- /dev/null +++ b/web/kyushu_compare.html @@ -0,0 +1,227 @@ + + + + + + 九州 S-52 PBF 左右对比 + + + + +
+ 九州 S-52 PBF 对比 + 左:Production v2 本地 PBF 右:extentfix 原始数据源 + + + -- +
+
+
+
+
左:九州 Production v2等待参考 style…
+
点击任一侧海图,对比同一位置的 Feature。
+
LOCAL
+
+
+
+
右:NavSea extentfix加载远端 style…
+
点击任一侧海图,对比同一位置的 Feature。
+
REFERENCE
+
+
+ + + + + diff --git a/web/kyushu_compare_server.py b/web/kyushu_compare_server.py new file mode 100644 index 0000000..978c32f --- /dev/null +++ b/web/kyushu_compare_server.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from urllib.parse import urlparse + +RELEASE_ID = "cm95-d1-62a85bd40d3a-bbaaeafee1b4" + + +def parse_args(): + parser = argparse.ArgumentParser(description="Kyushu S-52 PBF side-by-side comparison server") + parser.add_argument("--host", default="0.0.0.0") + parser.add_argument("--port", type=int, default=18888) + parser.add_argument("--release-id", default=RELEASE_ID) + return parser.parse_args() + + +def main(): + args = parse_args() + web_root = Path(__file__).resolve().parent + project_root = web_root.parent + release_dir = project_root / "build" / "releases-s52-v2" / args.release_id + pbf_root = release_dir / "pbf" / "delivery" + manifest_path = release_dir / "release_manifest_v1.json" + html_path = web_root / "kyushu_compare.html" + assets_root = project_root / "build" / "fukuoka_s52_web" / "site" / "assets" + + required = [pbf_root, manifest_path, html_path, assets_root / "maplibre-gl.js", assets_root / "maplibre-gl.css"] + missing = [str(path) for path in required if not path.exists()] + if missing: + raise SystemExit("Missing required path(s):\n" + "\n".join(missing)) + + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + class Handler(BaseHTTPRequestHandler): + def send_bytes(self, data: bytes, content_type: str, status: int = 200): + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(data))) + self.send_header("Cache-Control", "no-cache") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(data) + + def do_GET(self): + path = urlparse(self.path).path + + if path in ("/", "/index.html", "/compare"): + self.send_bytes(html_path.read_bytes(), "text/html; charset=utf-8") + return + + if path == "/health": + payload = { + "status": "ok", + "release_id": args.release_id, + "release_status": manifest.get("status"), + "pbf_file_count": manifest.get("pbf_file_count"), + "decoded_pbf_features": manifest.get("decoded_pbf_features"), + "s52_runtime_contract": (manifest.get("s52_runtime_contract") or {}).get("status"), + } + self.send_bytes(json.dumps(payload, ensure_ascii=False).encode("utf-8"), "application/json; charset=utf-8") + return + + if path == "/release-manifest.json": + self.send_bytes(manifest_path.read_bytes(), "application/json; charset=utf-8") + return + if path in ("/assets/maplibre-gl.js", "/assets/maplibre-gl.css"): + filename = path.rsplit("/", 1)[-1] + file_path = assets_root / filename + content_type = "application/javascript; charset=utf-8" if filename.endswith(".js") else "text/css; charset=utf-8" + self.send_bytes(file_path.read_bytes(), content_type) + return + + if path.startswith("/pbf/") and path.endswith(".pbf"): + parts = path[len("/pbf/"):].split("/") + if len(parts) != 3: + self.send_error(404) + return + z, x, yfile = parts + y = yfile[:-4] + if not (z.isdigit() and x.isdigit() and y.isdigit()): + self.send_error(404) + return + tile = pbf_root / z / x / yfile + if not tile.is_file(): + self.send_error(404, "PBF tile not found") + return + self.send_bytes(tile.read_bytes(), "application/vnd.mapbox-vector-tile") + return + + self.send_error(404) + + def log_message(self, fmt, *args): + print(f"{self.address_string()} - {fmt % args}") + + server = ThreadingHTTPServer((args.host, args.port), Handler) + print(f"Kyushu compare: http://{args.host}:{args.port}/") + print(f"Release: {release_dir}") + print(f"PBF: {pbf_root}") + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + + +if __name__ == "__main__": + main()