diff --git a/config/s52/cm95_s52_style_compiler_japan_compare_v1.json b/config/s52/cm95_s52_style_compiler_japan_compare_v1.json new file mode 100644 index 0000000..a149c19 --- /dev/null +++ b/config/s52/cm95_s52_style_compiler_japan_compare_v1.json @@ -0,0 +1,283 @@ +{ + "schema": "navsea.cm95.s52-style-compiler-config/v1", + "name": "CM95 S-52 Japan nationwide comparison style v1", + "palette": "DAY_BRIGHT", + "sprite": "http://192.168.200.184:18889/s52/sprite", + "background_color": "DEPDW", + "fallback_color": "NODTA", + "area_fill_opacity": 0.92, + "area_pattern_opacity": 0.85, + "line_width_fallback": 1.0, + "complex_line_width_px": 5.0, + "display_categories": [ + "Displaybase", + "Standard", + "Other" + ], + "source": { + "id": "cm95", + "type": "vector", + "tiles": [ + "http://192.168.200.184:18889/pbf/{z}/{x}/{y}.pbf" + ], + "bounds": [ + 122.0, + 20.0, + 154.5, + 46.0 + ], + "minzoom": 0, + "maxzoom": 12 + }, + "resources": { + "colors": "build/s52_resources_v2/base/s52_colors.json", + "symbol_metadata": "build/s52_resources_v2/base/s52_symbol_metadata.json", + "bitmap_pattern_metadata": "build/s52_resources_v2/base/s52_pattern_metadata.json", + "vector_pattern_metadata": "build/s52_resources_v2/ap/s52-sprite-ap-pattern-metadata.json", + "line_metadata": "build/s52_resources_v2/final/s52-sprite-full-line-metadata.json", + "sprite_index": "build/s52_resources_v2/final/s52-sprite-full.json", + "lookup_ir": "build/s52_presentation_ir_v2/lookup_rule_ir.json", + "resource_contract": "build/s52_resources_v2/resource_contract.json" + }, + "source_layers": [ + { + "name": "anchor_caution_hazard_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "anchor_caution_hazard_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "anchorage_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "anchorage_outline", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "anchorage_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "baseline_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "baseline_line", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "bridge_structure", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "clearance_limit_line", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "clearance_limit_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "cm95_unmapped", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "depth_contour", + "capabilities": [ + "line", + "text" + ] + }, + { + "name": "facility_boundary_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "facility_boundary_outline", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "facility_boundary_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "fixed_fishing_gear_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "land_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "navigation_hazard_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "navigation_hazard_outline", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "navigation_hazard_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "navigation_marks", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "onshore_structure_area", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "onshore_structure_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "pilot_station_point", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "place_label_land", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "s52_areas", + "capabilities": [ + "fill", + "line", + "symbol", + "text" + ] + }, + { + "name": "s52_lines", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "s52_points", + "capabilities": [ + "symbol", + "text" + ] + }, + { + "name": "seabed_line", + "capabilities": [ + "line", + "symbol", + "text" + ] + }, + { + "name": "seabed_text_point", + "capabilities": [ + "symbol", + "text" + ] + } + ] +} diff --git a/docs/CM95_S52_日本全国HTML对比使用说明_v1.md b/docs/CM95_S52_日本全国HTML对比使用说明_v1.md new file mode 100644 index 0000000..3688499 --- /dev/null +++ b/docs/CM95_S52_日本全国HTML对比使用说明_v1.md @@ -0,0 +1,31 @@ +# CM95 S-52 日本全国 HTML 对比使用说明 v1 + +日期:2026-09-27。项目:Linux X220 的 `/root/sourceserver/cm95`。 + +## 访问入口 + +- 全国对比:`http://192.168.200.184:18889/`,局域网访问。 +- 旧九州对比:`http://192.168.200.184:18888/`,保持不变。 +- 全国健康检查:`http://192.168.200.184:18889/health`。 +- 全国生产摘要:`http://192.168.200.184:18889/release-summary.json`。 +- 全国 PBF:`/pbf/{z}/{x}/{y}.pbf`,对应现有 Release 的 `pbf/delivery` 原始产物。 +- 左侧:全国 CM95 现有 S-52 Release;右侧:原 NavSea extentfix 旧版参考(不是同一版,也非官方基准)。 +- 保留旧页面的同步视图、完整 S-52/参考 Style 开关、runtime 预览、灯弧、等深线高亮、左右点选对象检查;新增全国总览与地区快捷定位。 + +## 版本和限制 + +- Release:`cm95-d1-62a85bd40d3a-ae61b8866e80`,Dataset 1,z0~z12。 +- 生产 177389 Job 中 177384 PASS,5 个低 Zoom FAILED;用户已接受遗留。z0~z3 总览可能缺图,尤其 z0/0/0。 +- 全国 PBF 原样全量提供;浏览器 S-52 Style 使用分层确定性抽样扫描实际 PBF 的使用情况以控制图层数量。此测试 Style **不代表全国全部符号已经逐条覆盖/验收**。 +- 右侧是不同数据/不同 Style 版本的视觉参考,差异需要再区分数据、Style、缩放及运行时原因。 +- 不存在已通过的全国 Finalize manifest;健康检查以原始生产摘要报告真实状态 `TEST_READY_WITH_KNOWN_ISSUES`。 +- 此对比入口用于辅助导航工程阶段 B 的调查与视觉验证,不是 B 阶段 PASS、官方 ENC 或 ECDIS 认证。 + +## 文件与运行 + +- 页面:`web/japan_compare.html`;服务:`web/japan_compare_server.py`;原九州页面/服务不改动。 +- 样式配置:`config/s52/cm95_s52_style_compiler_japan_compare_v1.json`。 +- 样式生成器:`tools/cm95_s52_japan_compare_build_style.py`;产物:`build/s52_style_compiler_japan_compare_v1/`。 +- 样式需要重建时,在项目目录执行 `./.venv/bin/python tools/cm95_s52_japan_compare_build_style.py`。 +- 服务重启命令:`./.venv/bin/python -u web/japan_compare_server.py --host 0.0.0.0 --port 18889`。 +- 不触碰 MySQL、Builder、生产 SQLite、原 PBF 或旧九州端口。 \ No newline at end of file diff --git a/docs/报告/CM95_S52_日本全国HTML对比启用报告_v1.md b/docs/报告/CM95_S52_日本全国HTML对比启用报告_v1.md new file mode 100644 index 0000000..779e473 --- /dev/null +++ b/docs/报告/CM95_S52_日本全国HTML对比启用报告_v1.md @@ -0,0 +1,34 @@ +# CM95 S-52 日本全国 HTML 对比启用报告 v1 + +日期:2026-09-27;设备:Linux X220;项目:`/root/sourceserver/cm95`。 + +## 1. 入口和实际数据 + +- 全国入口:`http://192.168.200.184:18889/`;旧九州入口:`http://192.168.200.184:18888/`(未修改)。 +- 全国 Release:`cm95-d1-62a85bd40d3a-ae61b8866e80`;左侧直接读取现有 `pbf/delivery`。 +- 右侧沿用旧 `style.navsea-delivery-full-semantic-extentfix.json`,与全国 S-52 不是同版本对等数据集,供视觉排查参考。 +- 全国生产状态仍是 177389 Job、177384 PASS、5 FAILED、177383 PBF;不伪造 Finalize manifest。 +- 页面使用独立 `web/japan_compare.html` 和 `web/japan_compare_server.py`,不覆盖九州页面及服务。 + +## 2. 浏览器样式与功能 + +- 样式按真实全国 PBF 的确定性分层/港口抽样编译:2418 / 177383 文件;产出 170 图层、1735989 字节。 +- 样式资源契约 PASS、hard_gate_failures=[];抽样仅控制 Style 用到的规则/符号集合,所有原 PBF 按请求直接服务。 +- 继续支持左右同步、S-52/extentfix 样式开关、runtime/灯弧预览、等深线高亮、点选对象检查。 +- 新增全国总览和 9 个地区快捷入口;z0~z3 的 5 个失败 Tile 有 UI 警告。 +- 全国 B 阶段尚未 PASS;本页面提供调查入口,不替代原始语义审计或实机航行验证。 + +## 3. 联通和烟雾检查 + +- 从另一台 Windows 设备访问全国根路径和健康检查:HTTP 200,Release ID 正确。 +- 全国 12 地区 × z10/z11/z12:36/36 PBF HTTP 200,MVT MIME 正确。 +- Style、Sprite、MapLibre JS、本地 Fukuoka z12 PBF:HTTP 200;旧九州根路径继续 HTTP 200。 +- headless Chromium 加载全国双地图:左右状态为已加载;本地 source loaded 且可查询已渲染 Feature;东京湾快捷定位后左右中心一致,可检索两侧 Feature。 +- 当前页面含 runtime 动态覆盖;headless 的 `leftMap.isStyleLoaded()` 未稳定为 true,但 `isSourceLoaded('cm95')` 和 `areTilesLoaded()` 为 true,显示/交互测试正常。B 运行时验收仍需人工复核。 +- JS 语法、Python 编译、样式 JSON/资源契约均通过。 + +## 4. 保留问题 + +- 5 个失败低 Zoom Tile 原样保留,特别是全球 z0/0/0 缺图。 +- Style 编译采用 2418 样本,不应被解释为全量符号或完整 S-52 呈现通过。 +- 浏览器比对仅为不同版本视觉参考;差异需进一步定位数据、Style、运行时及缩放来源。 diff --git a/tools/cm95_s52_japan_compare_build_style.py b/tools/cm95_s52_japan_compare_build_style.py new file mode 100644 index 0000000..ea809a2 --- /dev/null +++ b/tools/cm95_s52_japan_compare_build_style.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""只读汇总全国实际 PBF 使用情况,复用原 S-52 compiler 编译轻量对比样式。""" +from __future__ import annotations +import importlib.util +import argparse +import math +import os +import json +from pathlib import Path +import sys +import time + +ROOT = Path(__file__).resolve().parents[1] +RELEASE = "cm95-d1-62a85bd40d3a-ae61b8866e80" +PBF_DIR = ROOT / "build" / "releases-s52-v2" / RELEASE / "pbf" / "delivery" +OUT = ROOT / "build" / "s52_style_compiler_japan_compare_v1" +SPEC = importlib.util.spec_from_file_location("cm95_s52_style_compiler", ROOT / "tools" / "cm95_s52_style_compiler.py") +assert SPEC and SPEC.loader +compiler = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(compiler) + +def main() -> int: + parser = argparse.ArgumentParser(description="全国 Style 按真实 PBF 的代表性样本编译") + parser.add_argument("--all", action="store_true", help="扫描全部 PBF(资源密集,默认采用分层样本)") + args = parser.parse_args() + # 用 scandir 逐级枚举,避免为 177383 个文件创建和排序 Path 对象。 + strides = {9: 4, 10: 20, 11: 100, 12: 400} + files = [] + by_zoom = {} + all_count = 0 + with os.scandir(PBF_DIR) as zoom_items: + zoom_dirs = sorted((d for d in zoom_items if d.is_dir() and d.name.isdigit()), key=lambda d: int(d.name)) + for zoom_dir in zoom_dirs: + z = int(zoom_dir.name) + with os.scandir(zoom_dir.path) as x_items: + x_dirs = sorted((d for d in x_items if d.is_dir() and d.name.isdigit()), key=lambda d: int(d.name)) + for x_dir in x_dirs: + with os.scandir(x_dir.path) as y_items: + entries = sorted((d for d in y_items if d.is_file() and d.name.endswith('.pbf')), key=lambda d: int(d.name[:-4])) + for entry in entries: + i = by_zoom.get(z, 0) + by_zoom[z] = i + 1 + all_count += 1 + if args.all or z <= 8 or i % strides.get(z, 1) == 0: + files.append(Path(entry.path)) + if not all_count: + raise SystemExit('全国 PBF 目录为空') + ports = [(140.7288,41.7688),(140.8719,38.2682),(139.77,35.58), + (136.75,34.73),(135.32,34.63),(132.44,34.30),(130.38,33.59), + (129.97,33.44),(129.87,32.74),(130.56,31.56),(127.68,26.21), + (124.16,24.34)] + seen = set(files) + for lon,lat in ports: + for z in (10,11,12): + n = 1 << z + x = int((lon+180)/360*n) + y = int((1-math.asinh(math.tan(math.radians(lat)))/math.pi)/2*n) + path = PBF_DIR / str(z) / str(x) / (str(y)+".pbf") + if path.is_file() and path not in seen: + files.append(path) + seen.add(path) + files.sort() + print("SAMPLE", len(files), "of", all_count, "PBF; all production tiles remain served", flush=True) + OUT.mkdir(parents=True, exist_ok=True) + total = 0 + agg = None + chunk = [] + begun = time.monotonic() + for path in files: + chunk.append(compiler._scan_pbf_usage(path)) + total += 1 + if len(chunk) == 200 or total == len(files): + one = compiler._merge_pbf_usages(chunk, "national chunk") + agg = one if agg is None else compiler._merge_pbf_usages([agg, one], "national aggregate") + chunk.clear() + if total % 10000 == 0 or total == len(files): + print(f"USAGE {total}/{len(files)} elapsed={int(time.monotonic()-begun)}s", flush=True) + assert agg is not None + agg["pbf_count"] = total + agg["pbf"] = str(PBF_DIR) + (OUT / "usage_scope.json").write_text(json.dumps({ + "release_id": RELEASE, "mode": "all" if args.all else "deterministic_stratified_preview", + "all_pbf_count": all_count, "scanned_pbf_count": total, "strides": strides, + "ports": ports, "note": "仅控制浏览器 Style 的显示规则集合,不改变或删减全国 PBF 数据" + }, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + usage_path = OUT / "national_pbf_usage.json" + usage_path.write_text(json.dumps(agg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + # 不修改原编译器;将完整全国聚合 usage 交给原有编译入口。 + compiler._scan_pbf_directory = lambda path: agg + sys.argv = [ + str(ROOT / "tools" / "cm95_s52_style_compiler.py"), + "--config", str(ROOT / "config" / "s52" / "cm95_s52_style_compiler_japan_compare_v1.json"), + "--usage-pbf-dir", str(PBF_DIR), + "--output", str(OUT / "style.s52.json"), + "--audit-output", str(OUT / "style.s52.audit.json"), + ] + result = int(compiler.main()) + style = json.loads((OUT / "style.s52.json").read_text(encoding="utf-8")) + audit = json.loads((OUT / "style.s52.audit.json").read_text(encoding="utf-8")) + print("COMPLETE", json.dumps({"pbf_count": total, "layer_count": len(style["layers"]), + "style_bytes": (OUT / "style.s52.json").stat().st_size, + "hard_gate_failures": audit.get("hard_gate_failures"), + "resource_contract_status": audit.get("resource_contract_status")}, ensure_ascii=False), flush=True) + return result + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/web/japan_compare.html b/web/japan_compare.html new file mode 100644 index 0000000..1856a21 --- /dev/null +++ b/web/japan_compare.html @@ -0,0 +1,994 @@ + + +
+ + +