fix: align local S-52 CSP decisions with OpenCPN

This commit is contained in:
OpenAI Codex
2026-09-21 05:54:36 +08:00
parent 716acba74d
commit f3047a3f06

View File

@@ -145,13 +145,14 @@ def _optional_float(value: Any) -> float | None:
return None
def _light_color_ref(colours: list[int]) -> str:
def _light_color_ref(colours: list[int], *, sector: bool = False) -> str:
if len(colours) == 1:
if 3 in colours:
return "LITRD"
if 4 in colours:
return "LITGN"
if any(code in colours for code in (1, 6, 11)):
# OpenCPN uses orange (11) for sectors, amber (9) for flares/circles.
if any(code in colours for code in (1, 6, 11 if sector else 9)):
return "LITYW"
return "CHMGD"
if len(colours) == 2:
@@ -193,7 +194,7 @@ _LIGHT_CHARACTER = {
}
def _light_description(attrs: dict[str, Any], *, has_sector: bool) -> str:
def _light_description(attrs: dict[str, Any]) -> str:
try:
litchr = int(attrs.get("LITCHR"))
except (TypeError, ValueError):
@@ -201,18 +202,18 @@ def _light_description(attrs: dict[str, Any], *, has_sector: bool) -> str:
text = _LIGHT_CHARACTER.get(litchr, "")
two_groups = litchr in {13, 14, 15, 16, 25, 26, 27, 29}
siggrp = str(attrs.get("SIGGRP") or "").strip()
if siggrp:
groups = [part for part in siggrp.replace(")(", ",").strip("()").split(",") if part]
if two_groups and groups:
first = groups[0]
if " + " in text:
left, right = text.split(" + ", 1)
text = f"{left}({first}) + {right}"
if len(groups) > 1 and groups[1] != "1":
text += f"({groups[1]})"
elif siggrp != "(1)":
text += siggrp
if two_groups:
groups = [part for part in siggrp.replace("(", ")").split(")") if part]
first = groups[0][0] if groups else "?"
left, right = text.split(" + ", 1)
text = f"{left}({first}) + {right}"
if len(groups) > 1 and groups[1] != "1":
text += f"({groups[1]})"
elif siggrp and siggrp != "(1)":
text += siggrp
# Description suppression depends on SECTR1 presence, not sector validity.
has_sector = "SECTR1" in attrs
if not has_sector:
colour_codes = {1: "W", 3: "R", 4: "G", 6: "Y"}
colours = "".join(colour_codes.get(code, "") for code in _int_list(attrs.get("COLOUR")))
@@ -243,7 +244,7 @@ def resolve_lights05_instruction(attrs: dict[str, Any]) -> tuple[str, dict[str,
sectr1 = _optional_float(attrs.get("SECTR1"))
sectr2 = _optional_float(attrs.get("SECTR2"))
has_sector = sectr1 is not None and sectr2 is not None
has_sector = sectr1 not in (None, -9.0) and sectr2 not in (None, -9.0)
colours = _int_list(attrs.get("COLOUR")) or [12]
extra: dict[str, Any] = {
"s52_light_nominal_range_nm": float(valnmr),
@@ -266,7 +267,7 @@ def resolve_lights05_instruction(attrs: dict[str, Any]) -> tuple[str, dict[str,
else:
radius = _light_radius_mm(valnmr)
color = _light_color_ref(colours)
radius_adjust = {"LITRD": 1.0, "LITGN": 0.0, "LITYW": 2.0, "CHMGD": 3.0}.get(color, 5.0)
radius_adjust = {"LITRD": 1.0, "LITGN": 0.0, "LITYW": 2.0}.get(color, 3.0 if colours == [12] else 5.0)
instruction = f"CA(OUTLW,4,{color},2,0,360,{radius + radius_adjust:.1f},0)"
extra["s52_light_mode"] = "all_round"
else:
@@ -275,12 +276,12 @@ def resolve_lights05_instruction(attrs: dict[str, Any]) -> tuple[str, dict[str,
if sweep < 1.0:
radius = _light_radius_mm(valnmr)
color = _light_color_ref(colours)
radius_adjust = {"LITRD": 1.0, "LITGN": 0.0, "LITYW": 2.0, "CHMGD": 3.0}.get(color, 5.0)
radius_adjust = {"LITRD": 1.0, "LITGN": 0.0, "LITYW": 2.0}.get(color, 3.0 if colours == [12] else 5.0)
instruction = f"CA(OUTLW,4,{color},2,0,360,{radius + radius_adjust:.1f},0)"
extra["s52_light_mode"] = "all_round"
else:
arc_radius = 10.0 if 0 < valnmr < 15.0 else 15.0 if valnmr < 30.0 else 20.0
color = _light_color_ref(colours)
arc_radius = 10.0 if 0 < valnmr < 15.0 else 15.0 if 15.0 <= valnmr < 30.0 else 20.0
color = _light_color_ref(colours, sector=True)
outline_color, outline_width, arc_width = "OUTLW", 4, 2
litvis = _int_list(attrs.get("LITVIS"))
if any(code in litvis for code in (3, 7, 8)):
@@ -298,7 +299,7 @@ def resolve_lights05_instruction(attrs: dict[str, Any]) -> tuple[str, dict[str,
"s52_sector_sweep_deg": float(sweep),
})
description = _light_description(attrs, has_sector=has_sector)
description = _light_description(attrs)
if description:
safe = description.replace("'", "’")
instruction += f";TX('{safe}',3,2,3,'15110',2,0,CHBLK,23)"
@@ -760,10 +761,16 @@ def resolve_wrecks02_instruction(
quapos = _int_attr(attrs, "QUAPOS", 0)
if 2 <= quapos < 10:
line = "LC(LOWACC41)"
elif udw or valsou is None or valsou <= 20.0:
line = "LS(DOTT,2,CHBLK)" if udw or valsou is not None else "LS(DOTT,2,CSTLN)"
else:
elif udw or (valsou is not None and valsou <= 20.0):
line = "LS(DOTT,2,CHBLK)"
elif valsou is not None:
line = "LS(DASH,2,CHBLK)"
elif watlev in {1, 2}:
line = "LS(SOLD,2,CSTLN)"
elif watlev == 4:
line = "LS(DASH,2,CSTLN)"
else:
line = "LS(DOTT,2,CSTLN)"
instruction = line
if valsou is not None and valsou <= 20.0:
instruction += ";" + udw if udw else ""
@@ -836,8 +843,9 @@ def resolve_slcons03_instruction(
if geom == "AREA":
instructions.append("AP(CROSSX01)")
if quapos_present and 2 <= quapos < 10:
instructions.append("LC(LOWACC21)")
if quapos_present:
if 2 <= quapos < 10:
instructions.append("LC(LOWACC01)")
return ";".join(instructions)
condtn = _int_attr(attrs, "CONDTN", 0)
@@ -860,13 +868,13 @@ def resolve_restrn01_instruction(attrs: dict[str, Any]) -> str:
if not restrn:
return ""
if restrn & {7, 8, 14}:
symbol = "ENTRES61" if restrn & {1, 2, 3, 4, 5, 6} else "ENTRES71" if restrn & {9, 10, 11, 12, 13, 14, 15} else "ENTRES51"
symbol = "ENTRES61" if restrn & {1, 2, 3, 4, 5, 6} else "ENTRES71" if restrn & {9, 10, 11, 12, 13} else "ENTRES51"
elif restrn & {1, 2}:
symbol = "ACHRES61" if restrn & {3, 4, 5, 6} else "ACHRES71" if restrn & {9, 10, 11, 12, 13, 14, 15} else "ACHRES51"
symbol = "ACHRES61" if restrn & {3, 4, 5, 6} else "ACHRES71" if restrn & {9, 10, 11, 12, 13} else "ACHRES51"
elif restrn & {3, 4, 5, 6}:
symbol = "FSHRES71" if restrn & {9, 10, 11, 12, 13, 14, 15} else "FSHRES51"
symbol = "FSHRES71" if restrn & {9, 10, 11, 12, 13} else "FSHRES51"
else:
symbol = "INFARE51" if restrn & {9, 10, 11, 12, 13, 14, 15} else "RSRDEF51"
symbol = "INFARE51" if restrn & {9, 10, 11, 12, 13} else "RSRDEF51"
return f"SY({symbol})"
@@ -878,18 +886,18 @@ def resolve_resare02_instruction(
group_a = {1, 8, 9, 12, 14, 19, 21, 25}
group_b = {4, 5, 6, 7, 10, 18, 20, 22, 23, 24}
priority = False
if restrn:
if "RESTRN" in attrs:
if restrn & {7, 8, 14}:
symbol = "ENTRES61" if (restrn & {1,2,3,4,5,6} or catrea & group_a) else "ENTRES71" if (restrn & {9,10,11,12,13,14,15} or catrea & group_b) else "ENTRES51"
symbol = "ENTRES61" if (restrn & {1,2,3,4,5,6} or catrea & group_a) else "ENTRES71" if (restrn & {9,10,11,12,13} or catrea & group_b) else "ENTRES51"
line_code = "RESARE51"; priority = True
elif restrn & {1, 2}:
symbol = "ACHRES61" if (restrn & {3,4,5,6} or catrea & group_a) else "ACHRES71" if (restrn & {9,10,11,12,13,14,15} or catrea & group_b) else "RESTRN51"
symbol = "ACHRES61" if (restrn & {3,4,5,6} or catrea & group_a) else "ACHRES71" if (restrn & {9,10,11,12,13} or catrea & group_b) else "RESTRN51"
line_code = "RESARE51"; priority = True
elif restrn & {3,4,5,6}:
symbol = "FSHRES51" if catrea & group_a else "FSHRES71" if (restrn & {9,10,11,12,13,14,15} or catrea & group_b) else "FSHRES51"
symbol = "FSHRES51" if catrea & group_a else "FSHRES71" if (restrn & {9,10,11,12,13} or catrea & group_b) else "FSHRES51"
line_code = "FSHRES51"; priority = True
else:
symbol = "INFARE51" if restrn & {9,10,11,12,13,14,15} else "RSRDEF51"
symbol = "INFARE51" if restrn & {9,10,11,12,13} else "RSRDEF51"
line_code = "CTYARE51"
else:
symbol = "CTYARE71" if (catrea & group_a and catrea & group_b) else "CTYARE51" if catrea & group_a else "INFARE51" if catrea & group_b else "RSRDEF51"