feat: add Kyushu semantic icon test delivery

This commit is contained in:
OpenAI Codex
2026-08-06 17:44:22 +08:00
parent 35d713993f
commit e52328b3f5
18 changed files with 10763 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
#!/usr/bin/env python3
"""审计九州语义图标测试版是否丢失对象、几何或非图标属性。"""
from __future__ import annotations
import argparse
import json
from collections import Counter
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
from typing import Any
import mapbox_vector_tile
EXPECTED_FIELDS = {"chart_icon_image", "icon_id", "arc_id"}
def stable(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def feature_signature(feature: dict[str, Any]) -> str:
properties = dict(feature.get("properties") or {})
for field in EXPECTED_FIELDS:
properties.pop(field, None)
return stable({
"id": feature.get("id"),
"geometry": feature.get("geometry"),
"properties": properties,
})
def audit_tile(args: tuple[str, str]) -> dict[str, Any]:
source_path, target_path = args
source = mapbox_vector_tile.decode(Path(source_path).read_bytes())
target = mapbox_vector_tile.decode(Path(target_path).read_bytes())
errors: list[dict[str, Any]] = []
source_layers = set(source)
target_layers = set(target)
if source_layers != target_layers:
errors.append({"kind": "layer_set", "source": sorted(source_layers), "target": sorted(target_layers)})
source_counts = Counter()
target_counts = Counter()
icon_counts = Counter()
old_prefix_counts = Counter()
for layer_name in sorted(source_layers | target_layers):
source_features = source.get(layer_name, {}).get("features", [])
target_features = target.get(layer_name, {}).get("features", [])
source_counts[layer_name] = len(source_features)
target_counts[layer_name] = len(target_features)
source_signatures = Counter(feature_signature(feature) for feature in source_features)
target_signatures = Counter(feature_signature(feature) for feature in target_features)
if source_signatures != target_signatures:
missing = source_signatures - target_signatures
extra = target_signatures - source_signatures
errors.append({
"kind": "feature_or_geometry_or_property",
"layer": layer_name,
"source_count": len(source_features),
"target_count": len(target_features),
"missing_count": sum(missing.values()),
"extra_count": sum(extra.values()),
})
for feature in target_features:
properties = feature.get("properties") or {}
for field in ("icon_id", "arc_id"):
value = properties.get(field)
if isinstance(value, str):
icon_counts[f"{field}:{value}"] += 1
for value in properties.values():
if isinstance(value, str) and (value.startswith("symbol-daytime-") or value.startswith("arc-daytime-")):
old_prefix_counts[value] += 1
return {
"tile": source_path,
"errors": errors,
"source_counts": dict(source_counts),
"target_counts": dict(target_counts),
"icon_counts": dict(icon_counts),
"old_prefix_counts": dict(old_prefix_counts),
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=Path, required=True)
parser.add_argument("--target-root", type=Path, required=True)
parser.add_argument("--report-dir", type=Path, required=True)
args = parser.parse_args()
pairs = []
for target in sorted(args.target_root.glob("*/*/*.pbf")):
source = args.source_root / target.relative_to(args.target_root)
if source.exists():
pairs.append((str(source), str(target)))
results: list[dict[str, Any]] = []
with ProcessPoolExecutor() as executor:
for result in executor.map(audit_tile, pairs, chunksize=16):
results.append(result)
error_rows = [error | {"tile": result["tile"]} for result in results for error in result["errors"]]
icon_counts = Counter()
old_prefix_counts = Counter()
source_features = Counter()
target_features = Counter()
for result in results:
icon_counts.update(result["icon_counts"])
old_prefix_counts.update(result["old_prefix_counts"])
source_features.update(result["source_counts"])
target_features.update(result["target_counts"])
payload = {
"source_root": str(args.source_root),
"target_root": str(args.target_root),
"tiles_target": len(list(args.target_root.glob("*/*/*.pbf"))),
"tiles_compared": len(results),
"tiles_with_errors": len({row["tile"] for row in error_rows}),
"errors": error_rows[:200],
"source_features": dict(source_features),
"target_features": dict(target_features),
"icon_counts": dict(icon_counts),
"old_prefix_counts": dict(old_prefix_counts),
"verdict": "PASS" if not error_rows and not old_prefix_counts else "FAIL",
}
args.report_dir.mkdir(parents=True, exist_ok=True)
(args.report_dir / "object_geometry_icon_audit.json").write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
lines = [
"# 九州语义图标测试版对象 / 几何 / 图标审计",
"",
f"- 来源:`{args.source_root}`",
f"- 目标:`{args.target_root}`",
f"- 目标瓦片:`{payload['tiles_target']}`,实际比较:`{payload['tiles_compared']}`",
f"- 有错误瓦片:`{payload['tiles_with_errors']}`",
f"- 旧图标前缀残留:`{sum(old_prefix_counts.values())}`",
f"- 结论:`{payload['verdict']}`",
"",
"本审计忽略预期的 `chart_icon_image`、`icon_id`、`arc_id` 字段变化逐图层比较对象数量、ID、几何和其余属性。",
]
(args.report_dir / "object_geometry_icon_audit.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print(json.dumps(payload, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,425 @@
#!/usr/bin/env python3
"""从当前全国 Full 基线生成九州语义图标替换测试包。"""
from __future__ import annotations
import json
import os
import shutil
from collections import Counter
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
from typing import Any
import mercantile
from navsea_tile_reencode_guard import (
assert_reencoded_tile_safe,
decode_tile_with_extents,
encode_layers_preserving_extents,
)
REPO_ROOT = Path(__file__).resolve().parent
CONFIG_PATH = REPO_ROOT / "tasks/pbf/navsea_semantic_icon_map_v1.json"
SOURCE_ROOT = Path("/home/wwwroot/pbf-delivery-full-20260418-rebuild")
TARGET_ROOT = Path("/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806")
TARGET_TILE_URL = "http://192.168.200.184/pbf-delivery-kyushu-semantic-icon-v1-20260806/{z}/{x}/{y}.pbf"
KYUSHU_BBOX = (128.0, 30.0, 132.5, 34.9)
ZOOMS = tuple(range(5, 13))
SPRITE_SOURCE = Path("/mnt/sda1/www/newpec/sprite-semantic")
SPRITE_TARGET = Path("/mnt/sda1/www/newpec/sprite-kyushu-semantic-icon-v1-20260806")
SPRITE_URL = "http://192.168.200.184/newpec/sprite-kyushu-semantic-icon-v1-20260806/sprite"
STYLE_SOURCE = REPO_ROOT / "src/pbf/style.navsea-delivery-full-semantic-extentfix.json"
STYLE_OUTPUT = REPO_ROOT / "src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json"
STYLE_DEPLOY = Path("/mnt/sda1/www/newpec/domain/style.navsea-delivery-kyushu-semantic-icon-v1.json")
NEWPEC_STYLE_SOURCE = REPO_ROOT / "src/pbf/style.json"
NEWPEC_STYLE_OUTPUT = REPO_ROOT / "src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json"
NEWPEC_STYLE_DEPLOY = Path("/mnt/sda1/www/newpec/domain/style.navsea-newpec-kyushu-icon-test-v1.json")
REPORT_DIR = REPO_ROOT / "report/kyushu_semantic_icon_v1_2026-08-06"
SIMPLE_ICON_LAYERS = {
"hazard-points",
"anchorage-symbols",
"anchor-hazard-points",
"anchor-hazard-points-428",
"facility-points",
"landmark-points",
}
STYLE_EXTRA_MAP = {
"light_flare_green": "fl_G",
"light_flare_red": "fl_R",
"light_flare_yellow_white": "fl_YW",
}
CANONICAL_ICON_MAP = {
"coastal_lighthouse_over_15m": "lt_cst",
"harbor_lighthouse": "lt_hbr",
"breakwater_lighthouse": "lt_bkw",
"minor_light": "lt_min",
"light_beacon": "lt_bcn",
"light_buoy": "bu_lit",
"lattice_buoy": "bu_lat",
"pillar_buoy": "bu_pil",
"can_buoy": "bu_can",
"buoy_generic": "bu_gen",
"nav_mark_vais": "mk_vais",
}
def load_config() -> dict[str, Any]:
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
def collect_tiles() -> list[tuple[int, int, int, Path]]:
tiles: list[tuple[int, int, int, Path]] = []
west, south, east, north = KYUSHU_BBOX
for z in ZOOMS:
for tile in mercantile.tiles(west, south, east, north, [z]):
rel = Path(str(tile.z)) / str(tile.x) / f"{tile.y}.pbf"
source = SOURCE_ROOT / rel
if source.exists():
tiles.append((tile.z, tile.x, tile.y, rel))
return sorted(tiles)
def copy_link(source: Path, target: Path) -> None:
target.parent.mkdir(parents=True, exist_ok=True)
if target.exists():
target.unlink()
try:
target.hardlink_to(source)
except OSError:
shutil.copy2(source, target)
def class_icon(layer_name: str, class_code: object, config: dict[str, Any]) -> str | None:
code = str(class_code)
if layer_name in {"navigation_hazard_point", "anchor_caution_hazard_point"}:
return config["hazard_class_map"].get(code)
if layer_name == "onshore_structure_point":
return config["landmark_class_map"].get(code)
if layer_name == "anchorage_point":
return config["anchorage_class_map"].get(code)
if layer_name == "facility_boundary_point":
return config["facility_class_map"].get(code)
return None
def navigation_icon(properties: dict[str, Any], config: dict[str, Any]) -> str | None:
legacy = properties.get("chart_icon_image")
if isinstance(legacy, str):
mapped = config["sprite_key_map"].get(legacy)
if mapped:
return mapped
display_code = properties.get("display_code")
if display_code is not None:
mapped = config["display_code_map"].get(str(display_code))
if mapped:
return mapped
canonical = properties.get("canonical_object_type")
if isinstance(canonical, str):
return CANONICAL_ICON_MAP.get(canonical)
return None
def arc_icon(properties: dict[str, Any], config: dict[str, Any]) -> str | None:
if properties.get("chart_symbol_code") != "lighthouse":
return None
if not properties.get("light_color_code"):
return None
if (
properties.get("canonical_object_type") == "coastal_lighthouse_over_15m"
and properties.get("light_sector_mode") == "sector"
):
return config["arc_rules"]["sector"]
color = str(properties.get("light_color_code"))
return config["arc_rules"].get(color, config["arc_rules"]["other"])
def rewrite_tile(
source: Path,
target: Path,
config: dict[str, Any],
*,
verify_geometry: bool = False,
) -> dict[str, Any]:
decoded, extents = decode_tile_with_extents(source.read_bytes())
changed_features = 0
icon_counts: Counter[str] = Counter()
arc_counts: Counter[str] = Counter()
old_values: Counter[str] = Counter()
unknown_old_values: Counter[str] = Counter()
output_layers: list[dict[str, Any]] = []
for layer_name, layer in decoded.items():
output_features = []
for feature in layer["features"]:
properties = dict(feature.get("properties") or {})
before = dict(properties)
legacy = properties.get("chart_icon_image")
if isinstance(legacy, str) and (
legacy.startswith("symbol-daytime-") or legacy.startswith("arc-daytime-")
):
old_values[legacy] += 1
icon_id = class_icon(layer_name, properties.get("class_code"), config)
if layer_name == "navigation_marks":
icon_id = icon_id or navigation_icon(properties, config)
if icon_id is None and isinstance(legacy, str):
icon_id = config["sprite_key_map"].get(legacy)
if icon_id:
properties["icon_id"] = icon_id
icon_counts[icon_id] += 1
properties.pop("chart_icon_image", None)
if layer_name == "navigation_marks":
arc_id = arc_icon(properties, config)
if arc_id:
properties["arc_id"] = arc_id
arc_counts[arc_id] += 1
for value in properties.values():
if isinstance(value, str) and (
value.startswith("symbol-daytime-") or value.startswith("arc-daytime-")
):
unknown_old_values[value] += 1
if properties != before:
changed_features += 1
output_feature = {
"geometry": feature["geometry"],
"properties": properties,
}
if feature.get("id") is not None:
output_feature["id"] = feature["id"]
output_features.append(output_feature)
output_layers.append({"name": layer_name, "features": output_features})
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(encode_layers_preserving_extents(output_layers, extents))
if verify_geometry:
assert_reencoded_tile_safe(source, target)
return {
"changed_features": changed_features,
"icon_counts": dict(icon_counts),
"arc_counts": dict(arc_counts),
"old_values": dict(old_values),
"unknown_old_values": dict(unknown_old_values),
}
def rewrite_tile_job(args: tuple[str, str, dict[str, Any], bool]) -> dict[str, Any]:
source, target, config, verify_geometry = args
result = rewrite_tile(
Path(source), Path(target), config, verify_geometry=verify_geometry
)
result["source"] = source
return result
def transform_style(config: dict[str, Any]) -> dict[str, Any]:
style = json.loads(STYLE_SOURCE.read_text(encoding="utf-8"))
key_map = dict(config["sprite_key_map"])
key_map.update(STYLE_EXTRA_MAP)
def transform(value: Any) -> Any:
if isinstance(value, dict):
return {key: transform(item) for key, item in value.items()}
if isinstance(value, list):
return [transform(item) for item in value]
if isinstance(value, str):
if value == "chart_icon_image":
return "icon_id"
return key_map.get(value, value)
return value
style = transform(style)
style["name"] = "NavSea Kyushu Semantic Icon Test v1"
style["sprite"] = SPRITE_URL
style["metadata"] = {
"navsea_test_version": "kyushu-semantic-icon-v1-20260806",
"source_baseline": str(SOURCE_ROOT),
"icon_map": str(CONFIG_PATH),
}
style["sources"]["navsea_delivery"]["tiles"] = [TARGET_TILE_URL]
for layer in style["layers"]:
layer_id = layer.get("id")
layout = layer.get("layout") or {}
if layer_id in SIMPLE_ICON_LAYERS:
layout["icon-image"] = ["coalesce", ["get", "icon_id"], ""]
layer["layout"] = layout
elif layer_id == "nav-light-arc":
layout["icon-image"] = ["coalesce", ["get", "arc_id"], ""]
layer["layout"] = layout
return style
def write_style(style: dict[str, Any], path: Path, deploy_path: Path) -> None:
payload = json.dumps(style, ensure_ascii=False, indent=2) + "\n"
path.write_text(payload, encoding="utf-8")
deploy_path.parent.mkdir(parents=True, exist_ok=True)
deploy_path.write_text(payload, encoding="utf-8")
def write_newpec_style() -> None:
style = json.loads(NEWPEC_STYLE_SOURCE.read_text(encoding="utf-8"))
style["name"] = "NavSea Newpec Kyushu Icon Test v1"
style["metadata"] = {
"navsea_test_version": "kyushu-semantic-icon-v1-20260806",
"role": "left_original_newpec_reference",
}
write_style(style, NEWPEC_STYLE_OUTPUT, NEWPEC_STYLE_DEPLOY)
def build_sprite(config: dict[str, Any]) -> dict[str, Any]:
if SPRITE_TARGET.exists():
raise RuntimeError(f"refusing to overwrite existing sprite target: {SPRITE_TARGET}")
SPRITE_TARGET.mkdir(parents=True)
source_meta: dict[str, dict[str, dict[str, Any]]] = {}
for filename in ("sprite.json", "sprite@2x.json"):
source_meta[filename] = json.loads((SPRITE_SOURCE / filename).read_text(encoding="utf-8"))
key_map = dict(config["sprite_key_map"])
key_map.update(STYLE_EXTRA_MAP)
fallback_sources = {"lm_cus": "symbol-daytime-660"}
added: dict[str, str] = {}
removed = set(key_map)
for filename, meta in source_meta.items():
# 测试包只保留新的短语键,避免把未使用的旧 symbol/arc 键继续带入 sprite。
output: dict[str, dict[str, Any]] = {}
for old_key, new_key in key_map.items():
source_key = old_key if old_key in meta else None
if source_key is None:
for candidate, candidate_new in key_map.items():
if candidate_new == new_key and candidate in meta:
source_key = candidate
break
if source_key is None:
source_key = fallback_sources.get(new_key)
if source_key and source_key in meta:
output[new_key] = dict(meta[source_key])
added[new_key] = source_key
(SPRITE_TARGET / filename).write_text(
json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
for filename in ("sprite.png", "sprite@2x.png"):
shutil.copy2(SPRITE_SOURCE / filename, SPRITE_TARGET / filename)
return {"added": added, "fallbacks": {"lm_cus": fallback_sources["lm_cus"]}}
def main() -> None:
config = load_config()
tiles = collect_tiles()
if TARGET_ROOT.exists():
raise RuntimeError(f"refusing to overwrite existing PBF target: {TARGET_ROOT}")
TARGET_ROOT.mkdir(parents=True)
totals: Counter[str] = Counter()
old_values: Counter[str] = Counter()
unknown_old_values: Counter[str] = Counter()
icon_counts: Counter[str] = Counter()
arc_counts: Counter[str] = Counter()
rewritten = 0
candidate_needles = [
key.encode("utf-8") for key in config["sprite_key_map"]
] + [
b"navigation_marks",
b"navigation_hazard_point",
b"anchor_caution_hazard_point",
b"anchorage_point",
b"facility_boundary_point",
b"onshore_structure_point",
]
rewrite_jobs: list[tuple[str, str, dict[str, Any], bool]] = []
candidate_count = 0
for z, x, y, rel in tiles:
source = SOURCE_ROOT / rel
target = TARGET_ROOT / rel
raw = source.read_bytes()
if not any(needle in raw for needle in candidate_needles):
copy_link(source, target)
continue
rewrite_jobs.append(
(str(source), str(target), config, candidate_count < 3)
)
candidate_count += 1
worker_count = min(os.cpu_count() or 4, 12)
with ProcessPoolExecutor(max_workers=worker_count) as executor:
for result in executor.map(rewrite_tile_job, rewrite_jobs, chunksize=8):
if result["changed_features"]:
rewritten += 1
totals["features_changed"] += result["changed_features"]
old_values.update(result["old_values"])
unknown_old_values.update(result["unknown_old_values"])
icon_counts.update(result["icon_counts"])
arc_counts.update(result["arc_counts"])
sprite = build_sprite(config)
style = transform_style(config)
write_style(style, STYLE_OUTPUT, STYLE_DEPLOY)
write_newpec_style()
REPORT_DIR.mkdir(parents=True, exist_ok=True)
summary = {
"version": "kyushu-semantic-icon-v1-20260806",
"source_root": str(SOURCE_ROOT),
"target_root": str(TARGET_ROOT),
"bbox": KYUSHU_BBOX,
"zooms": list(ZOOMS),
"tiles": len(tiles),
"tiles_rewritten": rewritten,
"candidate_tiles": candidate_count,
**totals,
"old_values": dict(old_values),
"unknown_old_values_after_rewrite": dict(unknown_old_values),
"icon_counts": dict(icon_counts),
"arc_counts": dict(arc_counts),
"sprite": sprite,
"style": str(STYLE_OUTPUT),
"newpec_style": str(NEWPEC_STYLE_OUTPUT),
}
(REPORT_DIR / "kyushu_semantic_icon_v1.json").write_text(
json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
lines = [
"# 九州语义图标替换测试版 v1",
"",
f"- PBF 来源:`{SOURCE_ROOT}`",
f"- PBF 输出:`{TARGET_ROOT}`",
f"- 范围:`{KYUSHU_BBOX}`",
f"- zoom`{ZOOMS[0]}-{ZOOMS[-1]}`",
f"- 瓦片数:`{len(tiles)}`",
f"- 重写瓦片:`{rewritten}`",
f"- 改动 feature`{totals['features_changed']}`",
f"- sprite`{SPRITE_TARGET}`",
f"- delivery style`{STYLE_OUTPUT}`",
f"- newpec style`{NEWPEC_STYLE_OUTPUT}`",
"",
"## 旧图标值",
"",
"```json",
json.dumps(dict(old_values), ensure_ascii=False, indent=2),
"```",
"",
"## 审计关注",
"",
"- `unknown_old_values_after_rewrite` 必须为空。",
"- `lm_cus` 当前使用 `symbol-daytime-660` 的临时视觉回退,需人工确认。",
"- 几何使用原 tile extent 重编码,并执行 extent 安全检查。",
]
(REPORT_DIR / "kyushu_semantic_icon_v1.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print(json.dumps(summary, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,378 @@
{
"label": "kyushu-semantic-icon-v1",
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
"source_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
"tile_count": 4707,
"outside_extent_tiles": 4707,
"source_extent_mismatch_tiles": 0,
"fixable_from_source_tiles": 0,
"repaired_tiles": 0,
"zoom_summary": {
"5": {
"tile_count": 2,
"outside_extent_tiles": 2,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 2
}
},
"6": {
"tile_count": 4,
"outside_extent_tiles": 4,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 4
}
},
"7": {
"tile_count": 9,
"outside_extent_tiles": 9,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 9
}
},
"8": {
"tile_count": 20,
"outside_extent_tiles": 20,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 20
}
},
"9": {
"tile_count": 70,
"outside_extent_tiles": 70,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 70
}
},
"10": {
"tile_count": 234,
"outside_extent_tiles": 234,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 234
}
},
"11": {
"tile_count": 884,
"outside_extent_tiles": 884,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"4096": 884
}
},
"12": {
"tile_count": 3484,
"outside_extent_tiles": 3484,
"source_extent_mismatch_tiles": 0,
"extent_counts": {
"1048576": 3484
}
}
},
"sample_tiles": [
{
"tile": "10/876/405.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/406.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/407.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/408.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/409.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/410.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/411.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/412.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/413.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/414.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/415.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/416.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/417.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/418.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/419.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/420.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/421.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/876/422.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/405.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/406.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/407.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/408.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/409.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/410.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/411.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/412.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/413.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/414.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/415.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
},
{
"tile": "10/877/416.pbf",
"extent_values": [
4096
],
"source_extent": 4096,
"outside_extent": true,
"max_overflow": 80,
"fixable_from_source": false
}
]
}

View File

@@ -0,0 +1,314 @@
# kyushu-semantic-icon-v1 几何 / Extent / Native 风险审计
## 范围
- target root: `/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
- source root: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
- tiles: `4707`
- repaired tiles: `0`
## 总体结论
- 坐标超当前 extent 的瓦片数:`4707`
- 与源瓦片 extent 不一致的瓦片数:`0`
- 可直接按源 extent 修复的瓦片数:`0`
## Zoom 汇总
### z5
- tiles: `2`
- outside extent: `2`
- source mismatch: `0`
- extent counts: `{4096: 2}`
### z6
- tiles: `4`
- outside extent: `4`
- source mismatch: `0`
- extent counts: `{4096: 4}`
### z7
- tiles: `9`
- outside extent: `9`
- source mismatch: `0`
- extent counts: `{4096: 9}`
### z8
- tiles: `20`
- outside extent: `20`
- source mismatch: `0`
- extent counts: `{4096: 20}`
### z9
- tiles: `70`
- outside extent: `70`
- source mismatch: `0`
- extent counts: `{4096: 70}`
### z10
- tiles: `234`
- outside extent: `234`
- source mismatch: `0`
- extent counts: `{4096: 234}`
### z11
- tiles: `884`
- outside extent: `884`
- source mismatch: `0`
- extent counts: `{4096: 884}`
### z12
- tiles: `3484`
- outside extent: `3484`
- source mismatch: `0`
- extent counts: `{1048576: 3484}`
## 代表问题样本
### `10/876/405.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/406.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/407.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/408.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/409.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/410.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/411.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/412.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/413.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/414.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/415.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/416.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/417.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/418.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/419.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/420.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/421.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/876/422.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/405.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/406.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/407.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/408.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/409.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/410.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/411.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/412.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/413.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/414.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/415.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`
### `10/877/416.pbf`
- extent values: `[4096]`
- source extent: `4096`
- outside extent: `True`
- max overflow: `80`
- fixable from source: `False`

View File

@@ -0,0 +1,177 @@
{
"version": "kyushu-semantic-icon-v1-20260806",
"source_root": "/home/wwwroot/pbf-delivery-full-20260418-rebuild",
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
"bbox": [
128.0,
30.0,
132.5,
34.9
],
"zooms": [
5,
6,
7,
8,
9,
10,
11,
12
],
"tiles": 4707,
"tiles_rewritten": 1340,
"candidate_tiles": 1343,
"features_changed": 40131,
"old_values": {
"symbol-daytime-301": 2536,
"symbol-daytime-308": 33,
"symbol-daytime-428": 13416,
"symbol-daytime-30500003": 165,
"symbol-daytime-303": 4993,
"symbol-daytime-327": 626,
"symbol-daytime-310": 1833,
"symbol-daytime-30500001": 25,
"symbol-daytime-320": 959,
"symbol-daytime-321": 290,
"symbol-daytime-429": 256,
"symbol-daytime-413": 187,
"symbol-daytime-325": 38,
"symbol-daytime-323": 77,
"symbol-daytime-30700003": 24,
"symbol-daytime-30500002": 16,
"symbol-daytime-335359": 12,
"symbol-daytime-405": 5329,
"symbol-daytime-412": 50,
"symbol-daytime-719": 54,
"symbol-daytime-410": 10
},
"unknown_old_values_after_rewrite": {},
"icon_counts": {
"lt_hbr": 2536,
"mk_lead_v": 33,
"hz_foul": 3598,
"hz_reef": 6659,
"lt_lead_c": 165,
"lm_mtn": 2059,
"hz_overfall": 912,
"lt_min": 4993,
"bu_lat": 626,
"lt_bcn": 1833,
"lm_chim": 1907,
"lm_twr": 2527,
"hz_seaweed": 470,
"lt_lead_a": 25,
"bu_lit": 959,
"lm_mar": 121,
"lm_fish": 846,
"lm_cus": 85,
"lm_mon": 42,
"bu_gen": 290,
"hz_wreck_survey": 432,
"hz_sandwave": 662,
"lm_ctrl": 28,
"bu_can": 38,
"hz_outfall": 174,
"hz_danger_clear": 26,
"bu_pil": 77,
"lm_prom": 8,
"hz_whirl": 27,
"mk_lead_c": 24,
"hz_reef_danger": 25,
"lt_lead_b": 16,
"mk_vais": 12,
"fac_fishport": 1164,
"hz_rock_awash": 1397,
"hz_rock_sub": 3932,
"hz_danger_iso": 212,
"fac_port": 261,
"hz_tower": 57,
"hz_wreck_sub": 50,
"fac_seastation": 48,
"hz_dolphin": 205,
"fac_fisherina": 11,
"fac_marina": 95,
"an_quar": 33,
"hz_wreck_hull": 10,
"hz_pile": 340,
"hz_obst": 60,
"an_restrict": 2,
"an_no_anchor": 4,
"an_desig": 15
},
"arc_counts": {
"arc_open_YW": 204,
"arc_YW": 977,
"arc_G": 610,
"arc_R": 769
},
"sprite": {
"added": {
"lt_cst": "coastal_lighthouse",
"lt_hbr": "harbor_lighthouse",
"lt_bkw": "breakwater_lighthouse",
"lt_min": "light_minor",
"lt_lead_a": "leading_light_variantt_30500001",
"lt_lead_b": "leading_light_variantt_30500002",
"lt_lead_c": "leading_light_variantt_30500003",
"lt_up_G": "symbol-daytime-30700001",
"lt_up_R": "symbol-daytime-30700002",
"mk_lead_c": "leading_mark_variantt_30700003",
"mk_lead_v": "leading_mark_variantt_308",
"lt_bcn": "light_beacon",
"bu_lit": "buoy_light",
"bu_gen": "buoy_generic",
"bu_pil": "buoy_pillar",
"bu_can": "buoy_can",
"bu_lat": "buoy_lattice",
"mk_vais": "nav_mark_vais",
"hz_rock_awash": "rock_exposed_drying_awash_group",
"hz_rock_sub": "sunken_rock",
"hz_danger_clear": "cleared_danger_object",
"hz_danger_iso": "isolated_danger",
"hz_wreck_hull": "wreck_hull_exposed",
"hz_wreck_sub": "wreck_fully_submerged_dangerous",
"hz_wreck_survey": "wreck_surveyed",
"hz_foul": "foul_ground",
"hz_sandwave": "sand_wave",
"hz_overfall": "tide_rips_overfalls",
"hz_whirl": "eddy_whirlpool",
"hz_seaweed": "seaweed",
"hz_obst": "obstruction",
"hz_reef": "fish_reef",
"hz_reef_danger": "fish_reef_dangerous",
"hz_tower": "tower_yagura_observation_platform",
"hz_pile": "bollard_pile_stake",
"hz_dolphin": "dolphin_structure",
"hz_outfall": "subsea_installation_outfall_intake",
"lm_chim": "symbol-daytime-640",
"lm_twr": "symbol-daytime-650",
"lm_mar": "symbol-daytime-660",
"lm_fish": "symbol-daytime-661",
"lm_cus": "symbol-daytime-660",
"lm_prom": "symbol-daytime-680",
"lm_mtn": "symbol-daytime-681",
"lm_ctrl": "symbol-daytime-682",
"lm_mon": "symbol-daytime-698",
"an_quar": "anchorage_quarantine",
"an_desig": "anchorage_designated",
"an_restrict": "restriction_area_group",
"an_no_anchor": "anchoring_prohibited",
"fac_port": "port_general_small_harbor_group",
"fac_fishport": "fishing_port",
"fac_marina": "marina",
"fac_fisherina": "fisherina",
"fac_seastation": "sea_station_umi_no_eki",
"fac_pilot": "pilot_station",
"tide_spot": "tidespot-daytime",
"fl_G": "light_flare_green",
"fl_R": "light_flare_red",
"fl_YW": "light_flare_yellow_white"
},
"fallbacks": {
"lm_cus": "symbol-daytime-660"
}
},
"style": "/root/sourceserver/pbf/src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json",
"newpec_style": "/root/sourceserver/pbf/src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json"
}

View File

@@ -0,0 +1,46 @@
# 九州语义图标替换测试版 v1
- PBF 来源:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
- PBF 输出:`/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
- 范围:`(128.0, 30.0, 132.5, 34.9)`
- zoom`5-12`
- 瓦片数:`4707`
- 重写瓦片:`1340`
- 改动 feature`40131`
- sprite`/mnt/sda1/www/newpec/sprite-kyushu-semantic-icon-v1-20260806`
- delivery style`/root/sourceserver/pbf/src/pbf/style.navsea-delivery-kyushu-semantic-icon-v1.json`
- newpec style`/root/sourceserver/pbf/src/pbf/style.navsea-newpec-kyushu-icon-test-v1.json`
## 旧图标值
```json
{
"symbol-daytime-301": 2536,
"symbol-daytime-308": 33,
"symbol-daytime-428": 13416,
"symbol-daytime-30500003": 165,
"symbol-daytime-303": 4993,
"symbol-daytime-327": 626,
"symbol-daytime-310": 1833,
"symbol-daytime-30500001": 25,
"symbol-daytime-320": 959,
"symbol-daytime-321": 290,
"symbol-daytime-429": 256,
"symbol-daytime-413": 187,
"symbol-daytime-325": 38,
"symbol-daytime-323": 77,
"symbol-daytime-30700003": 24,
"symbol-daytime-30500002": 16,
"symbol-daytime-335359": 12,
"symbol-daytime-405": 5329,
"symbol-daytime-412": 50,
"symbol-daytime-719": 54,
"symbol-daytime-410": 10
}
```
## 审计关注
- `unknown_old_values_after_rewrite` 必须为空。
- `lm_cus` 当前使用 `symbol-daytime-660` 的临时视觉回退,需人工确认。
- 几何使用原 tile extent 重编码,并执行 extent 安全检查。

View File

@@ -0,0 +1,173 @@
{
"source_root": "/home/wwwroot/pbf-delivery-full-20260418-rebuild",
"target_root": "/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806",
"tiles_target": 4707,
"tiles_compared": 4707,
"tiles_with_errors": 0,
"errors": [],
"source_features": {
"baseline_area": 225324,
"baseline_outline": 401949,
"hole_area": 110994,
"land_area": 65455,
"bathymetry_line": 413730,
"depth_contour": 171277,
"navigation_marks": 11627,
"bridge_structure": 7224,
"hazard_boundary_outline": 20151,
"place_label_land": 10983,
"submerged_reef_area": 770,
"place_label_sea": 12092,
"anchor_caution_hazard_point": 12932,
"baseline_line": 14640,
"fixed_fishing_gear_area": 9044,
"onshore_structure_line": 114122,
"onshore_structure_point": 7623,
"anchor_caution_hazard_area": 1809,
"anchor_caution_hazard_outline": 1815,
"clearance_limit_line": 325,
"hazard_boundary_line": 37,
"onshore_structure_area": 3640,
"depth_contour_overview": 2154,
"depth_zone_739": 181,
"route_boundary_point": 78,
"route_area": 51,
"route_outline": 161,
"depth_zone_740": 9,
"route_axis_line": 14,
"depth_zone_700": 8,
"depth_zone_725": 4,
"seabed_line": 2106,
"seabed_text_point": 33376,
"facility_boundary_point": 1579,
"navigation_hazard_point": 6316,
"depth_zone_741": 497,
"depth_zone_748": 45,
"depth_zone_749": 43,
"clearance_limit_point": 261,
"clip_outline_754": 223,
"facility_boundary_area": 449,
"navigation_hazard_outline": 48,
"anchorage_area": 55,
"anchorage_outline": 55,
"anchorage_point": 54,
"facility_boundary_outline": 233,
"pilot_station_point": 16,
"navigation_hazard_area": 24,
"leading_line_outline": 16,
"clip_outline_730": 4,
"facility_boundary_area_transparent": 4
},
"target_features": {
"baseline_area": 225324,
"baseline_outline": 401949,
"hole_area": 110994,
"land_area": 65455,
"bathymetry_line": 413730,
"depth_contour": 171277,
"navigation_marks": 11627,
"bridge_structure": 7224,
"hazard_boundary_outline": 20151,
"place_label_land": 10983,
"submerged_reef_area": 770,
"place_label_sea": 12092,
"anchor_caution_hazard_point": 12932,
"baseline_line": 14640,
"fixed_fishing_gear_area": 9044,
"onshore_structure_line": 114122,
"onshore_structure_point": 7623,
"anchor_caution_hazard_area": 1809,
"anchor_caution_hazard_outline": 1815,
"clearance_limit_line": 325,
"hazard_boundary_line": 37,
"onshore_structure_area": 3640,
"depth_contour_overview": 2154,
"depth_zone_739": 181,
"route_boundary_point": 78,
"route_area": 51,
"route_outline": 161,
"depth_zone_740": 9,
"route_axis_line": 14,
"depth_zone_700": 8,
"depth_zone_725": 4,
"seabed_line": 2106,
"seabed_text_point": 33376,
"facility_boundary_point": 1579,
"navigation_hazard_point": 6316,
"depth_zone_741": 497,
"depth_zone_748": 45,
"depth_zone_749": 43,
"clearance_limit_point": 261,
"clip_outline_754": 223,
"facility_boundary_area": 449,
"navigation_hazard_outline": 48,
"anchorage_area": 55,
"anchorage_outline": 55,
"anchorage_point": 54,
"facility_boundary_outline": 233,
"pilot_station_point": 16,
"navigation_hazard_area": 24,
"leading_line_outline": 16,
"clip_outline_730": 4,
"facility_boundary_area_transparent": 4
},
"icon_counts": {
"icon_id:lt_hbr": 2536,
"arc_id:arc_open_YW": 204,
"arc_id:arc_YW": 977,
"arc_id:arc_G": 610,
"arc_id:arc_R": 769,
"icon_id:mk_lead_v": 33,
"icon_id:hz_foul": 3598,
"icon_id:hz_reef": 6659,
"icon_id:lt_lead_c": 165,
"icon_id:lm_mtn": 2059,
"icon_id:hz_overfall": 912,
"icon_id:lt_min": 4993,
"icon_id:bu_lat": 626,
"icon_id:lt_bcn": 1833,
"icon_id:lm_chim": 1907,
"icon_id:lm_twr": 2527,
"icon_id:hz_seaweed": 470,
"icon_id:lt_lead_a": 25,
"icon_id:bu_lit": 959,
"icon_id:lm_mar": 121,
"icon_id:lm_fish": 846,
"icon_id:lm_cus": 85,
"icon_id:lm_mon": 42,
"icon_id:bu_gen": 290,
"icon_id:hz_wreck_survey": 432,
"icon_id:hz_sandwave": 662,
"icon_id:lm_ctrl": 28,
"icon_id:bu_can": 38,
"icon_id:hz_outfall": 174,
"icon_id:hz_danger_clear": 26,
"icon_id:bu_pil": 77,
"icon_id:lm_prom": 8,
"icon_id:hz_whirl": 27,
"icon_id:mk_lead_c": 24,
"icon_id:hz_reef_danger": 25,
"icon_id:lt_lead_b": 16,
"icon_id:mk_vais": 12,
"icon_id:fac_fishport": 1164,
"icon_id:hz_rock_awash": 1397,
"icon_id:hz_rock_sub": 3932,
"icon_id:hz_danger_iso": 212,
"icon_id:fac_port": 261,
"icon_id:hz_tower": 57,
"icon_id:hz_wreck_sub": 50,
"icon_id:fac_seastation": 48,
"icon_id:hz_dolphin": 205,
"icon_id:fac_fisherina": 11,
"icon_id:fac_marina": 95,
"icon_id:an_quar": 33,
"icon_id:hz_wreck_hull": 10,
"icon_id:hz_pile": 340,
"icon_id:hz_obst": 60,
"icon_id:an_restrict": 2,
"icon_id:an_no_anchor": 4,
"icon_id:an_desig": 15
},
"old_prefix_counts": {},
"verdict": "PASS"
}

View File

@@ -0,0 +1,10 @@
# 九州语义图标测试版对象 / 几何 / 图标审计
- 来源:`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
- 目标:`/home/wwwroot/pbf-delivery-kyushu-semantic-icon-v1-20260806`
- 目标瓦片:`4707`,实际比较:`4707`
- 有错误瓦片:`0`
- 旧图标前缀残留:`0`
- 结论:`PASS`
本审计忽略预期的 `chart_icon_image``icon_id``arc_id` 字段变化逐图层比较对象数量、ID、几何和其余属性。

View File

@@ -0,0 +1,64 @@
{
"used_keys": [
"an_desig",
"an_no_anchor",
"an_quar",
"an_restrict",
"arc_G",
"arc_R",
"arc_YW",
"arc_open_YW",
"bu_can",
"bu_gen",
"bu_lat",
"bu_lit",
"bu_pil",
"fac_fisherina",
"fac_fishport",
"fac_marina",
"fac_port",
"fac_seastation",
"hz_danger_clear",
"hz_danger_iso",
"hz_dolphin",
"hz_foul",
"hz_obst",
"hz_outfall",
"hz_overfall",
"hz_pile",
"hz_reef",
"hz_reef_danger",
"hz_rock_awash",
"hz_rock_sub",
"hz_sandwave",
"hz_seaweed",
"hz_tower",
"hz_whirl",
"hz_wreck_hull",
"hz_wreck_sub",
"hz_wreck_survey",
"lm_chim",
"lm_ctrl",
"lm_cus",
"lm_fish",
"lm_mar",
"lm_mon",
"lm_mtn",
"lm_prom",
"lm_twr",
"lt_bcn",
"lt_hbr",
"lt_lead_a",
"lt_lead_b",
"lt_lead_c",
"lt_min",
"mk_lead_c",
"mk_lead_v",
"mk_vais"
],
"used_key_count": 55,
"sprite_key_count": 64,
"missing_keys": [],
"legacy_sprite_keys": [],
"verdict": "PASS"
}

View File

@@ -0,0 +1,7 @@
# 九州测试版 Sprite 图标闭合审计
- 使用中的 icon_id/arc_id`55`
- sprite 键数:`64`
- 缺失键:`0`
- 旧前缀键:`0`
- 结论:`PASS`

View File

@@ -0,0 +1,141 @@
{
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html",
"cases": [
{
"id": "karatsu_kyushu_icon_v1_semantic_z12",
"label": "唐津九州语义图标测试",
"variant": "semantic",
"zoom": 12,
"center": [
129.9697,
33.4425
],
"radius_nm": 5,
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=129.9697%2C33.4425&zoom=12",
"changed_pixels": 287577,
"total_pixels": 364800,
"changed_ratio": 0.788314,
"mean_abs_rgb": [
39.3424,
42.4767,
74.167
],
"rms_rgb": [
61.2913,
62.6326,
97.1048
],
"diff_bbox": [
0,
0,
640,
513
],
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_left.png",
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_right.png",
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_diff.png"
},
{
"id": "karatsu_kyushu_icon_v1_semantic_z10",
"label": "唐津九州语义图标测试",
"variant": "semantic",
"zoom": 10,
"center": [
129.9697,
33.4425
],
"radius_nm": 5,
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=129.9697%2C33.4425&zoom=10",
"changed_pixels": 274296,
"total_pixels": 364800,
"changed_ratio": 0.751908,
"mean_abs_rgb": [
33.6012,
36.7804,
64.1346
],
"rms_rgb": [
56.4791,
56.9124,
89.4452
],
"diff_bbox": [
0,
0,
640,
513
],
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_left.png",
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_right.png",
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_diff.png"
},
{
"id": "hakata_kyushu_icon_v1_semantic_z12",
"label": "博多港九州语义图标测试",
"variant": "semantic",
"zoom": 12,
"center": [
130.335,
33.6385
],
"radius_nm": 10,
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=130.335%2C33.6385&zoom=12",
"changed_pixels": 237978,
"total_pixels": 364800,
"changed_ratio": 0.652352,
"mean_abs_rgb": [
30.9085,
30.6867,
39.4968
],
"rms_rgb": [
55.1912,
58.1514,
67.7886
],
"diff_bbox": [
0,
0,
640,
513
],
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_left.png",
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_right.png",
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_diff.png"
},
{
"id": "hakata_kyushu_icon_v1_semantic_z10",
"label": "博多港九州语义图标测试",
"variant": "semantic",
"zoom": 10,
"center": [
130.335,
33.6385
],
"radius_nm": 10,
"compare_url": "http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=130.335%2C33.6385&zoom=10",
"changed_pixels": 236727,
"total_pixels": 364800,
"changed_ratio": 0.648923,
"mean_abs_rgb": [
31.9821,
32.9781,
55.3087
],
"rms_rgb": [
57.9478,
56.188,
83.9112
],
"diff_bbox": [
0,
0,
640,
513
],
"left_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_left.png",
"right_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_right.png",
"diff_image": "report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_diff.png"
}
]
}

View File

@@ -0,0 +1,48 @@
# 全国 AOI 截图对比审计
## 总览
- compare page: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html`
- cases: `4`
## 差异排序
### 唐津九州语义图标测试 · semantic · z12
- changed ratio: `0.788314`
- changed pixels: `287577` / `364800`
- mean abs rgb: `[39.3424, 42.4767, 74.167]`
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=129.9697%2C33.4425&zoom=12`
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_left.png`
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_right.png`
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z12/compare_diff.png`
### 唐津九州语义图标测试 · semantic · z10
- changed ratio: `0.751908`
- changed pixels: `274296` / `364800`
- mean abs rgb: `[33.6012, 36.7804, 64.1346]`
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=129.9697%2C33.4425&zoom=10`
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_left.png`
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_right.png`
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/karatsu_kyushu_icon_v1_semantic_z10/compare_diff.png`
### 博多港九州语义图标测试 · semantic · z12
- changed ratio: `0.652352`
- changed pixels: `237978` / `364800`
- mean abs rgb: `[30.9085, 30.6867, 39.4968]`
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=130.335%2C33.6385&zoom=12`
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_left.png`
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_right.png`
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z12/compare_diff.png`
### 博多港九州语义图标测试 · semantic · z10
- changed ratio: `0.648923`
- changed pixels: `236727` / `364800`
- mean abs rgb: `[31.9821, 32.9781, 55.3087]`
- compare url: `http://192.168.200.184/newpec/navsea-compare-kyushu-semantic-icon-v1.html?variant=semantic&audit=1&center=130.335%2C33.6385&zoom=10`
- left image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_left.png`
- right image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_right.png`
- diff image: `report/kyushu_semantic_icon_v1_2026-08-06/visual/hakata_kyushu_icon_v1_semantic_z10/compare_diff.png`

View File

@@ -0,0 +1,101 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>NavSea 九州语义图标替换测试 v1</title>
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
<style>
:root { --bg:#eef2ed; --panel:rgba(250,248,242,.95); --ink:#17232b; --muted:#667780; --line:rgba(23,35,43,.14); }
* { box-sizing:border-box; }
html,body { margin:0; width:100%; height:100%; overflow:hidden; background:var(--bg); color:var(--ink); font-family:"Avenir Next","Segoe UI","PingFang SC","Noto Sans SC",sans-serif; }
body { display:grid; grid-template-rows:auto 1fr; }
.toolbar { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 12px; background:var(--panel); border-bottom:1px solid var(--line); box-shadow:0 8px 20px rgba(23,35,43,.12); z-index:10; }
.title { font-size:16px; font-weight:700; }
.subtitle { margin-top:2px; color:var(--muted); font-size:11px; line-height:1.35; }
.actions { display:flex; align-items:center; justify-content:flex-end; gap:7px; flex-wrap:wrap; }
.chip { max-width:260px; padding:7px 9px; border:1px solid var(--line); border-radius:999px; background:#fffdf8; font-size:10px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
button { height:31px; padding:0 11px; border:1px solid rgba(23,35,43,.16); border-radius:8px; background:#17333f; color:#fff; font:inherit; font-size:11px; font-weight:700; cursor:pointer; }
button:disabled { opacity:.55; cursor:wait; }
.layout { min-height:0; display:grid; grid-template-columns:1fr 1fr; }
.pane { position:relative; min-width:0; border-right:1px solid var(--line); }
.pane:last-child { border-right:0; }
.map { position:absolute; inset:0; }
.tag { position:absolute; top:10px; left:10px; z-index:2; padding:8px 10px; border:1px solid var(--line); border-radius:10px; background:var(--panel); box-shadow:0 8px 18px rgba(23,35,43,.12); font-size:11px; }
.tag strong { display:block; margin-bottom:2px; font-size:12px; }
.tag span { color:var(--muted); }
.inspect { position:fixed; right:12px; bottom:12px; z-index:5; width:min(390px,calc(100vw - 24px)); max-height:42vh; overflow:auto; padding:10px 12px; border:1px solid var(--line); border-radius:12px; background:var(--panel); box-shadow:0 12px 26px rgba(23,35,43,.16); }
.inspect h3 { margin:0 0 5px; font-size:12px; }
.inspect p { margin:0 0 7px; color:var(--muted); font-size:10px; }
.inspect dl { display:grid; grid-template-columns:84px 1fr; gap:3px 7px; margin:0 0 7px; font-size:10px; }
.inspect dt { color:var(--muted); font-weight:700; }
.inspect dd { margin:0; word-break:break-word; }
.inspect pre { margin:0; padding:7px; border-radius:8px; background:#17232b; color:#eef4f7; font-size:10px; white-space:pre-wrap; word-break:break-word; }
#status { position:fixed; left:50%; bottom:12px; z-index:4; transform:translateX(-50%); max-width:calc(100vw - 430px); padding:7px 11px; border-radius:999px; background:rgba(23,35,43,.86); color:#fff; font-size:10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@media (max-width:900px) { .toolbar { align-items:flex-start; flex-direction:column; } .actions { justify-content:flex-start; } #status { max-width:calc(100vw - 24px); bottom:6px; } .inspect { bottom:38px; } }
</style>
</head>
<body>
<header class="toolbar">
<div>
<div class="title">NavSea 九州语义图标替换测试 v1</div>
<div class="subtitle">左侧:原始 newpec右侧当前 Full 基线生成的九州语义图标测试版。两侧同步平移、缩放,可点击对象查看属性。</div>
</div>
<div class="actions">
<span id="version" class="chip">HTML: kyushu-semantic-icon-v1-20260806</span>
<span id="style" class="chip">Style: 加载中</span>
<button id="reload" type="button">重新加载</button>
<button id="home" type="button">回到九州</button>
</div>
</header>
<main class="layout">
<section class="pane"><div class="tag"><strong style="color:#305f72">左侧 · 原始 newpec</strong><span>原始瓦片 + 原始 sprite</span></div><div id="original" class="map"></div></section>
<section class="pane"><div class="tag"><strong style="color:#8b5e34">右侧 · 语义图标测试版</strong><span>Kyushu semantic icon v1</span></div><div id="delivery" class="map"></div></section>
</main>
<aside class="inspect"><h3>点击对比</h3><p id="hint">点击左右任一地图对象,查看 source-layer、render layer 和属性。</p><dl><dt>面板</dt><dd id="side">-</dd><dt>坐标</dt><dd id="lnglat">-</dd><dt>source-layer</dt><dd id="layer">-</dd><dt>render layer</dt><dd id="render">-</dd></dl><pre id="json">{\n "message": "等待点击对象"\n}</pre></aside>
<div id="status">等待加载样式…</div>
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
<script>
const VERSION = "kyushu-semantic-icon-v1-20260806";
const ORIGINAL_STYLE = "./domain/style.navsea-newpec-kyushu-icon-test-v1.json";
const DELIVERY_STYLE = "./domain/style.navsea-delivery-kyushu-semantic-icon-v1.json";
const CENTER = [130.4, 32.5], ZOOM = 7.5;
const q = new URLSearchParams(location.search);
const requestedCenter = (q.get("center") || "").split(",").map(Number);
const camera = { center: requestedCenter.length === 2 && requestedCenter.every(Number.isFinite) ? requestedCenter : CENTER, zoom: Number(q.get("zoom")) || ZOOM, bearing: Number(q.get("bearing")) || 0, pitch: Number(q.get("pitch")) || 0 };
let originalMap, deliveryMap, lock = false, cacheVersion = `${VERSION}-${Date.now()}`;
const $ = (id) => document.getElementById(id);
function bust(url) { const join = url.includes("?") ? "&" : "?"; return `${url}${join}v=${encodeURIComponent(cacheVersion)}`; }
function touch(style) {
const next = structuredClone(style);
if (next.sprite) next.sprite = bust(next.sprite);
if (next.glyphs) next.glyphs = bust(next.glyphs);
for (const source of Object.values(next.sources || {})) {
if (source.tiles) source.tiles = source.tiles.map(bust);
if (source.url) source.url = bust(source.url);
}
return next;
}
async function loadStyle(url) { const response = await fetch(bust(url), { cache:"no-store" }); if (!response.ok) throw new Error(`加载样式失败: ${url}`); return touch(await response.json()); }
function sync(primary, secondary) { primary.on("move", () => { if (lock) return; lock = true; secondary.jumpTo({ center:primary.getCenter(), zoom:primary.getZoom(), bearing:primary.getBearing(), pitch:primary.getPitch() }); lock = false; }); }
function inspect(map, label) { map.on("click", (event) => { const feature = map.queryRenderedFeatures(event.point)[0]; if (!feature) { $("hint").textContent = `${label}:点击位置未命中对象。`; return; } $("hint").textContent = "已选中对象。继续点击可查看另一侧。"; $("side").textContent = label; $("lnglat").textContent = `${event.lngLat.lng.toFixed(6)}, ${event.lngLat.lat.toFixed(6)}`; $("layer").textContent = feature.sourceLayer || "-"; $("render").textContent = feature.layer?.id || "-"; $("json").textContent = JSON.stringify(feature.properties || {}, null, 2); }); }
async function start() {
$("reload").disabled = true; $("status").textContent = `正在加载九州双屏… ${VERSION}`; cacheVersion = `${VERSION}-${Date.now()}`;
try {
const [leftStyle, rightStyle] = await Promise.all([loadStyle(ORIGINAL_STYLE), loadStyle(DELIVERY_STYLE)]);
$("style").textContent = "Style: newpec 原始 / delivery semantic v1";
if (!originalMap) {
originalMap = new maplibregl.Map({ container:"original", style:leftStyle, ...camera });
deliveryMap = new maplibregl.Map({ container:"delivery", style:rightStyle, ...camera });
originalMap.addControl(new maplibregl.NavigationControl(), "top-right"); deliveryMap.addControl(new maplibregl.NavigationControl(), "top-right");
sync(originalMap, deliveryMap); sync(deliveryMap, originalMap); inspect(originalMap, "左侧原始版"); inspect(deliveryMap, "右侧语义版");
let loaded = 0; const mark = () => { loaded += 1; if (loaded === 2) $("status").textContent = `九州双屏已加载,可拖动、缩放、点击审计。${VERSION}`; }; originalMap.once("load", mark); deliveryMap.once("load", mark);
} else {
let loaded = 0; const mark = () => { loaded += 1; if (loaded === 2) { originalMap.jumpTo(camera); deliveryMap.jumpTo(camera); $("status").textContent = `九州双屏已重新加载。${VERSION}`; } }; originalMap.once("style.load", mark); deliveryMap.once("style.load", mark); originalMap.setStyle(leftStyle, { diff:false }); deliveryMap.setStyle(rightStyle, { diff:false });
}
} catch (error) { console.error(error); $("status").textContent = error.message || "加载失败"; } finally { $("reload").disabled = false; }
}
$("reload").addEventListener("click", start); $("home").addEventListener("click", () => { if (originalMap && deliveryMap) { originalMap.jumpTo(camera); deliveryMap.jumpTo(camera); } }); start();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
[
{
"id": "hakata_kyushu_icon_v1",
"label": "博多港九州语义图标测试",
"center": [130.335, 33.6385],
"radius_nm": 10,
"zoom_levels": [10, 12]
},
{
"id": "karatsu_kyushu_icon_v1",
"label": "唐津九州语义图标测试",
"center": [129.9697, 33.4425],
"radius_nm": 5,
"zoom_levels": [10, 12]
}
]

View File

@@ -0,0 +1,156 @@
# NavSea 语义图标替换表-v1
状态:设计稿,尚未写入 PBF、style 或 sprite。
基线:
- PBF`/home/wwwroot/pbf-delivery-full-20260418-rebuild`
- style`src/pbf/style.navsea-delivery-full-semantic-extentfix.json`
- sprite`/mnt/sda1/www/newpec/sprite-semantic/sprite`
## 1. 统一规则
PBF、style 和 `sprite.json` 使用同一个短语 ID。PBF 中建议使用:
```text
icon_id普通点状图标
arc_id灯弧图标
```
`class_code` 继续保留用于对象追溯,但不再被 style 用来拼接图标名称。
颜色缩写:
| 缩写 | 含义 |
|---|---|
| `G` | 绿色 |
| `R` | 红色 |
| `Y` | 黄色 |
| `W` | 白色 |
| `B` | 蓝色 |
| `YW` | 黄/白组合 |
## 2. 灯弧
| 当前 key | 新 ID | 条件/含义 |
|---|---|---|
| `arc-daytime-m1` | `arc_G` | 绿色闭合灯弧 |
| `arc-daytime-m2` | `arc_R` | 红色闭合灯弧 |
| `arc-daytime-m3` | `arc_YW` | 黄/白闭合灯弧 |
| `arc-daytime-04027289` | `arc_open_YW` | `coastal_lighthouse_over_15m``light_sector_mode=sector` |
普通灯弧按 `light_color_code` 选择 `arc_G``arc_R``arc_YW`;扇弧条件优先级最高。
## 3. 航标、灯塔、浮标
| 当前 key | 新 ID | 含义 |
|---|---|---|
| `symbol-daytime-300` | `lt_cst` | 沿岸灯台 |
| `symbol-daytime-301` | `lt_hbr` | 港湾灯台 |
| `symbol-daytime-302` | `lt_bkw` | 防波堤灯台 |
| `symbol-daytime-303` | `lt_min` | 小型灯 |
| `symbol-daytime-30500001` | `lt_lead_a` | 导标变体 A |
| `symbol-daytime-30500002` | `lt_lead_b` | 导标变体 B |
| `symbol-daytime-30500003` | `lt_lead_c` | 导标变体 C |
| `symbol-daytime-30700001` | `lt_up_G` | 绿色上向灯 |
| `symbol-daytime-30700002` | `lt_up_R` | 红色上向灯 |
| `symbol-daytime-30700003` | `mk_lead_c` | 导标变体 |
| `symbol-daytime-308` | `mk_lead_v` | 导标变体 |
| `symbol-daytime-310` | `lt_bcn` | 灯标 |
| `symbol-daytime-320` | `bu_lit` | 灯浮标 |
| `symbol-daytime-321` | `bu_gen` | 普通浮标 |
| `symbol-daytime-323` | `bu_pil` | 圆柱/柱型浮标 |
| `symbol-daytime-325` | `bu_can` | 圆筒型浮标 |
| `symbol-daytime-327` | `bu_lat` | 桁架型浮标 |
| `symbol-daytime-335359` | `mk_vais` | V-AIS 航标 |
| `symbol-daytime-719` | `an_quar` | 检疫锚地 |
现有 style 中的别名也统一归并:
| 当前 key | 新 ID |
|---|---|
| `harbor_lighthouse` | `lt_hbr` |
| `breakwater_lighthouse` | `lt_bkw` |
## 4. 危险物与鱼礁
| 当前 class_code / key | 新 ID | 含义 |
|---|---|---|
| `401``402``403` / `symbol-daytime-401` | `hz_rock_awash` | 水上岩、干出岩、洗岩组 |
| `404` / `symbol-daytime-404` | `hz_rock_sub` | 暗岩 |
| `405` / `symbol-daytime-405` | `hz_danger_clear` | 扫海后的危险物 |
| `409` / `symbol-daytime-409` | `hz_danger_iso` | 孤立危险物 |
| `410` / `symbol-daytime-410` | `hz_wreck_hull` | 船体露出沉船 |
| `412` / `symbol-daytime-412` | `hz_wreck_sub` | 危险全沉没船 |
| `413``415` / `symbol-daytime-413` | `hz_wreck_survey` | 测量/调查后的沉船 |
| `420` / `symbol-daytime-420` | `hz_foul` | 险恶物 |
| `421` / `symbol-daytime-421` | `hz_sandwave` | 沙波 |
| `422` / `symbol-daytime-422` | `hz_overfall` | 急潮、波纹、激潮 |
| `424` / `symbol-daytime-424` | `hz_whirl` | 渦流 |
| `425` / `symbol-daytime-425` | `hz_seaweed` | 海草 |
| `427` / `symbol-daytime-427` | `hz_obst` | 障碍物 |
| `428` / `symbol-daytime-428` | `hz_reef` | 鱼礁 |
| `429` / `symbol-daytime-429` | `hz_reef_danger` | 危险鱼礁 |
| `431` / `symbol-daytime-431` | `hz_tower` | 塔、橹、测台 |
| `432` / `symbol-daytime-432` | `hz_pile` | 墩柱、桩、杭 |
| `433` / `symbol-daytime-433` | `hz_dolphin` | 系缆墩 |
| `434` / `symbol-daytime-434` | `hz_outfall` | 海底设施、排水口、取水口 |
## 5. 陆上地标
这些图标由 `onshore_structure_point``class_code` 生成,必须在重建 PBF 时直接写入 `icon_id`,不能继续由 style 动态拼接编号。
| class_code | canonical_object_type | 新 ID |
|---:|---|---|
| 640 | `chimney` | `lm_chim` |
| 650 | `tower_yagura_windmill` | `lm_twr` |
| 660 | `maritime_office` | `lm_mar` |
| 661 | `fishing_cooperative` | `lm_fish` |
| 662 | `customs_office` | `lm_cus` |
| 680 | 陆上显著物(观览车等) | `lm_prom` |
| 681 | `mountain_top` | `lm_mtn` |
| 682 | 管制信号所 | `lm_ctrl` |
| 698 | `other_landmark_monument` | `lm_mon` |
## 6. 当前 PBF 中的直接图标值
当前全国 Full PBF 实际出现的 `chart_icon_image` 值,均应落入本表:
```text
symbol-daytime-301
symbol-daytime-303
symbol-daytime-30500001
symbol-daytime-30500002
symbol-daytime-30500003
symbol-daytime-30700003
symbol-daytime-308
symbol-daytime-310
symbol-daytime-320
symbol-daytime-321
symbol-daytime-323
symbol-daytime-325
symbol-daytime-327
symbol-daytime-335359
symbol-daytime-405
symbol-daytime-410
symbol-daytime-412
symbol-daytime-413
symbol-daytime-428
symbol-daytime-429
symbol-daytime-719
```
其中 `symbol-daytime-719` 当前语义建议为:
```text
an_quar = quarantine anchorage
```
## 7. 实施约束
- 本表确认后,统一重建全部 Full PBF不在现有 PBF 上做局部替换。
- style 的 `icon-image` 统一读取 `icon_id``arc_id`,删除 `class_code` 拼接逻辑。
- sprite JSON key 与新 ID 完全一致。
- `class_code`、FID 和 source-layer 继续保留,用于反向追溯和对象审计。
- `symbol-daytime-*``arc-daytime-*` 只允许出现在转换审计日志或历史映射表中,不能出现在正式 PBF、正式 style 和正式 sprite JSON 中。
- `symbol-daytime-662` 对应 `lm_cus`,需要补上海关图标素材。

View File

@@ -0,0 +1,224 @@
{
"version": "semantic-icon-v1",
"baseline": "full-semantic-extentfix",
"fields": {
"point_icon": "icon_id",
"light_arc": "arc_id",
"legacy_point_icon": "chart_icon_image"
},
"color_codes": {
"green": "G",
"red": "R",
"yellow": "Y",
"white": "W",
"blue": "B",
"yellow_white": "YW"
},
"sprite_key_map": {
"symbol-daytime-300": "lt_cst",
"symbol-daytime-301": "lt_hbr",
"symbol-daytime-302": "lt_bkw",
"symbol-daytime-303": "lt_min",
"symbol-daytime-30500001": "lt_lead_a",
"symbol-daytime-30500002": "lt_lead_b",
"symbol-daytime-30500003": "lt_lead_c",
"symbol-daytime-30700001": "lt_up_G",
"symbol-daytime-30700002": "lt_up_R",
"symbol-daytime-30700003": "mk_lead_c",
"symbol-daytime-308": "mk_lead_v",
"symbol-daytime-310": "lt_bcn",
"symbol-daytime-320": "bu_lit",
"symbol-daytime-321": "bu_gen",
"symbol-daytime-323": "bu_pil",
"symbol-daytime-325": "bu_can",
"symbol-daytime-327": "bu_lat",
"symbol-daytime-335359": "mk_vais",
"symbol-daytime-401": "hz_rock_awash",
"symbol-daytime-404": "hz_rock_sub",
"symbol-daytime-405": "hz_danger_clear",
"symbol-daytime-409": "hz_danger_iso",
"symbol-daytime-410": "hz_wreck_hull",
"symbol-daytime-412": "hz_wreck_sub",
"symbol-daytime-413": "hz_wreck_survey",
"symbol-daytime-420": "hz_foul",
"symbol-daytime-421": "hz_sandwave",
"symbol-daytime-422": "hz_overfall",
"symbol-daytime-424": "hz_whirl",
"symbol-daytime-425": "hz_seaweed",
"symbol-daytime-427": "hz_obst",
"symbol-daytime-428": "hz_reef",
"symbol-daytime-429": "hz_reef_danger",
"symbol-daytime-431": "hz_tower",
"symbol-daytime-432": "hz_pile",
"symbol-daytime-433": "hz_dolphin",
"symbol-daytime-434": "hz_outfall",
"symbol-daytime-640": "lm_chim",
"symbol-daytime-650": "lm_twr",
"symbol-daytime-660": "lm_mar",
"symbol-daytime-661": "lm_fish",
"symbol-daytime-662": "lm_cus",
"symbol-daytime-680": "lm_prom",
"symbol-daytime-681": "lm_mtn",
"symbol-daytime-682": "lm_ctrl",
"symbol-daytime-698": "lm_mon",
"symbol-daytime-719": "an_quar",
"symbol-daytime-720": "an_desig",
"symbol-daytime-721": "an_restrict",
"symbol-daytime-724": "an_no_anchor",
"symbol-daytime-505": "fac_port",
"symbol-daytime-520": "fac_fishport",
"symbol-daytime-530": "fac_marina",
"symbol-daytime-540": "fac_fisherina",
"symbol-daytime-550": "fac_seastation",
"harbor_lighthouse": "lt_hbr",
"breakwater_lighthouse": "lt_bkw",
"coastal_lighthouse": "lt_cst",
"light_minor": "lt_min",
"leading_light_variant_30500001": "lt_lead_a",
"leading_light_variantt_30500001": "lt_lead_a",
"leading_light_variant_30500002": "lt_lead_b",
"leading_light_variantt_30500002": "lt_lead_b",
"leading_light_variant_30500003": "lt_lead_c",
"leading_light_variantt_30500003": "lt_lead_c",
"leading_mark_variant_30700003": "mk_lead_c",
"leading_mark_variantt_30700003": "mk_lead_c",
"leading_mark_variant_308": "mk_lead_v",
"leading_mark_variantt_308": "mk_lead_v",
"light_beacon": "lt_bcn",
"buoy_light": "bu_lit",
"buoy_generic": "bu_gen",
"buoy_pillar": "bu_pil",
"buoy_can": "bu_can",
"buoy_lattice": "bu_lat",
"nav_mark_vais": "mk_vais",
"pilot_station": "fac_pilot",
"tidespot-daytime": "tide_spot",
"light-daytime-1": "fl_G",
"light-daytime-2": "fl_R",
"light-daytime-3": "fl_YW",
"arc-daytime-04027289": "arc_open_YW",
"arc-daytime-m1": "arc_G",
"arc-daytime-m2": "arc_R",
"arc-daytime-m3": "arc_YW",
"rock_exposed_drying_awash_group": "hz_rock_awash",
"sunken_rock": "hz_rock_sub",
"cleared_danger_object": "hz_danger_clear",
"isolated_danger": "hz_danger_iso",
"wreck_hull_exposed": "hz_wreck_hull",
"wreck_fully_submerged_dangerous": "hz_wreck_sub",
"wreck_surveyed": "hz_wreck_survey",
"foul_ground": "hz_foul",
"sand_wave": "hz_sandwave",
"tide_rips_overfalls": "hz_overfall",
"eddy_whirlpool": "hz_whirl",
"seaweed": "hz_seaweed",
"obstruction": "hz_obst",
"fish_reef": "hz_reef",
"fish_reef_dangerous": "hz_reef_danger",
"tower_yagura_observation_platform": "hz_tower",
"bollard_pile_stake": "hz_pile",
"dolphin_structure": "hz_dolphin",
"subsea_installation_outfall_intake": "hz_outfall",
"anchorage_quarantine": "an_quar",
"anchorage_designated": "an_desig",
"restriction_area_group": "an_restrict",
"anchoring_prohibited": "an_no_anchor",
"port_general_small_harbor_group": "fac_port",
"fishing_port": "fac_fishport",
"marina": "fac_marina",
"fisherina": "fac_fisherina",
"sea_station_umi_no_eki": "fac_seastation"
},
"hazard_class_map": {
"401": "hz_rock_awash",
"402": "hz_rock_awash",
"403": "hz_rock_awash",
"404": "hz_rock_sub",
"405": "hz_danger_clear",
"409": "hz_danger_iso",
"410": "hz_wreck_hull",
"412": "hz_wreck_sub",
"413": "hz_wreck_survey",
"415": "hz_wreck_survey",
"420": "hz_foul",
"421": "hz_sandwave",
"422": "hz_overfall",
"424": "hz_whirl",
"425": "hz_seaweed",
"427": "hz_obst",
"428": "hz_reef",
"429": "hz_reef_danger",
"431": "hz_tower",
"432": "hz_pile",
"433": "hz_dolphin",
"434": "hz_outfall"
},
"landmark_class_map": {
"640": "lm_chim",
"650": "lm_twr",
"660": "lm_mar",
"661": "lm_fish",
"662": "lm_cus",
"680": "lm_prom",
"681": "lm_mtn",
"682": "lm_ctrl",
"698": "lm_mon"
},
"anchorage_class_map": {
"719": "an_quar",
"720": "an_desig",
"721": "an_restrict",
"722": "an_restrict",
"723": "an_restrict",
"724": "an_no_anchor"
},
"facility_class_map": {
"505": "fac_port",
"510": "fac_port",
"520": "fac_fishport",
"530": "fac_marina",
"540": "fac_fisherina",
"550": "fac_seastation"
},
"display_code_map": {
"30300000": "lt_min",
"30300002": "lt_min",
"30300004": "lt_min",
"30500001": "lt_lead_a",
"30500002": "lt_lead_b",
"30500003": "lt_lead_c",
"30500004": "lt_lead_c",
"30500010": "lt_lead_c",
"30600000": "lt_min",
"30600001": "lt_min",
"30600002": "lt_min",
"30600003": "lt_min",
"30600004": "lt_min",
"30600006": "lt_min",
"30700000": "mk_lead_c",
"30700001": "lt_up_G",
"30700002": "lt_up_R",
"30700003": "mk_lead_c",
"30700004": "mk_lead_c",
"30700007": "mk_lead_c",
"30800000": "mk_lead_v",
"30900000": "lt_lead_c",
"30900002": "lt_lead_b",
"30900004": "lt_lead_c",
"30900009": "lt_lead_c",
"30900011": "lt_lead_c"
},
"arc_rules": {
"sector": "arc_open_YW",
"green": "arc_G",
"red": "arc_R",
"other": "arc_YW"
},
"fallbacks": {
"lm_cus": {
"source_sprite_key": "symbol-daytime-660",
"status": "temporary_visual_fallback",
"reason": "当前 sprite 没有 symbol-daytime-662 原图"
}
}
}