# NavSea Weather Server
Task: WeatherServer_VectorTileGenerator
Architecture: NavSea V11
Codex: codex6
Status: TODO

---

# 1 任务目标

实现 Vector Tile Generator。

功能：

将 Weather Grid JSON 转换为 MapLibre Vector Tile (PBF)。

输入：

data/grid/*.json

输出：

output/weather/{time}/{z}/{x}/{y}.pbf

用于 NavSea 客户端加载天气图层。

---

# 2 使用库

需要安装：

pip install mercantile
pip install mapbox-vector-tile

---

# 3 输入数据

GridBuilder v2 生成：

data/grid/

grid_20260312_00_f000.json
grid_20260312_00_f003.json

结构：

{
 "time": "...",
 "grid": {
   "lat": [...],
   "lon": [...],
   "wind_speed": [[...]],
   "wind_dir": [[...]],
   "rain": [[...]],
   "temp": [[...]],
   "pressure": [[...]]
 }
}

grid 尺寸：

121 × 121

---

# 4 Tile Zoom 设计

Weather tile 只需要 4 个 zoom：

2
4
6
8

---

# 5 Tile 输出结构

output/weather/

time/
   z/
      x/
        y.pbf

示例：

output/weather/20260312_00_f000/4/10/7.pbf

---

# 6 Feature 结构

每个 grid 点 → 一个 feature

geometry：

POINT(lon lat)

properties：

{
 "ws": wind_speed
 "wd": wind_dir
 "r": rain
 "t": temp
 "p": pressure
}

字段缩写减少 tile 大小。

---

# 7 创建文件

weather_server/tiles/vector_tile_generator.py

---

# 8 实现代码

```python
import os
import json
import mercantile
import mapbox_vector_tile

GRID_DIR = "data/grid"
OUTPUT_DIR = "output/weather"

ZOOMS = [2,4,6,8]

def load_grid(path):

    with open(path) as f:
        data = json.load(f)

    return data["time"], data["grid"]


def grid_to_features(grid):

    lat = grid["lat"]
    lon = grid["lon"]

    ws = grid["wind_speed"]
    wd = grid["wind_dir"]
    rain = grid["rain"]
    temp = grid["temp"]
    pres = grid["pressure"]

    features = []

    for i in range(len(lat)):
        for j in range(len(lon)):

            feature = {
                "geometry":{
                    "type":"Point",
                    "coordinates":[lon[j], lat[i]]
                },
                "properties":{
                    "ws":ws[i][j],
                    "wd":wd[i][j],
                    "r":rain[i][j],
                    "t":temp[i][j],
                    "p":pres[i][j]
                }
            }

            features.append(feature)

    return features


def generate_tiles(time, features):

    for z in ZOOMS:

        tiles = mercantile.tiles(120,20,150,50,z)

        for tile in tiles:

            bounds = mercantile.bounds(tile)

            tile_features = []

            for f in features:

                lon, lat = f["geometry"]["coordinates"]

                if (
                    bounds.west <= lon <= bounds.east
                    and bounds.south <= lat <= bounds.north
                ):
                    tile_features.append(f)

            if not tile_features:
                continue

            layer = {
                "weather": tile_features
            }

            tile_data = mapbox_vector_tile.encode(layer)

            path = os.path.join(
                OUTPUT_DIR,
                time,
                str(z),
                str(tile.x)
            )

            os.makedirs(path, exist_ok=True)

            filename = os.path.join(
                path,
                f"{tile.y}.pbf"
            )

            with open(filename,"wb") as f:
                f.write(tile_data)

            print("tile", time, z, tile.x, tile.y)


def main():

    for file in os.listdir(GRID_DIR):

        if not file.endswith(".json"):
            continue

        path = os.path.join(GRID_DIR, file)

        print("processing", file)

        time, grid = load_grid(path)

        features = grid_to_features(grid)

        generate_tiles(time, features)


if __name__ == "__main__":
    main()