Files
pbf/tools/style-visual-object/build_reference_names.py

462 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""生成 CM95 / NavSea Visual Object Reviewer 使用的中文名称补充表。
注意:
- 该文件只用于 UI 显示和人工理解;
- 不参与 Visual Object 匹配、聚类、Layer 判定;
- CM95 英文说明来自当前 CM95 仓库中的 S-52 metadata;
- 中文名称为工程显示译名,不冒充 IHO 官方中文译名。
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parent
OUTPUT = ROOT / "output"
CM95_ROOT = Path("/root/sourceserver/cm95")
LAYER_ZH = {
"anchor_caution_hazard_outline": "锚泊注意/危险区轮廓",
"anchor_caution_hazard_point": "锚泊注意/危险点",
"anchorage_area": "锚地面",
"anchorage_outline": "锚地轮廓",
"anchorage_point": "锚地点",
"baseline_area": "基础区域",
"baseline_line": "基础线",
"baseline_outline": "基础区域轮廓",
"bathymetry_line": "海底地形线",
"bridge_structure": "桥梁结构",
"clearance_limit_line": "净空限制线",
"clearance_limit_point": "净空限制点",
"clip_outline_754": "特殊边界线",
"depth_contour": "等深线",
"depth_zone_739": "水深分区 739",
"depth_zone_741": "水深分区 741",
"facility_boundary_outline": "设施边界轮廓",
"facility_boundary_point": "港口/设施点",
"fixed_fishing_gear_area": "定置渔具区域",
"hazard_boundary_outline": "危险边界轮廓",
"hole_area": "孔洞/挖空区域",
"land_area": "陆地区域",
"navigation_hazard_outline": "航行危险轮廓",
"navigation_hazard_point": "航行危险点",
"navigation_marks": "航路标识",
"onshore_structure_area": "陆上结构物面",
"onshore_structure_line": "陆上结构物线",
"onshore_structure_point": "陆上结构物点",
"pilot_station_point": "引航站",
"place_label_land": "陆地地名",
"place_label_sea": "海域地名",
"seabed_line": "海底对象线",
"seabed_text_point": "海底文字点",
"submerged_reef_area": "水下礁区域",
"tide": "潮汐",
}
STD_KEY_ZH = {
"tidespot-daytime": "潮汐点",
"pattern_fill_fishery_or_fish_reef_area": "定置渔业/鱼礁填充",
"special_pattern_line_754": "特殊边界线",
"bollard_pile_stake": "系缆柱、桩、杭",
"cleared_danger_object": "扫海済危险物",
"dolphin_structure": "系泊墩",
"eddy_whirlpool": "涡流",
"fish_reef": "鱼礁",
"fish_reef_dangerous": "危险鱼礁",
"foul_ground": "碍锚海底",
"isolated_danger": "孤立危险物",
"obstruction": "障碍物",
"rock_exposed_drying_awash_group": "露出岩/干出岩/洗岩",
"sand_wave": "沙波",
"seaweed": "海草",
"subsea_installation_outfall_intake": "海底设施/放水口/取水口",
"sunken_rock": "暗岩",
"tide_rips_overfalls": "急潮/波纹",
"tower_yagura_observation_platform": "塔/架台/测台",
"wreck_fully_submerged_dangerous": "危险全沉船",
"wreck_hull_exposed": "船体或桅杆露出沉船",
"wreck_surveyed": "测定済沉船",
"anchorage_designated": "指定锚地",
"anchorage_quarantine": "检疫锚地",
"anchoring_prohibited": "禁止锚泊区",
"restriction_area_group": "限制区域",
"light_flare_green": "绿色灯光",
"light_flare_red": "红色灯光",
"light_flare_yellow_white": "黄色/白色灯光",
"harbor_lighthouse": "港湾灯塔",
"breakwater_lighthouse": "防波堤灯塔",
"coastal_lighthouse": "沿岸灯塔",
"light_minor": "小型灯标",
"light_beacon": "灯标",
"light_beacon_east_cardinal_variantt": "东方位灯标",
"light_beacon_isolated_danger_variantt_311359": "孤立危险标识灯标",
"light_beacon_north_cardinal_variantt": "北方位灯标",
"light_beacon_north_cardinal_variantt_311356": "北方位灯标",
"light_beacon_safe_water_variantt_311360": "安全水域标识灯标",
"light_beacon_south_cardinal_variantt": "南方位灯标",
"light_beacon_west_cardinal_variantt_311358": "西方位灯标",
"buoy_can": "罐形浮标",
"buoy_east_cardinal": "东方位浮标",
"buoy_isolated_danger": "孤立危险浮标",
"buoy_lattice": "架形浮标",
"buoy_north_cardinal": "北方位浮标",
"buoy_pillar": "柱形浮标",
"buoy_port_lateral_variantt_312350": "左舷浮标",
"buoy_safe_water": "安全水域浮标",
"buoy_south_cardinal": "南方位浮标",
"buoy_special_mark": "特殊标识浮标",
"buoy_starboard_lateral_variantt_312351": "右舷浮标",
"buoy_west_cardinal": "西方位浮标",
"mooring_buoy": "系泊浮标",
"fisherina": "渔港休闲设施",
"fishing_port": "渔港",
"marina": "游艇码头",
"port_general_small_harbor_group": "一般港湾/小港湾",
"sea_station_umi_no_eki": "海之驿",
"pilot_station": "引航站",
"symbol-daytime-401": "露出岩/干出岩/洗岩",
"symbol-daytime-404": "暗岩",
"symbol-daytime-405": "扫海済危险物",
"symbol-daytime-409": "孤立危险物",
"symbol-daytime-410": "船体或桅杆露出沉船",
"symbol-daytime-412": "危险全沉船",
"symbol-daytime-413": "测定済沉船",
"symbol-daytime-420": "碍锚海底",
"symbol-daytime-421": "沙波",
"symbol-daytime-422": "急潮/波纹",
"symbol-daytime-424": "涡流",
"symbol-daytime-425": "海草",
"symbol-daytime-427": "障碍物",
"symbol-daytime-428": "鱼礁",
"symbol-daytime-429": "危险鱼礁",
"symbol-daytime-431": "塔/架台/测台",
"symbol-daytime-432": "系缆柱、桩、杭",
"symbol-daytime-433": "系泊墩",
"symbol-daytime-434": "海底设施/放水口/取水口",
"symbol-daytime-640": "烟囱",
"symbol-daytime-650": "陆上显著结构物",
"symbol-daytime-660": "陆上显著结构物",
"symbol-daytime-661": "陆上显著结构物",
"symbol-daytime-681": "陆上显著结构物",
"symbol-daytime-698": "其他陆上结构物",
}
def color_zh(text: str) -> str:
replacements = [
("black/yellow/black", "黑/黄/黑"),
("yellow/black/yellow", "黄/黑/黄"),
("black/red/black", "黑/红/黑"),
("green/white", "绿/白"),
("red/white", "红/白"),
("black/yellow", "黑/黄"),
("yellow/black", "黄/黑"),
("white/black", "白/黑"),
("black/white", "黑/白"),
("white/red", "白/红"),
("red/green", "红/绿"),
("white or yellow", "白色或黄色"),
("yellow", "黄色"),
("green", "绿色"),
("red", "红色"),
("white", "白色"),
("black", "黑色"),
]
out = text
for en, zh in replacements:
out = out.replace(en, zh)
return out
def s52_zh(key: str, desc: str) -> tuple[str, str]:
"""返回 (中文显示名, 来源说明)。"""
d = desc.strip().lower()
if not d:
fallback = {
"STARPT01": "星形点标志",
"TOPMA115": "顶标",
"TOPMAR01": "浮标顶标",
}.get(key, "未提供说明")
return fallback, "工程显示译名"
exact = {
"anchorage area": "锚地",
"designated anchor berth for a single vessel": "指定单船锚泊位",
"symbol for opening bridge": "开启桥",
"berth number symbol": "泊位编号",
"built-up area": "建成区",
"coastguard station": "海岸警卫站",
"chimney": "烟囱",
"conspicuous chimney": "显著烟囱",
"ho information note": "海图机构信息注记",
"flare stack": "火炬塔",
"flagstaff, flagpole": "旗杆",
"fog signal": "雾号",
"fortified structure": "防御工事",
"fish haven": "鱼礁/鱼类栖息设施",
"fishing harbour": "渔港",
"light flare": "灯光标志",
"floodlight": "泛光灯",
"fish farm": "养殖场",
"mooring dolphin": "系泊墩",
"mast": "桅杆",
"offshore platform": "海上平台",
"pilot boarding place": "引航员登轮点",
"pile or bollard": "桩/系缆柱",
"traffic precautionary area": "通航注意区",
"radar reflector": "雷达反射器",
"radio station": "无线电台",
"radar transponder beacon": "雷达应答标",
"yacht harbour, marina": "游艇码头",
"sand waves": "沙波",
"traffic crossing area": "交通交叉区",
"traffic roundabout": "交通环岛",
"weed, kelp": "海草/海带",
"windmotor": "风车",
"submarine cable": "海底电缆",
"ferry route": "渡轮航线",
"fishing stakes": "定置渔桩",
"area with minor restrictions or information notices": "轻微限制/信息提示区域",
"beacon in general, black/yellow/back, full-chart": "一般立标(黑/黄/黑,完整显示)",
"beacon in general, yellow/back/yellow, full-chart": "一般立标(黄/黑/黄,完整显示)",
"beacon in general, black/red/back, full-chart": "一般立标(黑/红/黑,完整显示)",
"conical buoy,lateral-green,full-chart": "锥形侧面浮标(绿色,完整显示)",
"mooring buoy, can shape, paper-chart": "罐形系泊浮标(海图符号)",
"pillar buoy,cardianl-yby,full-chart": "柱形方位浮标(黄/黑/黄,完整显示)",
"topmark for buoys, cone point up, paper-chart": "浮标顶标:单锥尖向上",
"topmark for buoys, 2 cones point upward, paper-chart": "浮标顶标:双锥尖向上",
"topmark for buoys, 2 cones point downward, paper-chart": "浮标顶标:双锥尖向下",
"topmark for buoys, 2 cones base to base, paper-chart": "浮标顶标:双锥底相对",
"topmark for buoys, 2 cones point to point, paper-chart": "浮标顶标:双锥尖相对",
"topmark for buoys, sphere, paper-chart": "浮标顶标:球形",
"topmark for buoys, 2 spheres, paper-chart": "浮标顶标:双球",
"topmark for buoys, cylinder, paper-chart": "浮标顶标:圆柱形",
"beacon top mark, red boarded square board, vertical": "立标顶标:红边方形板(竖向)",
}
if d in exact:
return exact[d], "S-52 英文说明的中文显示译名"
if d.startswith("anchorage area as a point"):
return "锚地点", "S-52 英文说明的中文显示译名"
if d.startswith("beacon in general"):
suffix = d.split(",", 1)[1].strip() if "," in d else ""
return "一般立标" + (f"({color_zh(suffix.replace('full-chart','完整显示').replace('paper-chart','海图符号'))})" if suffix else ""), "S-52 英文说明的中文显示译名"
if "spar beacon" in d:
return color_zh(d.replace("spar beacon", "杆形立标").replace("full-chart", "完整显示")), "S-52 英文说明的中文显示译名"
if "stake or pole beacon" in d:
return color_zh(d.replace("minor, ", "小型").replace("stake or pole beacon", "桩/杆形立标").replace("full-chart", "完整显示")), "S-52 英文说明的中文显示译名"
if d.startswith("beacon tower"):
return color_zh(d.replace("beacon tower", "塔形立标").replace("full-chart", "完整显示").replace("brb", "黑/红/黑")), "S-52 英文说明的中文显示译名"
if "buoy" in d:
out = d
out = out.replace("can buoy", "罐形浮标").replace("conical buoy", "锥形浮标")
out = out.replace("mooring buoy", "系泊浮标").replace("pillar buoy", "柱形浮标")
out = out.replace("spherical buoy", "球形浮标").replace("spar buoy", "杆形浮标")
out = out.replace("super-buoy", "特大型浮标").replace("super buoy", "特大型浮标")
out = out.replace("default symbol for buoy", "一般浮标")
out = out.replace("paper-chart", "海图符号").replace("full-chart", "完整显示")
out = out.replace("lateral-r", "侧面标识-红").replace("lateral-g", "侧面标识-绿")
out = out.replace("special purpose-y", "特殊用途-黄")
out = out.replace("cardinal-byb", "方位标-黑/黄/黑").replace("cardinal-yby", "方位标-黄/黑/黄")
out = out.replace("cardinal-by", "方位标-黑/黄").replace("cardinal-yb", "方位标-黄/黑")
out = out.replace("isol.danger-brb", "孤立危险标-黑/红/黑").replace("safe water-rw", "安全水域标-红/白")
out = color_zh(out)
return out, "S-52 英文说明的中文显示译名"
if d.startswith("caution area"):
return "注意区域", "S-52 英文说明的中文显示译名"
if d.startswith("cautionary area"):
return "谨慎航行区域", "S-52 英文说明的中文显示译名"
if d.startswith("foul area of seabed"):
return "碍锚海底(航行安全但不宜锚泊)", "S-52 英文说明的中文显示译名"
if d.startswith("fish trap"):
return "渔具/鱼栅/金枪鱼网", "S-52 英文说明的中文显示译名"
if d.startswith("navigable lock gate"):
return "可通航闸门", "S-52 英文说明的中文显示译名"
if d.startswith("non-navigable lock gate"):
return "不可通航闸门", "S-52 英文说明的中文显示译名"
if d.startswith("light flare"):
return color_zh(d.replace("light flare", "灯光标志")), "S-52 英文说明的中文显示译名"
if d.startswith("light float"):
return "灯浮体", "S-52 英文说明的中文显示译名"
if d.startswith("light vessel"):
return color_zh(d.replace("light vessel", "灯船").replace("full-chart", "完整显示")), "S-52 英文说明的中文显示译名"
if d.startswith("cursor pick site for a magnetic anomaly"):
return "磁异常位置", "S-52 英文说明的中文显示译名"
if d.startswith("point feature or area of low accuracy"):
return "低位置精度对象/区域", "S-52 英文说明的中文显示译名"
if d.startswith("position of an elevation or control point"):
return "高程点/控制点", "S-52 英文说明的中文显示译名"
if d.startswith("object which is not sufficiently described"):
return "未充分描述/无对应符号的对象", "S-52 英文说明的中文显示译名"
if d.startswith("symbol indicating this object is radar conspicuous"):
return "雷达显著物标志", "S-52 英文说明的中文显示译名"
if d.startswith("recommended route"):
return "推荐航路", "S-52 英文说明的中文显示译名"
if d.startswith("topmark for buoys"):
tail = d.split(",", 1)[1].strip() if "," in d else ""
trans = tail.replace("2 cones point upward", "双锥尖向上").replace("2 cones point downward", "双锥尖向下")
trans = trans.replace("2 cones base to base", "双锥底相对").replace("2 cones point to point", "双锥尖相对")
trans = trans.replace("cone point up", "单锥尖向上").replace("2 spheres", "双球").replace("sphere", "球形").replace("cylinder", "圆柱形")
trans = trans.replace("paper-chart", "海图符号")
return f"浮标顶标:{trans}", "S-52 英文说明的中文显示译名"
if d.startswith("topmark for beacons"):
tail = d.split(",", 1)[1].strip() if "," in d else ""
trans = tail.replace("2 cones point upward", "双锥尖向上").replace("2 cones point downward", "双锥尖向下")
trans = trans.replace("2 cones base to base", "双锥底相对").replace("2 cones point to point", "双锥尖相对")
trans = trans.replace("cone point up", "单锥尖向上").replace("2 spheres", "双球").replace("sphere", "球形").replace("cylinder", "圆柱形")
trans = trans.replace("paper-chart", "海图符号")
return f"立标顶标:{trans}", "S-52 英文说明的中文显示译名"
if d.startswith("beacon top mark"):
return color_zh(d.replace("beacon top mark", "立标顶标").replace("square board", "方形板").replace("diagonal", "对角").replace("vertical", "竖向")), "S-52 英文说明的中文显示译名"
if d.startswith("tower"):
return color_zh(d.replace("tower", "塔").replace("full-chart and simplified", "完整/简化显示")), "S-52 英文说明的中文显示译名"
if d == "water tower":
return "水塔", "S-52 英文说明的中文显示译名"
if d == "conspicuous tower":
return "显著塔", "S-52 英文说明的中文显示译名"
if d == "radio, television tower":
return "无线电/电视塔", "S-52 英文说明的中文显示译名"
if d == "conspicuous radio, television tower":
return "显著无线电/电视塔", "S-52 英文说明的中文显示译名"
if d.startswith("traffic direction"):
return "分道通航单向航道交通方向", "S-52 英文说明的中文显示译名"
if d.startswith("overfalls, eddies and breakers"):
return "急流、涡流和碎波", "S-52 英文说明的中文显示译名"
if d.startswith("pattern of symbols for a fish haven"):
return "鱼礁填充图案", "S-52 英文说明的中文显示译名"
if d.startswith("pattern of symbols for a marine farm"):
return "养殖场填充图案", "S-52 英文说明的中文显示译名"
if d.startswith("pattern of symbols for a marsh"):
return "沼泽填充图案", "S-52 英文说明的中文显示译名"
if d.startswith("incompletely surveyed area"):
return "未完全测量区域", "S-52 英文说明的中文显示译名"
if d.startswith("pattern of symbols for an area which is not sufficiently described"):
return "未充分描述区域填充图案", "S-52 英文说明的中文显示译名"
if d.startswith("precautionary area or a traffic separation scheme"):
return "通航注意区/分道通航交叉或环岛", "S-52 英文说明的中文显示译名"
if d.startswith("pattern of symbols for wooded areas"):
return "林地区域填充图案", "S-52 英文说明的中文显示译名"
if d == "essa or pssa boundary":
return "特别敏感海域/环境敏感海域边界", "S-52 英文说明的中文显示译名"
if d.startswith("safety contour of low accuracy"):
return "低位置精度安全等深线", "S-52 英文说明的中文显示译名"
if d.startswith("coastline or shoreline construction of low accuracy"):
return "低位置精度海岸线/岸线构筑物", "S-52 英文说明的中文显示译名"
if d.startswith("oil, gas pipeline"):
return "油气管线(海底或陆上)", "S-52 英文说明的中文显示译名"
if d.startswith("water pipeline"):
return "水管/下水管等", "S-52 英文说明的中文显示译名"
if d.startswith("non-regulated recommended two-way track"):
return "非管制推荐双向航迹", "S-52 英文说明的中文显示译名"
if d.startswith("non-regulated recommended one-way track"):
return "非管制推荐单向航迹", "S-52 英文说明的中文显示译名"
# 兜底:避免伪造精确术语。仍展示英文原说明。
return "待人工确认中文名", "尚未建立可靠中文显示译名"
def build_cm95_names() -> dict[str, Any]:
cm = json.loads((OUTPUT / "cm95-anonymous" / "anonymous-catalog.json").read_text(encoding="utf-8"))
keys = sorted({o["resource_key"] for o in cm["visual_objects"] if o.get("resource_key")})
base = json.loads((CM95_ROOT / "build/s52_resources_v2/base/s52_symbol_metadata.json").read_text(encoding="utf-8"))
ap_raw = json.loads((CM95_ROOT / "build/s52_resources_v2/ap/s52-sprite-ap-pattern-metadata.json").read_text(encoding="utf-8"))
lc_raw = json.loads((CM95_ROOT / "build/s52_resources_v2/final/s52-sprite-full-line-metadata.json").read_text(encoding="utf-8"))
ap = {v.get("sprite_key"): v for v in ap_raw.values()}
lc = {v.get("sprite_key"): v for v in lc_raw.values()}
out: dict[str, Any] = {}
for key in keys:
rec = base.get(key) or ap.get(key) or lc.get(key)
source_type = "symbol" if key in base else ("fill-pattern" if key in ap else ("line-pattern" if key in lc else "unknown"))
desc = str((rec or {}).get("description") or "")
if not desc:
# 两个基础图标可从其同名 pattern metadata 得到安全的对象级概念。
if key == "FSHHAV02":
desc = "fish haven"
elif key == "MARSHES1":
desc = "marsh"
elif key == "CROSSX01":
desc = ""
zh, zh_source = s52_zh(key, desc)
if key == "MARSHES1":
zh, zh_source = "沼泽", "由同名 S-52 填充图案说明补充"
elif key == "FSHHAV02":
zh, zh_source = "鱼礁/鱼类栖息设施", "由同名 S-52 填充图案说明补充"
elif key == "CROSSX01":
zh, zh_source = "交叉标志(待确认)", "S-52 metadata 未找到说明,名称仅作 UI 提示"
out[key] = {
"code": key,
"resource_type": source_type,
"official_description_en": desc or None,
"display_name_zh": zh,
"display_name_source": zh_source,
}
return out
def std_name(key: str, layer: str) -> dict[str, Any]:
zh = STD_KEY_ZH.get(key)
source = "NavSea 标准资源语义键中文显示译名"
if not zh:
if key.startswith("leading_light"):
zh = "导灯"
elif key.startswith("leading_mark"):
zh = "导标"
elif key.startswith("arc-daytime"):
zh = "灯弧"
elif key.startswith("symbol-daytime"):
zh = LAYER_ZH.get(layer, "标准视觉对象")
source = "按标准 Layer 显示,具体对象名待补"
else:
zh = LAYER_ZH.get(layer, "标准视觉对象")
source = "按标准 Layer 显示,具体对象名待补"
return {
"resource_key": key,
"standard_layer": layer,
"standard_layer_zh": LAYER_ZH.get(layer, layer),
"display_name_zh": zh,
"display_name_source": source,
}
def build_std_names() -> dict[str, Any]:
std = json.loads((OUTPUT / "navsea-standard" / "standard-catalog.json").read_text(encoding="utf-8"))
out: dict[str, Any] = {}
for obj in std["visual_objects"]:
key = obj.get("resource_key")
layer = obj.get("standard_layer_name")
if not key or not layer:
continue
out[key] = std_name(key, layer)
return out
def main() -> None:
result = {
"schema": "visual-object-reference-names/v1",
"policy": {
"ui_only": True,
"used_for_matching": False,
"used_for_layer_assignment": False,
"chinese_names_are_display_translations": True,
},
"cm95": build_cm95_names(),
"navsea_standard": build_std_names(),
"standard_layer_zh": LAYER_ZH,
}
path = OUTPUT / "reference-name-enrichment-v1.json"
path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(path)
print("CM95", len(result["cm95"]))
print("NavSea Standard", len(result["navsea_standard"]))
print("CM95 待人工确认中文名", sum(x["display_name_zh"] == "待人工确认中文名" for x in result["cm95"].values()))
if __name__ == "__main__":
main()