Build 20nm final release candidate
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user