Verify Ground Zero neighbor edges
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify Ground Zero neighbor tile edges before multi-tile stitching."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
REGISTRY_PATH = REPO_ROOT / "Data/Tiles/ground_zero_tiles.json"
|
||||
OUTPUT_PATH = (
|
||||
REPO_ROOT
|
||||
/ "Data/Terrain/Analysis/gz_us_ca_pacifica_utm10n_e544_n4160/"
|
||||
/ "gz_us_ca_pacifica_utm10n_e544_n4160_neighbor_edge_verification.json"
|
||||
)
|
||||
|
||||
GROUND_ZERO_TILE_ID = "gz_us_ca_pacifica_utm10n_e544_n4160"
|
||||
TILE_SIZE_M = 1000
|
||||
|
||||
DIRECTION_OFFSETS = {
|
||||
"n": (0, 1),
|
||||
"ne": (1, 1),
|
||||
"e": (1, 0),
|
||||
"se": (1, -1),
|
||||
"s": (0, -1),
|
||||
"sw": (-1, -1),
|
||||
"w": (-1, 0),
|
||||
"nw": (-1, 1),
|
||||
}
|
||||
|
||||
|
||||
def load_registry() -> dict:
|
||||
return json.loads(REGISTRY_PATH.read_text())
|
||||
|
||||
|
||||
def grid(tile: dict) -> dict:
|
||||
return tile["grid"]
|
||||
|
||||
|
||||
def expected_tile_id(tile_family: str, easting_min_m: int, northing_min_m: int) -> str:
|
||||
return f"{tile_family}_e{easting_min_m // 1000}_n{northing_min_m // 1000}"
|
||||
|
||||
|
||||
def verify_tile_shape(tile: dict) -> list[str]:
|
||||
errors: list[str] = []
|
||||
g = grid(tile)
|
||||
width = g["easting_max_m"] - g["easting_min_m"]
|
||||
height = g["northing_max_m"] - g["northing_min_m"]
|
||||
|
||||
if width != TILE_SIZE_M:
|
||||
errors.append(f"width is {width}m, expected {TILE_SIZE_M}m")
|
||||
if height != TILE_SIZE_M:
|
||||
errors.append(f"height is {height}m, expected {TILE_SIZE_M}m")
|
||||
if g["tile_size_m"] != TILE_SIZE_M:
|
||||
errors.append(f"tile_size_m is {g['tile_size_m']}, expected {TILE_SIZE_M}")
|
||||
if g["utm_zone"] != "10N":
|
||||
errors.append(f"utm_zone is {g['utm_zone']}, expected 10N")
|
||||
if g["projection"] != "WGS84 / UTM zone 10N":
|
||||
errors.append(f"projection is {g['projection']}, expected WGS84 / UTM zone 10N")
|
||||
|
||||
tile_family = "_".join(tile["tile_id"].split("_")[:-2])
|
||||
expected_id = expected_tile_id(tile_family, g["easting_min_m"], g["northing_min_m"])
|
||||
if tile["tile_id"] != expected_id:
|
||||
errors.append(f"tile_id is {tile['tile_id']}, expected {expected_id}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def expected_bounds(center_grid: dict, dx: int, dy: int) -> dict:
|
||||
easting_min = center_grid["easting_min_m"] + dx * TILE_SIZE_M
|
||||
northing_min = center_grid["northing_min_m"] + dy * TILE_SIZE_M
|
||||
return {
|
||||
"easting_min_m": easting_min,
|
||||
"northing_min_m": northing_min,
|
||||
"easting_max_m": easting_min + TILE_SIZE_M,
|
||||
"northing_max_m": northing_min + TILE_SIZE_M,
|
||||
}
|
||||
|
||||
|
||||
def verify_neighbor_edge(direction: str, center: dict, neighbor: dict) -> dict:
|
||||
center_grid = grid(center)
|
||||
neighbor_grid = grid(neighbor)
|
||||
dx, dy = DIRECTION_OFFSETS[direction]
|
||||
expected = expected_bounds(center_grid, dx, dy)
|
||||
|
||||
errors: list[str] = []
|
||||
for key, expected_value in expected.items():
|
||||
actual_value = neighbor_grid[key]
|
||||
if actual_value != expected_value:
|
||||
errors.append(f"{key} is {actual_value}, expected {expected_value}")
|
||||
|
||||
if direction == "n":
|
||||
shared_edge = {
|
||||
"center_edge": "north",
|
||||
"neighbor_edge": "south",
|
||||
"shared_northing_m": center_grid["northing_max_m"],
|
||||
"easting_span_m": [center_grid["easting_min_m"], center_grid["easting_max_m"]],
|
||||
}
|
||||
if neighbor_grid["northing_min_m"] != center_grid["northing_max_m"]:
|
||||
errors.append("north neighbor south edge does not meet Ground Zero north edge")
|
||||
elif direction == "s":
|
||||
shared_edge = {
|
||||
"center_edge": "south",
|
||||
"neighbor_edge": "north",
|
||||
"shared_northing_m": center_grid["northing_min_m"],
|
||||
"easting_span_m": [center_grid["easting_min_m"], center_grid["easting_max_m"]],
|
||||
}
|
||||
if neighbor_grid["northing_max_m"] != center_grid["northing_min_m"]:
|
||||
errors.append("south neighbor north edge does not meet Ground Zero south edge")
|
||||
elif direction == "e":
|
||||
shared_edge = {
|
||||
"center_edge": "east",
|
||||
"neighbor_edge": "west",
|
||||
"shared_easting_m": center_grid["easting_max_m"],
|
||||
"northing_span_m": [center_grid["northing_min_m"], center_grid["northing_max_m"]],
|
||||
}
|
||||
if neighbor_grid["easting_min_m"] != center_grid["easting_max_m"]:
|
||||
errors.append("east neighbor west edge does not meet Ground Zero east edge")
|
||||
elif direction == "w":
|
||||
shared_edge = {
|
||||
"center_edge": "west",
|
||||
"neighbor_edge": "east",
|
||||
"shared_easting_m": center_grid["easting_min_m"],
|
||||
"northing_span_m": [center_grid["northing_min_m"], center_grid["northing_max_m"]],
|
||||
}
|
||||
if neighbor_grid["easting_max_m"] != center_grid["easting_min_m"]:
|
||||
errors.append("west neighbor east edge does not meet Ground Zero west edge")
|
||||
else:
|
||||
shared_edge = {
|
||||
"center_corner_touch": direction,
|
||||
"corner_touch_only": True,
|
||||
"expected_bounds": expected,
|
||||
}
|
||||
|
||||
return {
|
||||
"direction": direction,
|
||||
"neighbor_tile_id": neighbor["tile_id"],
|
||||
"expected_bounds_utm_m": expected,
|
||||
"actual_bounds_utm_m": {
|
||||
"easting_min_m": neighbor_grid["easting_min_m"],
|
||||
"northing_min_m": neighbor_grid["northing_min_m"],
|
||||
"easting_max_m": neighbor_grid["easting_max_m"],
|
||||
"northing_max_m": neighbor_grid["northing_max_m"],
|
||||
},
|
||||
"shared_edge": shared_edge,
|
||||
"passed": not errors,
|
||||
"errors": errors,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
registry = load_registry()
|
||||
tiles_by_id = {tile["tile_id"]: tile for tile in registry["tiles"]}
|
||||
center = tiles_by_id[GROUND_ZERO_TILE_ID]
|
||||
|
||||
tile_shape_checks = []
|
||||
for tile in registry["tiles"]:
|
||||
errors = verify_tile_shape(tile)
|
||||
tile_shape_checks.append(
|
||||
{
|
||||
"tile_id": tile["tile_id"],
|
||||
"passed": not errors,
|
||||
"errors": errors,
|
||||
}
|
||||
)
|
||||
|
||||
neighbor_checks = []
|
||||
missing_neighbors = []
|
||||
for direction in ["n", "ne", "e", "se", "s", "sw", "w", "nw"]:
|
||||
neighbor_id = center["neighbors"].get(direction)
|
||||
if not neighbor_id or neighbor_id not in tiles_by_id:
|
||||
missing_neighbors.append({"direction": direction, "neighbor_tile_id": neighbor_id})
|
||||
continue
|
||||
neighbor_checks.append(verify_neighbor_edge(direction, center, tiles_by_id[neighbor_id]))
|
||||
|
||||
passed = (
|
||||
not missing_neighbors
|
||||
and all(check["passed"] for check in neighbor_checks)
|
||||
and all(check["passed"] for check in tile_shape_checks)
|
||||
)
|
||||
|
||||
result = {
|
||||
"schema_version": 1,
|
||||
"tile_id": GROUND_ZERO_TILE_ID,
|
||||
"registry_path": str(REGISTRY_PATH.relative_to(REPO_ROOT)),
|
||||
"grid_scheme": registry["grid_scheme"],
|
||||
"verified_tile_count": len(registry["tiles"]),
|
||||
"expected_neighbor_count": 8,
|
||||
"found_neighbor_count": len(neighbor_checks),
|
||||
"all_passed": passed,
|
||||
"missing_neighbors": missing_neighbors,
|
||||
"tile_shape_checks": tile_shape_checks,
|
||||
"neighbor_edge_checks": neighbor_checks,
|
||||
"stitching_decision": {
|
||||
"ready_for_coordinate_based_neighbor_stitching": passed,
|
||||
"notes": [
|
||||
"All Ground Zero neighbor placeholders align on exact 1000m UTM boundaries.",
|
||||
"Cardinal neighbors share full 1km edges with no gap or overlap.",
|
||||
"Diagonal neighbors touch at corners only.",
|
||||
"This verifies registry coordinates, not yet elevation seam continuity.",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT_PATH.write_text(json.dumps(result, indent=2) + "\n")
|
||||
print(f"Wrote {OUTPUT_PATH.relative_to(REPO_ROOT)}")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"all_passed": result["all_passed"],
|
||||
"verified_tile_count": result["verified_tile_count"],
|
||||
"found_neighbor_count": result["found_neighbor_count"],
|
||||
"missing_neighbors": result["missing_neighbors"],
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user