Initial import of NavSea pbf project

This commit is contained in:
OpenAI Codex
2026-03-17 19:48:15 +08:00
commit 5f3f7409b3
84 changed files with 43889 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
import os
from collections import defaultdict
from pathlib import Path
import json
try:
import mapbox_vector_tile
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
mapbox_vector_tile = None
try:
import mercantile
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
mercantile = None
PROJECT_ROOT = Path(__file__).resolve().parent.parent
GRID_DIR = PROJECT_ROOT / "data" / "grid"
OUTPUT_DIR = Path("/home/wwwroot/weather")
DEFAULT_ZOOMS = [2, 4, 6, 8, 10, 12]
def get_zoom_levels():
raw_value = os.environ.get("WEATHER_TILE_ZOOMS", "")
if not raw_value.strip():
return DEFAULT_ZOOMS
zooms = []
for chunk in raw_value.split(","):
chunk = chunk.strip()
if not chunk:
continue
zoom = int(chunk)
if zoom < 0:
raise ValueError(f"Invalid zoom level: {zoom}")
zooms.append(zoom)
if not zooms:
raise ValueError("WEATHER_TILE_ZOOMS did not contain any usable zoom levels")
return sorted(set(zooms))
def load_grid(path):
with path.open(encoding="utf-8") as file_handle:
data = json.load(file_handle)
return data["time"], data["grid"]
def get_grid_field(grid, field_name, latitudes, longitudes):
values = grid.get(field_name)
if values is not None:
return values
return [[0.0 for _ in longitudes] for _ in latitudes]
def grid_to_features(grid):
latitudes = grid["lat"]
longitudes = grid["lon"]
wind_speed = grid["wind_speed"]
wind_dir = grid["wind_dir"]
rain = grid["rain"]
temp = grid["temp"]
pressure = grid["pressure"]
wave_h = get_grid_field(grid, "wave_h", latitudes, longitudes)
wave_dir = get_grid_field(grid, "wave_dir", latitudes, longitudes)
wave_period = get_grid_field(grid, "wave_period", latitudes, longitudes)
features = []
for lat_index, latitude in enumerate(latitudes):
for lon_index, longitude in enumerate(longitudes):
features.append(
{
"geometry": {"type": "Point", "coordinates": [longitude, latitude]},
"properties": {
"ws": wind_speed[lat_index][lon_index],
"wd": wind_dir[lat_index][lon_index],
"r": rain[lat_index][lon_index],
"t": temp[lat_index][lon_index],
"p": pressure[lat_index][lon_index],
"wh": wave_h[lat_index][lon_index],
"wdir": wave_dir[lat_index][lon_index],
"wp": wave_period[lat_index][lon_index],
},
}
)
return features
def bucket_features_by_tile(features, zoom):
buckets = defaultdict(list)
for feature in features:
longitude, latitude = feature["geometry"]["coordinates"]
tile = mercantile.tile(longitude, latitude, zoom)
buckets[(tile.x, tile.y)].append(feature)
return buckets
def write_tile(tile_time, zoom, tile_x, tile_y, features):
bounds = mercantile.bounds(mercantile.Tile(x=tile_x, y=tile_y, z=zoom))
tile_dir = OUTPUT_DIR / tile_time / str(zoom) / str(tile_x)
tile_dir.mkdir(parents=True, exist_ok=True)
tile_path = tile_dir / f"{tile_y}.pbf"
layer = {"name": "weather", "features": features}
tile_data = mapbox_vector_tile.encode(
layer,
default_options={
"quantize_bounds": (bounds.west, bounds.south, bounds.east, bounds.north),
},
)
tile_path.write_bytes(tile_data)
print("tile", tile_time, zoom, tile_x, tile_y)
def generate_tiles(tile_time, features):
for zoom in get_zoom_levels():
buckets = bucket_features_by_tile(features, zoom)
for (tile_x, tile_y), tile_features in buckets.items():
write_tile(tile_time, zoom, tile_x, tile_y, tile_features)
def main():
if mapbox_vector_tile is None or mercantile is None:
raise RuntimeError("mapbox-vector-tile and mercantile are required to generate vector tiles")
for path in sorted(GRID_DIR.glob("*.json")):
print("processing", path.name)
tile_time, grid = load_grid(path)
features = grid_to_features(grid)
generate_tiles(tile_time, features)
if __name__ == "__main__":
main()