Build 20nm final release candidate

This commit is contained in:
OpenAI Codex
2026-04-01 09:48:01 +08:00
parent d1477f49ec
commit 36f3a2f2f4
8 changed files with 2943 additions and 36 deletions

View File

@@ -52,6 +52,29 @@ LIGHT_COLOR_REMARK_MAP = {
"A": "amber",
}
FINAL_RELEASE_PROPERTY_ALLOWLIST = frozenset(
{
"canonical_object_type",
"chart_fill_pattern",
"chart_fill_style",
"chart_icon_image",
"chart_label_position_code",
"chart_label_subtext",
"chart_label_text",
"chart_line_color",
"chart_line_width",
"chart_symbol_code",
"chart_text_color",
"chart_text_style",
"depth_value_m",
"least_depth_m",
"light_color_code",
"light_sector_mode",
"name_ja",
"place_name_en",
}
)
@dataclass(frozen=True)
class DbConfig:
@@ -163,6 +186,18 @@ def parse_number(value: object) -> float | None:
return None
def feature_id_from_current_fid(fid_value: object) -> int | None:
text = text_or_none(fid_value)
if not text:
return None
try:
if re.fullmatch(r"[0-9A-Fa-f]{8}", text):
return int(text, 16)
return int(text)
except ValueError:
return None
def infer_light_color_code_from_remark(light_remark: object) -> str | None:
remark_text = text_or_none(light_remark)
if not remark_text:
@@ -213,8 +248,10 @@ class NavSeaTileBuilder:
zmax: int,
workers: int,
all_tiles: bool,
reference_tile_root: Path | None,
engineering_mode: bool,
strip_legacy_japanese_delivery: bool,
release_minimal: bool,
fid_codec: NavSeaFidCodec | None,
fid_key_id: str | None,
bundle_id: str | None,
@@ -229,8 +266,10 @@ class NavSeaTileBuilder:
self.zmax = zmax
self.workers = workers
self.all_tiles = all_tiles
self.reference_tile_root = reference_tile_root
self.engineering_mode = engineering_mode
self.strip_legacy_japanese_delivery = strip_legacy_japanese_delivery
self.release_minimal = release_minimal
self.fid_codec = fid_codec
self.fid_key_id = fid_key_id
self.bundle_id = bundle_id
@@ -305,6 +344,8 @@ class NavSeaTileBuilder:
return [int(row["z"]) for row in cur.fetchall()]
def build_jobs(self, supported_zooms: list[int]) -> list[TileJob]:
if self.reference_tile_root is not None:
return self.build_reference_jobs(supported_zooms)
if self.all_tiles:
return self.build_all_jobs(supported_zooms)
@@ -335,6 +376,26 @@ class NavSeaTileBuilder:
return jobs
def build_reference_jobs(self, supported_zooms: list[int]) -> list[TileJob]:
jobs: list[TileJob] = []
zoom_set = set(supported_zooms)
assert self.reference_tile_root is not None
for path in sorted(self.reference_tile_root.glob("*/*/*.pbf")):
try:
z = int(path.parent.parent.name)
x = int(path.parent.name)
y = int(path.stem)
except ValueError:
continue
if z not in zoom_set or z < self.zmin or z > self.zmax:
continue
if not (self.source_root / str(z) / str(x) / f"{y}.pbf").exists():
continue
jobs.append(TileJob(z=z, x=x, y=y))
return jobs
def aoi_bbox(self) -> tuple[float, float, float, float]:
radius_km = self.radius_nm * 1.852
lat_delta = radius_km / 111.32
@@ -380,7 +441,6 @@ class NavSeaTileBuilder:
COALESCE(fid.v, '') AS fid,
r.source_layer,
r.geom_type,
COALESCE(NULLIF(r.render_layer, ''), r.source_layer) AS output_layer,
r.canonical_object_type,
r.canonical_family,
r.semantic_key,
@@ -438,27 +498,27 @@ class NavSeaTileBuilder:
)
for source_feature, row in zip(source_features, db_rows):
output_layer = str(row["output_layer"])
source_layer_jp = str(row["source_layer"])
output_layer, source_layer_rule_id = self.mapping_registry.resolve_source_layer(source_layer_jp)
properties = dict(source_feature.get("properties") or {})
legacy_fid_raw = properties.get("fid", row["fid"])
current_feature_id = feature_id_from_current_fid(legacy_fid_raw)
navsea_fid_pair = None
if self.fid_codec is not None:
navsea_fid_pair = self.build_engineering_fid(legacy_fid_raw, row, job)
navsea_fid_int, navsea_fid_hex = navsea_fid_pair
properties["fid"] = navsea_fid_hex
properties["fid_algo_id"] = "feistel32_aes_cyclewalk_v1"
properties["fid_key_id"] = self.fid_key_id
current_feature_id = navsea_fid_int
if self.engineering_mode:
if navsea_fid_pair is None:
raise RuntimeError("engineering mode requires encrypted fid generation")
navsea_fid_int, _ = navsea_fid_pair
properties["fid_algo_id"] = "feistel32_aes_cyclewalk_v1"
properties["fid_key_id"] = self.fid_key_id
properties["fid_legacy_raw"] = legacy_fid_raw
properties["fid_navsea_int"] = navsea_fid_int
properties["source_layer_jp"] = row["source_layer"]
source_layer_std, source_layer_rule_id = self.mapping_registry.resolve_source_layer(
str(row["source_layer"])
)
properties["source_layer_std"] = source_layer_std
properties["source_layer_jp"] = source_layer_jp
properties["source_layer_std"] = output_layer
properties["normalization_bundle_id"] = self.bundle_id
properties["source_layer_rule_id"] = source_layer_rule_id
properties["feature_id"] = row["feature_id"]
@@ -470,7 +530,7 @@ class NavSeaTileBuilder:
properties["render_layer"] = output_layer
chart_properties, feature_unresolved, trace_status = self.build_chart_properties(
properties=properties,
source_layer=str(row["source_layer"]),
source_layer=source_layer_jp,
output_layer=output_layer,
canonical_object_type=text_or_none(row["canonical_object_type"]) or "",
canonical_family=text_or_none(row["canonical_family"]) or "",
@@ -483,13 +543,15 @@ class NavSeaTileBuilder:
properties["trace_status"] = trace_status
properties, field_name_unresolved = self.standardize_output_properties(
properties=properties,
source_layer=str(row["source_layer"]),
source_layer=source_layer_jp,
canonical_object_type=text_or_none(row["canonical_object_type"]) or "",
canonical_family=text_or_none(row["canonical_family"]) or "",
geom_type=str(source_feature.get("geometry", {}).get("type", "")),
feature_id=int(row["feature_id"]),
tile=job,
)
if self.release_minimal and not self.engineering_mode:
properties = self.minimize_delivery_properties(properties)
unresolved.extend(feature_unresolved)
unresolved.extend(field_name_unresolved)
@@ -497,11 +559,13 @@ class NavSeaTileBuilder:
"geometry": source_feature["geometry"],
"properties": properties,
}
if source_feature.get("id") is not None:
if current_feature_id is not None:
output_feature["id"] = current_feature_id
elif source_feature.get("id") is not None:
output_feature["id"] = source_feature["id"]
layer_features[output_layer].append(output_feature)
layer_extents.setdefault(output_layer, source_extents[str(row["source_layer"])])
layer_extents.setdefault(output_layer, source_extents[source_layer_jp])
encoded_layers = []
per_layer_options: dict[str, dict[str, int]] = {}
@@ -779,9 +843,10 @@ class NavSeaTileBuilder:
for key, value in properties.items():
if key == "fid":
# By the time we normalize output properties, fid already means
# the NavSea public ID, not the original source fid field.
normalized[key] = value
if self.engineering_mode:
# Engineering output keeps the public NavSea fid in properties
# so it can be inspected alongside trace metadata.
normalized[key] = value
continue
rule = self.mapping_registry.get_field_name_rule(str(key))
if rule is None:
@@ -820,6 +885,14 @@ class NavSeaTileBuilder:
return normalized, unresolved
@staticmethod
def minimize_delivery_properties(properties: dict[str, object]) -> dict[str, object]:
return {
key: value
for key, value in properties.items()
if key in FINAL_RELEASE_PROPERTY_ALLOWLIST
}
@staticmethod
def make_unresolved_event(
*,
@@ -1376,6 +1449,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--source-root", type=Path, default=SOURCE_TILE_ROOT)
parser.add_argument("--workers", type=int, default=4)
parser.add_argument("--all-tiles", action="store_true")
parser.add_argument("--reference-tile-root", type=Path)
parser.add_argument("--engineering", action="store_true")
parser.add_argument(
"--strip-legacy-japanese-delivery",
@@ -1389,13 +1463,18 @@ def parse_args() -> argparse.Namespace:
dest="strip_legacy_japanese_delivery",
help="keep mapped Japanese structural keys in delivery tiles for legacy compatibility",
)
parser.add_argument(
"--release-minimal",
action="store_true",
help="emit a final release tile payload with only the minimal style-driven delivery fields",
)
parser.add_argument("--fid-key")
parser.add_argument("--fid-key-id", default="navsea-fid-key-v1")
parser.add_argument("--bundle-id", default="navsea-reversible-v1")
parser.set_defaults(strip_legacy_japanese_delivery=True)
args = parser.parse_args()
if not args.all_tiles:
if not args.all_tiles and args.reference_tile_root is None:
missing = [
name
for name in ("center_lat", "center_lon", "radius_nm")
@@ -1405,8 +1484,8 @@ def parse_args() -> argparse.Namespace:
parser.error(
"--center-lat, --center-lon, and --radius-nm are required unless --all-tiles is used"
)
if args.engineering and not args.fid_key:
parser.error("--fid-key is required when --engineering is used")
if not args.fid_key:
parser.error("--fid-key is required for NavSea delivery and engineering builds")
return args
@@ -1425,8 +1504,10 @@ def main() -> None:
zmax=args.zmax,
workers=max(1, args.workers),
all_tiles=args.all_tiles,
reference_tile_root=args.reference_tile_root,
engineering_mode=args.engineering,
strip_legacy_japanese_delivery=args.strip_legacy_japanese_delivery,
release_minimal=args.release_minimal,
fid_codec=fid_codec,
fid_key_id=args.fid_key_id,
bundle_id=args.bundle_id,