docs(pbf): archive Full production provenance and engineering history

This commit is contained in:
OpenAI Codex
2026-10-13 14:02:56 +08:00
parent 1fef4c6dd1
commit 07d16c38a8
381 changed files with 107826 additions and 527 deletions

View File

@@ -0,0 +1,164 @@
from __future__ import annotations
import json
import shutil
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parent
MAP_JSON = ROOT / "src/pbf/semantic_sprite_key_map_2026-08-08.json"
SOURCE_SPRITE_JSON = Path("/mnt/sda1/www/newpec/sprite/sprite@2x.json")
SOURCE_SPRITE_PNG = Path("/mnt/sda1/www/newpec/sprite/sprite@2x.png")
OUTPUT_SPRITE_DIR = Path("/mnt/sda1/www/newpec/sprite-semantic-unified-20260808")
SOURCE_STYLE = ROOT / "src/pbf/style.navsea-delivery-full-semantic-extentfix.json"
OUTPUT_STYLE = ROOT / "src/pbf/style.navsea-delivery-full-semantic-unified-20260808.json"
DEPLOYED_STYLE = Path("/mnt/sda1/www/newpec/domain/style.navsea-delivery-full-semantic-unified-20260808.json")
SPRITE_URL = "http://192.168.200.184/newpec/sprite-semantic-unified-20260808/sprite"
PBF_URL = "http://192.168.200.184/pbf-delivery-full-semantic-unified-20260808/{z}/{x}/{y}.pbf"
def pack_sprite(mapping: dict[str, str], source_meta: dict[str, dict], scale: float, max_width: int):
source = Image.open(SOURCE_SPRITE_PNG).convert("RGBA")
positions: dict[str, dict] = {}
packed: list[tuple[str, tuple[int, int, int, int]]] = []
cursor_x = cursor_y = row_height = 0
for old_key, new_key in mapping.items():
meta = source_meta[old_key]
width = max(1, round(meta["width"] * scale))
height = max(1, round(meta["height"] * scale))
if cursor_x and cursor_x + width > max_width:
cursor_x = 0
cursor_y += row_height
row_height = 0
packed.append((old_key, (cursor_x, cursor_y, width, height)))
positions[new_key] = {
"x": cursor_x,
"y": cursor_y,
"width": width,
"height": height,
"pixelRatio": 1 if scale == 0.5 else 2,
}
cursor_x += width
row_height = max(row_height, height)
image = Image.new("RGBA", (max_width, cursor_y + row_height), (0, 0, 0, 0))
for old_key, (x, y, width, height) in packed:
meta = source_meta[old_key]
crop = source.crop((meta["x"], meta["y"], meta["x"] + meta["width"], meta["y"] + meta["height"]))
if scale != 1.0:
crop = crop.resize((width, height), Image.Resampling.LANCZOS)
image.paste(crop, (x, y))
return image, positions
def replace_literals(value, mapping: dict[str, str]):
if isinstance(value, dict):
return {key: replace_literals(item, mapping) for key, item in value.items()}
if isinstance(value, list):
return [replace_literals(item, mapping) for item in value]
if isinstance(value, str):
return mapping.get(value, value)
return value
def build_style(mapping: dict[str, str]) -> dict:
style = json.loads(SOURCE_STYLE.read_text(encoding="utf-8"))
style = replace_literals(style, mapping)
# The source style contains a second, older semantic vocabulary such as
# ``buoy_can`` and ``coastal_lighthouse``. Those names are not guaranteed
# to exist in the actual PNG atlas. The delivery PBF already carries the
# resolved, auditable image key in chart_icon_image/chart_fill_pattern, so
# use those properties as the single style/sprite contract. This also
# makes an unavailable source image render as empty instead of silently
# pointing at a non-existent sprite key.
direct_icon = ["coalesce", ["get", "chart_icon_image"], ""]
direct_fill_pattern = ["coalesce", ["get", "chart_fill_pattern"], ""]
direct_line_pattern = ["coalesce", ["get", "chart_line_pattern"], ""]
for layer in style.get("layers", []):
layer_id = layer.get("id")
if layer_id in {
"hazard-points",
"anchorage-symbols",
"anchor-hazard-points",
"anchor-hazard-points-428",
"nav-marks-harbor-lighthouses",
"nav-marks-breakwater-lighthouses",
"nav-marks-small-lights",
"nav-marks-light-beacons",
"nav-marks",
"facility-points",
"pilot-station-points",
"landmark-points",
}:
layer.setdefault("layout", {})["icon-image"] = direct_icon
if layer.get("id") == "nav-light-arc":
layer.setdefault("layout", {})["icon-image"] = [
"case",
["has", "arc_id"],
["get", "arc_id"],
[
"match",
["get", "light_color_code"],
"green",
mapping["arc-daytime-m1"],
"red",
mapping["arc-daytime-m2"],
mapping["arc-daytime-m3"],
],
]
if layer_id == "nav-light-flare":
layer.setdefault("layout", {})["icon-image"] = [
"match",
["get", "light_color_code"],
"green",
mapping["light-daytime-1"],
"red",
mapping["light-daytime-2"],
"yellow",
mapping["light-daytime-3"],
"white",
mapping["light-daytime-3"],
"amber",
mapping["light-daytime-3"],
"orange",
mapping["light-daytime-3"],
mapping["light-daytime-3"],
]
if layer_id == "fishery-areas":
layer.setdefault("paint", {})["fill-pattern"] = direct_fill_pattern
if layer_id == "special-pattern-line-754":
layer.setdefault("paint", {})["line-pattern"] = direct_line_pattern
style["name"] = "NavSea Delivery Full Semantic Unified 20260808"
style["sprite"] = SPRITE_URL
source = style.get("sources", {}).get("navsea_delivery")
if source:
source["tiles"] = [PBF_URL]
return style
def main() -> None:
mapping = json.loads(MAP_JSON.read_text(encoding="utf-8"))
source_meta = json.loads(SOURCE_SPRITE_JSON.read_text(encoding="utf-8"))
if OUTPUT_SPRITE_DIR.exists():
shutil.rmtree(OUTPUT_SPRITE_DIR)
OUTPUT_SPRITE_DIR.mkdir(parents=True, exist_ok=True)
for scale, json_name, png_name, width in (
(1.0, "sprite@2x.json", "sprite@2x.png", 2048),
(0.5, "sprite.json", "sprite.png", 1024),
):
image, metadata = pack_sprite(mapping, source_meta, scale, width)
image.save(OUTPUT_SPRITE_DIR / png_name)
(OUTPUT_SPRITE_DIR / json_name).write_text(json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
style = build_style(mapping)
OUTPUT_STYLE.write_text(json.dumps(style, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
DEPLOYED_STYLE.parent.mkdir(parents=True, exist_ok=True)
DEPLOYED_STYLE.write_text(json.dumps(style, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"sprite_dir": str(OUTPUT_SPRITE_DIR), "sprite_keys": len(mapping), "style": str(OUTPUT_STYLE), "deployed_style": str(DEPLOYED_STYLE)}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()