248 lines
3.7 KiB
Markdown
248 lines
3.7 KiB
Markdown
# 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() |