Initial import of NavSea pbf project
This commit is contained in:
211
src/gfs_downloader.py
Normal file
211
src/gfs_downloader.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import time
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||
requests = None
|
||||
|
||||
BASE_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl"
|
||||
WAVE_BASE_URL = "https://nomads.ncep.noaa.gov/cgi-bin/filter_gfswave.pl"
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
OUTPUT_DIR = PROJECT_ROOT / "data" / "grib"
|
||||
REQUEST_TIMEOUT = (10, 120)
|
||||
RETRY_LIMIT = 3
|
||||
MIN_FILE_SIZE_BYTES = 1024
|
||||
|
||||
FORECAST_HOURS = [
|
||||
0, 3, 6, 9, 12, 15, 18, 21, 24,
|
||||
27, 30, 33, 36, 39, 42, 45,
|
||||
48, 51, 54, 57, 60, 63, 66,
|
||||
69, 72,
|
||||
]
|
||||
|
||||
REGION = {
|
||||
"leftlon": 120,
|
||||
"rightlon": 150,
|
||||
"toplat": 50,
|
||||
"bottomlat": 20,
|
||||
}
|
||||
|
||||
ATMOS_VARIABLES = [
|
||||
"UGRD",
|
||||
"VGRD",
|
||||
"APCP",
|
||||
"PRMSL",
|
||||
"TMP",
|
||||
]
|
||||
|
||||
WAVE_VARIABLES = [
|
||||
"HTSGW",
|
||||
"DIRPW",
|
||||
"PERPW",
|
||||
]
|
||||
|
||||
ATMOS_LEVELS = [
|
||||
"lev_10_m_above_ground",
|
||||
"lev_surface",
|
||||
"lev_mean_sea_level",
|
||||
]
|
||||
|
||||
WAVE_LEVELS = [
|
||||
"lev_surface",
|
||||
]
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def get_cycle(reference_time=None):
|
||||
now = reference_time or datetime.now(timezone.utc)
|
||||
# NOMADS availability usually lags the wall clock. Bias one cycle back.
|
||||
candidate = now - timedelta(hours=5)
|
||||
hour = (candidate.hour // 6) * 6
|
||||
cycle_time = candidate.replace(hour=hour, minute=0, second=0, microsecond=0)
|
||||
return cycle_time.strftime("%Y%m%d"), f"{cycle_time.hour:02d}"
|
||||
|
||||
|
||||
def build_atmos_request(date, cycle, forecast_hour):
|
||||
filename = f"gfs.t{cycle}z.pgrb2.0p25.f{forecast_hour}"
|
||||
params = {
|
||||
"file": filename,
|
||||
"leftlon": REGION["leftlon"],
|
||||
"rightlon": REGION["rightlon"],
|
||||
"toplat": REGION["toplat"],
|
||||
"bottomlat": REGION["bottomlat"],
|
||||
"dir": f"/gfs.{date}/{cycle}/atmos",
|
||||
}
|
||||
|
||||
for variable in ATMOS_VARIABLES:
|
||||
params[f"var_{variable}"] = "on"
|
||||
|
||||
for level in ATMOS_LEVELS:
|
||||
params[level] = "on"
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def build_wave_request(date, cycle, forecast_hour):
|
||||
filename = f"gfswave.t{cycle}z.global.0p25.f{forecast_hour}.grib2"
|
||||
params = {
|
||||
"file": filename,
|
||||
"leftlon": REGION["leftlon"],
|
||||
"rightlon": REGION["rightlon"],
|
||||
"toplat": REGION["toplat"],
|
||||
"bottomlat": REGION["bottomlat"],
|
||||
"dir": f"/gfs.{date}/{cycle}/wave/gridded",
|
||||
}
|
||||
|
||||
for variable in WAVE_VARIABLES:
|
||||
params[f"var_{variable}"] = "on"
|
||||
|
||||
for level in WAVE_LEVELS:
|
||||
params[level] = "on"
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def validate_response(response):
|
||||
content_type = response.headers.get("Content-Type", "").lower()
|
||||
if "html" in content_type or "text/plain" in content_type:
|
||||
preview = response.text[:200].strip().replace("\n", " ")
|
||||
raise ValueError(f"unexpected response content type {content_type}: {preview}")
|
||||
|
||||
|
||||
def is_fatal_network_error(error):
|
||||
error_text = str(error)
|
||||
fatal_markers = (
|
||||
"NameResolutionError",
|
||||
"Failed to resolve",
|
||||
"Temporary failure in name resolution",
|
||||
)
|
||||
return any(marker in error_text for marker in fatal_markers)
|
||||
|
||||
|
||||
def download_file(session, base_url, params, output_path):
|
||||
temp_path = output_path.with_suffix(".grib2.part")
|
||||
|
||||
if output_path.exists() and output_path.stat().st_size >= MIN_FILE_SIZE_BYTES:
|
||||
print("skip", output_path)
|
||||
return
|
||||
|
||||
for attempt in range(1, RETRY_LIMIT + 1):
|
||||
try:
|
||||
print(f"downloading {output_path} (attempt {attempt}/{RETRY_LIMIT})")
|
||||
with session.get(
|
||||
base_url,
|
||||
params=params,
|
||||
stream=True,
|
||||
timeout=REQUEST_TIMEOUT,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
validate_response(response)
|
||||
|
||||
with temp_path.open("wb") as file_handle:
|
||||
for chunk in response.iter_content(1024 * 1024):
|
||||
if chunk:
|
||||
file_handle.write(chunk)
|
||||
|
||||
if temp_path.stat().st_size < MIN_FILE_SIZE_BYTES:
|
||||
raise ValueError(f"downloaded file too small: {temp_path.stat().st_size} bytes")
|
||||
|
||||
temp_path.replace(output_path)
|
||||
return
|
||||
except (requests.RequestException, ValueError) as exc:
|
||||
if temp_path.exists():
|
||||
temp_path.unlink()
|
||||
print(" download failed:", exc)
|
||||
if is_fatal_network_error(exc):
|
||||
raise RuntimeError("fatal network error while reaching NOAA") from exc
|
||||
if attempt == RETRY_LIMIT:
|
||||
raise
|
||||
time.sleep(attempt * 2)
|
||||
|
||||
|
||||
def download_forecast(session, date, cycle, forecast_hour):
|
||||
atmos_path = OUTPUT_DIR / f"{date}_{cycle}_f{forecast_hour}.grib2"
|
||||
wave_path = OUTPUT_DIR / f"{date}_{cycle}_f{forecast_hour}_wave.grib2"
|
||||
|
||||
download_file(
|
||||
session,
|
||||
BASE_URL,
|
||||
build_atmos_request(date, cycle, forecast_hour),
|
||||
atmos_path,
|
||||
)
|
||||
download_file(
|
||||
session,
|
||||
WAVE_BASE_URL,
|
||||
build_wave_request(date, cycle, forecast_hour),
|
||||
wave_path,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
if requests is None:
|
||||
raise RuntimeError("requests is required to download GFS data")
|
||||
|
||||
date, cycle = get_cycle()
|
||||
print("cycle:", date, cycle)
|
||||
|
||||
session = requests.Session()
|
||||
session.headers["User-Agent"] = "weather-pipeline/1.0"
|
||||
|
||||
failures = []
|
||||
for forecast_hour in FORECAST_HOURS:
|
||||
forecast_hour_str = f"{forecast_hour:03d}"
|
||||
try:
|
||||
download_forecast(session, date, cycle, forecast_hour_str)
|
||||
except RuntimeError as exc:
|
||||
failures.append((forecast_hour_str, str(exc)))
|
||||
break
|
||||
except Exception as exc:
|
||||
failures.append((forecast_hour_str, str(exc)))
|
||||
|
||||
if failures:
|
||||
for forecast_hour, error in failures:
|
||||
print(f"failed forecast {forecast_hour}: {error}")
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
189
src/grid_builder_v2.py
Normal file
189
src/grid_builder_v2.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import xarray as xr
|
||||
|
||||
try:
|
||||
import cfgrib
|
||||
except ModuleNotFoundError: # pragma: no cover - dependency guard for runtime environments
|
||||
cfgrib = None
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
INPUT_DIR = PROJECT_ROOT / "data" / "grib"
|
||||
OUTPUT_DIR = PROJECT_ROOT / "data" / "grid"
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
REGION = {
|
||||
"lon_min": 120,
|
||||
"lon_max": 150,
|
||||
"lat_min": 20,
|
||||
"lat_max": 50,
|
||||
}
|
||||
|
||||
VARIABLE_CANDIDATES = {
|
||||
"u10": ("u10", "u"),
|
||||
"v10": ("v10", "v"),
|
||||
"tp": ("tp", "prate", "unknown"),
|
||||
"msl": ("prmsl", "msl", "pres"),
|
||||
"temp": ("t2m", "t"),
|
||||
"wave_h": ("htsgw", "swh", "wvhgt"),
|
||||
"wave_dir": ("dirpw", "mwd", "wvdir"),
|
||||
"wave_period": ("perpw", "mwp", "wvper"),
|
||||
}
|
||||
|
||||
|
||||
def compute_wind(u_component, v_component):
|
||||
speed = np.sqrt(u_component ** 2 + v_component ** 2)
|
||||
# Meteorological direction: where the wind comes from, in degrees clockwise from north.
|
||||
direction = (270 - np.degrees(np.arctan2(v_component, u_component))) % 360
|
||||
return speed, direction
|
||||
|
||||
|
||||
def load_datasets(path):
|
||||
if cfgrib is None:
|
||||
raise RuntimeError("cfgrib is required to build grids from GRIB2 files")
|
||||
with xr.set_options(use_new_combine_kwarg_defaults=True):
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message="In a future version of xarray the default value for compat will change",
|
||||
category=FutureWarning,
|
||||
)
|
||||
return cfgrib.xarray_store.open_datasets(str(path))
|
||||
|
||||
|
||||
def find_variable(datasets, candidates):
|
||||
for candidate in candidates:
|
||||
for dataset in datasets:
|
||||
if candidate in dataset:
|
||||
return dataset[candidate]
|
||||
return None
|
||||
|
||||
|
||||
def get_lat_lon(datasets):
|
||||
for dataset in datasets:
|
||||
if "latitude" in dataset and "longitude" in dataset:
|
||||
return dataset["latitude"].values, dataset["longitude"].values
|
||||
raise ValueError("no latitude/longitude coordinates found in GRIB datasets")
|
||||
|
||||
|
||||
def to_2d_values(data_array, lat_size, lon_size):
|
||||
if data_array is None:
|
||||
return np.zeros((lat_size, lon_size), dtype=float)
|
||||
|
||||
values = np.asarray(data_array.squeeze().values)
|
||||
if values.ndim != 2:
|
||||
raise ValueError(f"expected 2D field, got shape {values.shape} for {data_array.name}")
|
||||
if values.shape != (lat_size, lon_size):
|
||||
raise ValueError(
|
||||
f"field {data_array.name} shape {values.shape} does not match coordinates {(lat_size, lon_size)}"
|
||||
)
|
||||
return values
|
||||
|
||||
|
||||
def normalize_longitudes(lon_values, fields):
|
||||
lon_values = np.asarray(lon_values, dtype=float)
|
||||
normalized_lon = ((lon_values + 180) % 360) - 180
|
||||
sort_idx = np.argsort(normalized_lon)
|
||||
normalized_lon = normalized_lon[sort_idx]
|
||||
normalized_fields = [field[:, sort_idx] for field in fields]
|
||||
return normalized_lon, normalized_fields
|
||||
|
||||
|
||||
def select_region(lat_values, lon_values, fields):
|
||||
lat_mask = (lat_values >= REGION["lat_min"]) & (lat_values <= REGION["lat_max"])
|
||||
lon_mask = (lon_values >= REGION["lon_min"]) & (lon_values <= REGION["lon_max"])
|
||||
|
||||
lat_idx = np.where(lat_mask)[0]
|
||||
lon_idx = np.where(lon_mask)[0]
|
||||
if lat_idx.size == 0 or lon_idx.size == 0:
|
||||
raise ValueError("selected region is empty after coordinate filtering")
|
||||
|
||||
return (
|
||||
lat_values[lat_idx],
|
||||
lon_values[lon_idx],
|
||||
[field[np.ix_(lat_idx, lon_idx)] for field in fields],
|
||||
)
|
||||
|
||||
|
||||
def process_file(path):
|
||||
datasets = load_datasets(path)
|
||||
try:
|
||||
wave_path = path.with_name(f"{path.stem}_wave.grib2")
|
||||
wave_datasets = load_datasets(wave_path) if wave_path.exists() else []
|
||||
all_datasets = [*datasets, *wave_datasets]
|
||||
|
||||
lat_values, lon_values = get_lat_lon(datasets)
|
||||
lat_size = len(lat_values)
|
||||
lon_size = len(lon_values)
|
||||
|
||||
u10 = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["u10"]), lat_size, lon_size)
|
||||
v10 = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["v10"]), lat_size, lon_size)
|
||||
rain = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["tp"]), lat_size, lon_size)
|
||||
pressure = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["msl"]), lat_size, lon_size)
|
||||
temp = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["temp"]), lat_size, lon_size)
|
||||
wave_h = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_h"]), lat_size, lon_size)
|
||||
wave_dir = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_dir"]), lat_size, lon_size)
|
||||
wave_period = to_2d_values(find_variable(all_datasets, VARIABLE_CANDIDATES["wave_period"]), lat_size, lon_size)
|
||||
|
||||
wind_speed, wind_dir = compute_wind(u10, v10)
|
||||
|
||||
lon_values, normalized_fields = normalize_longitudes(
|
||||
lon_values,
|
||||
[wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period],
|
||||
)
|
||||
wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period = normalized_fields
|
||||
|
||||
lat_region, lon_region, region_fields = select_region(
|
||||
lat_values,
|
||||
lon_values,
|
||||
[wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period],
|
||||
)
|
||||
wind_speed, wind_dir, rain, temp, pressure, wave_h, wave_dir, wave_period = region_fields
|
||||
|
||||
return {
|
||||
"lat": lat_region.tolist(),
|
||||
"lon": lon_region.tolist(),
|
||||
"wind_speed": wind_speed.tolist(),
|
||||
"wind_dir": wind_dir.tolist(),
|
||||
"rain": rain.tolist(),
|
||||
"temp": temp.tolist(),
|
||||
"pressure": pressure.tolist(),
|
||||
"wave_h": wave_h.tolist(),
|
||||
"wave_dir": wave_dir.tolist(),
|
||||
"wave_period": wave_period.tolist(),
|
||||
}
|
||||
finally:
|
||||
for dataset in datasets:
|
||||
dataset.close()
|
||||
if 'wave_datasets' in locals():
|
||||
for dataset in wave_datasets:
|
||||
dataset.close()
|
||||
|
||||
|
||||
def main():
|
||||
for path in sorted(INPUT_DIR.glob("*.grib2")):
|
||||
if path.stem.endswith("_wave"):
|
||||
continue
|
||||
print("processing", path.name)
|
||||
try:
|
||||
grid = process_file(path)
|
||||
except Exception as exc:
|
||||
print(" error processing", path.name, exc)
|
||||
continue
|
||||
|
||||
output_path = OUTPUT_DIR / f"grid_{path.stem}.json"
|
||||
payload = {
|
||||
"time": path.stem,
|
||||
"grid": grid,
|
||||
}
|
||||
with output_path.open("w", encoding="utf-8") as file_handle:
|
||||
json.dump(payload, file_handle)
|
||||
print("saved", output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
104
src/pbf/audit_pbf_mysql.py
Normal file
104
src/pbf/audit_pbf_mysql.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import pymysql
|
||||
import os
|
||||
|
||||
OUTPUT_DIR = "report"
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
conn = pymysql.connect(
|
||||
host="localhost",
|
||||
user="root",
|
||||
password="2chi9ks2",
|
||||
database="pbf_analysis",
|
||||
charset="utf8mb4"
|
||||
)
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
def write_file(name, rows):
|
||||
|
||||
path = os.path.join(OUTPUT_DIR, name)
|
||||
|
||||
with open(path,"w",encoding="utf8") as f:
|
||||
|
||||
for r in rows:
|
||||
f.write("\t".join([str(x) for x in r])+"\n")
|
||||
|
||||
print("write:",path)
|
||||
|
||||
|
||||
print("Layer summary...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT vt_layer, COUNT(*)
|
||||
FROM features
|
||||
GROUP BY vt_layer
|
||||
ORDER BY COUNT(*) DESC
|
||||
""")
|
||||
|
||||
write_file("layer_summary.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("Geometry summary...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT vt_layer, geom_type, COUNT(*)
|
||||
FROM features
|
||||
GROUP BY vt_layer, geom_type
|
||||
ORDER BY vt_layer
|
||||
""")
|
||||
|
||||
write_file("geometry_summary.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("Property keys...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT k, COUNT(*)
|
||||
FROM properties
|
||||
GROUP BY k
|
||||
ORDER BY COUNT(*) DESC
|
||||
""")
|
||||
|
||||
write_file("property_keys.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("Layer properties...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT f.vt_layer, p.k, COUNT(*)
|
||||
FROM properties p
|
||||
JOIN features f ON p.feature_id=f.id
|
||||
GROUP BY f.vt_layer,p.k
|
||||
ORDER BY f.vt_layer
|
||||
""")
|
||||
|
||||
write_file("layer_properties.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("Classification distribution...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT v, COUNT(*)
|
||||
FROM properties
|
||||
WHERE k='分類番号'
|
||||
GROUP BY v
|
||||
ORDER BY COUNT(*) DESC
|
||||
""")
|
||||
|
||||
write_file("classification_distribution.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("Tile density...")
|
||||
|
||||
cur.execute("""
|
||||
SELECT z,x,y,COUNT(*)
|
||||
FROM features
|
||||
GROUP BY z,x,y
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 100
|
||||
""")
|
||||
|
||||
write_file("tile_density.txt",cur.fetchall())
|
||||
|
||||
|
||||
print("done.")
|
||||
5652
src/pbf/style.json
Normal file
5652
src/pbf/style.json
Normal file
File diff suppressed because it is too large
Load Diff
5652
src/pbf/style.karatsu-10nm-compatible.json
Normal file
5652
src/pbf/style.karatsu-10nm-compatible.json
Normal file
File diff suppressed because it is too large
Load Diff
1892
src/pbf/style.karatsu-10nm-v2.json
Normal file
1892
src/pbf/style.karatsu-10nm-v2.json
Normal file
File diff suppressed because it is too large
Load Diff
5652
src/pbf/style.navsea-compatible.json
Normal file
5652
src/pbf/style.navsea-compatible.json
Normal file
File diff suppressed because it is too large
Load Diff
1391
src/pbf/style.navsea-delivery-karatsu-10nm.json
Normal file
1391
src/pbf/style.navsea-delivery-karatsu-10nm.json
Normal file
File diff suppressed because it is too large
Load Diff
1391
src/pbf/style.navsea-engineering-karatsu-10nm.json
Normal file
1391
src/pbf/style.navsea-engineering-karatsu-10nm.json
Normal file
File diff suppressed because it is too large
Load Diff
692
src/pbf/style.navsea-redesign.json
Normal file
692
src/pbf/style.navsea-redesign.json
Normal file
@@ -0,0 +1,692 @@
|
||||
{
|
||||
"version": 8,
|
||||
"name": "navsea-redesign",
|
||||
"glyphs": "http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf",
|
||||
"sources": {
|
||||
"navsea": {
|
||||
"type": "vector",
|
||||
"minzoom": 0,
|
||||
"maxzoom": 12,
|
||||
"tiles": [
|
||||
"http://192.168.200.184/pbf/{z}/{x}/{y}.pbf"
|
||||
],
|
||||
"attribution": "NavSea semantic vector tiles"
|
||||
}
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#dfe8d8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-area",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P陸域",
|
||||
"paint": {
|
||||
"fill-color": "#d9cfab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-hole",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P穴",
|
||||
"paint": {
|
||||
"fill-color": "#bfc8b1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "water-depth-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_family"
|
||||
],
|
||||
[
|
||||
"surface"
|
||||
],
|
||||
true,
|
||||
false
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"0-2m",
|
||||
"#d8f3f6",
|
||||
"0-5m",
|
||||
"#d2eff4",
|
||||
"2-5m",
|
||||
"#c8eaf0",
|
||||
"5-10m",
|
||||
"#bce3ec",
|
||||
"10-20m",
|
||||
"#b0dbe8",
|
||||
"20-100m",
|
||||
"#a0d2e0",
|
||||
"100-200m",
|
||||
"#93c7d6",
|
||||
"200-500m",
|
||||
"#7eb7ca",
|
||||
"500-1000m",
|
||||
"#68a6bc",
|
||||
"1000-2000m",
|
||||
"#558eab",
|
||||
"#dff3f7"
|
||||
],
|
||||
"fill-opacity": 0.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "shoal-hazard-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"==",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"浅所危険界"
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#f2a65a",
|
||||
"fill-opacity": 0.45
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tidal-flat-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"==",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"干潮帯"
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#d7d0b4",
|
||||
"fill-opacity": 0.75
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "river-lake-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
[
|
||||
"河川域",
|
||||
"湖沼域",
|
||||
"陸上水域"
|
||||
],
|
||||
true,
|
||||
false
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#b8d8e8",
|
||||
"fill-opacity": 0.85
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "unsurveyed-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"==",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"未測海域"
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#f7f0b8",
|
||||
"fill-opacity": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coast-structure-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
[
|
||||
"防波堤",
|
||||
"浮施設・桟橋",
|
||||
"撤去跡"
|
||||
],
|
||||
true,
|
||||
false
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": "#8d8578",
|
||||
"fill-opacity": 0.85
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bridge-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P橋りょう等構造物",
|
||||
"paint": {
|
||||
"fill-color": "#736b5f",
|
||||
"fill-opacity": 0.85
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fishery-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P漁具定置箇所",
|
||||
"paint": {
|
||||
"fill-color": "#89b26f",
|
||||
"fill-opacity": 0.45,
|
||||
"fill-outline-color": "#56764a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-structure-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P陸上構造物陸",
|
||||
"paint": {
|
||||
"fill-color": "#8e6f3e",
|
||||
"fill-opacity": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coast-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線ククリ",
|
||||
"paint": {
|
||||
"line-color": "#736b5f",
|
||||
"line-width": 1.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "danger-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P危険界ククリ",
|
||||
"paint": {
|
||||
"line-color": "#c95c2a",
|
||||
"line-width": 1.3,
|
||||
"line-dasharray": [
|
||||
2,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "anchor-danger-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P投錨注意障害物ククリ",
|
||||
"paint": {
|
||||
"line-color": "#c03d3d",
|
||||
"line-width": 1.1,
|
||||
"line-dasharray": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bathymetry-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L海底地形",
|
||||
"paint": {
|
||||
"line-color": "#7fa6b7",
|
||||
"line-width": 0.5,
|
||||
"line-opacity": 0.45
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "depth-contours",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L等深線",
|
||||
"paint": {
|
||||
"line-color": "#487b94",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
5,
|
||||
0.3,
|
||||
8,
|
||||
0.6,
|
||||
12,
|
||||
1.1
|
||||
],
|
||||
"line-opacity": 0.85
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "subsea-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L海底線",
|
||||
"paint": {
|
||||
"line-color": "#8d4f9c",
|
||||
"line-width": 1.2,
|
||||
"line-dasharray": [
|
||||
3,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coast-structure-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L基本線",
|
||||
"paint": {
|
||||
"line-color": "#60584d",
|
||||
"line-width": 1.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-structure-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L陸上構造物陸",
|
||||
"paint": {
|
||||
"line-color": "#805f2c",
|
||||
"line-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "water-boundary-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L739",
|
||||
"paint": {
|
||||
"line-color": "#4b89a5",
|
||||
"line-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "regulatory-boundary-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L741",
|
||||
"paint": {
|
||||
"line-color": "#cf5b47",
|
||||
"line-width": 1.2,
|
||||
"line-dasharray": [
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "height-limit-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L高さ制限",
|
||||
"paint": {
|
||||
"line-color": "#b1466b",
|
||||
"line-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-area-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P投錨注意障害物",
|
||||
"paint": {
|
||||
"fill-color": "#d85f4c",
|
||||
"fill-opacity": 0.42,
|
||||
"fill-outline-color": "#9b3324"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航行危険障害物",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#d5452f",
|
||||
"circle-stroke-color": "#fff4df",
|
||||
"circle-stroke-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "anchor-hazard-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p投錨注意障害物",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#cf7d2b",
|
||||
"circle-stroke-color": "#fff4df",
|
||||
"circle-stroke-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nav-marks",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航路標識群",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#f6ce4f",
|
||||
"circle-stroke-color": "#313131",
|
||||
"circle-stroke-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facility-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p施設・境界線等",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
8,
|
||||
2,
|
||||
12,
|
||||
5
|
||||
],
|
||||
"circle-color": "#4d6b86",
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "landmark-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p陸上構造物",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
8,
|
||||
2,
|
||||
12,
|
||||
4.5
|
||||
],
|
||||
"circle-color": "#6b5840",
|
||||
"circle-stroke-color": "#f7f1e2",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bottom-material-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p底質",
|
||||
"paint": {
|
||||
"circle-radius": 1.5,
|
||||
"circle-color": "#3f7183",
|
||||
"circle-opacity": 0.55
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "height-limit-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p高さ制限",
|
||||
"paint": {
|
||||
"circle-radius": 3,
|
||||
"circle-color": "#a83566",
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sea-place-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p地名",
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"日本語地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"英文字地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"name"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSerifCJK-Regular"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
14
|
||||
],
|
||||
"text-anchor": "center",
|
||||
"text-max-width": 10
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#284857",
|
||||
"text-halo-color": "#f6fbff",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-place-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p地名陸",
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"日本語地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"英文字地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"name"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSerifCJK-Regular"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
14
|
||||
],
|
||||
"text-anchor": "center",
|
||||
"text-max-width": 10
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#3a3327",
|
||||
"text-halo-color": "#fff9ef",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nav-object-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航路標識群",
|
||||
"minzoom": 10,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"名称"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"name"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSansCJK-Regular"
|
||||
],
|
||||
"text-size": 11,
|
||||
"text-offset": [
|
||||
0,
|
||||
1.1
|
||||
],
|
||||
"text-anchor": "top",
|
||||
"text-max-width": 12
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#2f2f2f",
|
||||
"text-halo-color": "#fff8e0",
|
||||
"text-halo-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航行危険障害物",
|
||||
"minzoom": 10,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"名称"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSansCJK-Regular"
|
||||
],
|
||||
"text-size": 11,
|
||||
"text-offset": [
|
||||
0,
|
||||
1.1
|
||||
],
|
||||
"text-anchor": "top"
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#7f221a",
|
||||
"text-halo-color": "#fff3eb",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
679
src/pbf/style.navsea-semantic-only.json
Normal file
679
src/pbf/style.navsea-semantic-only.json
Normal file
@@ -0,0 +1,679 @@
|
||||
{
|
||||
"version": 8,
|
||||
"name": "navsea-semantic-only",
|
||||
"glyphs": "http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf",
|
||||
"sources": {
|
||||
"navsea": {
|
||||
"type": "vector",
|
||||
"minzoom": 0,
|
||||
"maxzoom": 12,
|
||||
"tiles": [
|
||||
"http://192.168.200.184/pbf/{z}/{x}/{y}.pbf"
|
||||
],
|
||||
"attribution": "NavSea semantic vector tiles"
|
||||
}
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "#edf3ef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P陸域",
|
||||
"paint": {
|
||||
"fill-color": "#d8cda8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hole-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P穴",
|
||||
"paint": {
|
||||
"fill-color": "#c7cfbb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "surface-water",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
[
|
||||
"0-2m",
|
||||
"0-5m",
|
||||
"2-5m",
|
||||
"5-10m",
|
||||
"10-20m",
|
||||
"20-100m",
|
||||
"100-200m",
|
||||
"200-500m",
|
||||
"500-1000m",
|
||||
"1000-2000m",
|
||||
"2000-3000m",
|
||||
"3000-4000m",
|
||||
"4000-5000m",
|
||||
"5000-6000m",
|
||||
"6000-7000m",
|
||||
"7000-8000m",
|
||||
"8000-9000m",
|
||||
"9000m以深",
|
||||
"干潮帯",
|
||||
"河川域",
|
||||
"湖沼域",
|
||||
"陸上水域"
|
||||
],
|
||||
true,
|
||||
false
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"干潮帯",
|
||||
"#d7cfb4",
|
||||
"河川域",
|
||||
"#afd8e7",
|
||||
"湖沼域",
|
||||
"#afd8e7",
|
||||
"陸上水域",
|
||||
"#afd8e7",
|
||||
"0-2m",
|
||||
"#d8f1f4",
|
||||
"0-5m",
|
||||
"#d0edf1",
|
||||
"2-5m",
|
||||
"#c4e7ed",
|
||||
"5-10m",
|
||||
"#b6dfe8",
|
||||
"10-20m",
|
||||
"#a8d6e1",
|
||||
"20-100m",
|
||||
"#96cbd8",
|
||||
"100-200m",
|
||||
"#84bfd0",
|
||||
"200-500m",
|
||||
"#71afc2",
|
||||
"500-1000m",
|
||||
"#629fb4",
|
||||
"1000-2000m",
|
||||
"#548ea4",
|
||||
"2000-3000m",
|
||||
"#497c93",
|
||||
"3000-4000m",
|
||||
"#3f6c84",
|
||||
"4000-5000m",
|
||||
"#365e75",
|
||||
"5000-6000m",
|
||||
"#2f5268",
|
||||
"6000-7000m",
|
||||
"#28475d",
|
||||
"7000-8000m",
|
||||
"#213d52",
|
||||
"8000-9000m",
|
||||
"#1d3448",
|
||||
"#182c3f"
|
||||
],
|
||||
"fill-opacity": 0.88
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-area",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線",
|
||||
"filter": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
[
|
||||
"浅所危険界",
|
||||
"未測海域",
|
||||
"浮施設・桟橋",
|
||||
"防波堤",
|
||||
"撤去跡"
|
||||
],
|
||||
true,
|
||||
false
|
||||
],
|
||||
"paint": {
|
||||
"fill-color": [
|
||||
"match",
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
],
|
||||
"浅所危険界",
|
||||
"#ee9f56",
|
||||
"未測海域",
|
||||
"#eadf9b",
|
||||
"浮施設・桟橋",
|
||||
"#88857d",
|
||||
"防波堤",
|
||||
"#7d796f",
|
||||
"#9d7c67"
|
||||
],
|
||||
"fill-opacity": 0.45
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fishery-area",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P漁具定置箇所",
|
||||
"paint": {
|
||||
"fill-color": "#7db065",
|
||||
"fill-opacity": 0.38,
|
||||
"fill-outline-color": "#507541"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-polygon",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P投錨注意障害物",
|
||||
"paint": {
|
||||
"fill-color": "#d15d49",
|
||||
"fill-opacity": 0.4,
|
||||
"fill-outline-color": "#8b3226"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bridge-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P橋りょう等構造物",
|
||||
"paint": {
|
||||
"fill-color": "#71685b",
|
||||
"fill-opacity": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-structure-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P陸上構造物陸",
|
||||
"paint": {
|
||||
"fill-color": "#896d3d",
|
||||
"fill-opacity": 0.82
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "submerged-fill",
|
||||
"type": "fill",
|
||||
"source": "navsea",
|
||||
"source-layer": "P潜堤",
|
||||
"paint": {
|
||||
"fill-color": "#757d95",
|
||||
"fill-opacity": 0.45
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "depth-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L等深線",
|
||||
"paint": {
|
||||
"line-color": "#447a94",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
5,
|
||||
0.3,
|
||||
8,
|
||||
0.6,
|
||||
12,
|
||||
1.1
|
||||
],
|
||||
"line-opacity": 0.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "major-depth-lines",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L概略等深線",
|
||||
"paint": {
|
||||
"line-color": "#275f77",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
5,
|
||||
0.5,
|
||||
8,
|
||||
0.9,
|
||||
12,
|
||||
1.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bathymetry-support",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L海底地形",
|
||||
"paint": {
|
||||
"line-color": "#8aadb9",
|
||||
"line-width": 0.45,
|
||||
"line-opacity": 0.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cable-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L海底線",
|
||||
"paint": {
|
||||
"line-color": "#7d4a94",
|
||||
"line-width": 1.2,
|
||||
"line-dasharray": [
|
||||
3,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coast-structure-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L基本線",
|
||||
"paint": {
|
||||
"line-color": "#635d53",
|
||||
"line-width": 1.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-structure-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L陸上構造物陸",
|
||||
"paint": {
|
||||
"line-color": "#7b5d2e",
|
||||
"line-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "boundary-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L739",
|
||||
"paint": {
|
||||
"line-color": "#4f8ba5",
|
||||
"line-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "regulatory-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L741",
|
||||
"paint": {
|
||||
"line-color": "#c95b46",
|
||||
"line-width": 1.2,
|
||||
"line-dasharray": [
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "height-limit-line",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "L高さ制限",
|
||||
"paint": {
|
||||
"line-color": "#a93a62",
|
||||
"line-width": 1.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coast-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P基本線ククリ",
|
||||
"paint": {
|
||||
"line-color": "#7d7566",
|
||||
"line-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "danger-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P危険界ククリ",
|
||||
"paint": {
|
||||
"line-color": "#c35f31",
|
||||
"line-width": 1.2,
|
||||
"line-dasharray": [
|
||||
2,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "anchor-outline",
|
||||
"type": "line",
|
||||
"source": "navsea",
|
||||
"source-layer": "P投錨注意障害物ククリ",
|
||||
"paint": {
|
||||
"line-color": "#b64335",
|
||||
"line-width": 1.1,
|
||||
"line-dasharray": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航行危険障害物",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#d1442f",
|
||||
"circle-stroke-color": "#fff4ea",
|
||||
"circle-stroke-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "anchor-hazard-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p投錨注意障害物",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#d18a38",
|
||||
"circle-stroke-color": "#fff6e7",
|
||||
"circle-stroke-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nav-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航路標識群",
|
||||
"paint": {
|
||||
"circle-radius": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
7,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"circle-color": "#f1cb4d",
|
||||
"circle-stroke-color": "#313131",
|
||||
"circle-stroke-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "facility-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p施設・境界線等",
|
||||
"paint": {
|
||||
"circle-radius": 3.5,
|
||||
"circle-color": "#4f6b86",
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bottom-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p底質",
|
||||
"paint": {
|
||||
"circle-radius": 1.8,
|
||||
"circle-color": "#3d7182",
|
||||
"circle-opacity": 0.65
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "landmark-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p陸上構造物",
|
||||
"paint": {
|
||||
"circle-radius": 3,
|
||||
"circle-color": "#6a5640",
|
||||
"circle-stroke-color": "#fff7eb",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "height-points",
|
||||
"type": "circle",
|
||||
"source": "navsea",
|
||||
"source-layer": "p高さ制限",
|
||||
"paint": {
|
||||
"circle-radius": 3,
|
||||
"circle-color": "#aa365f",
|
||||
"circle-stroke-color": "#ffffff",
|
||||
"circle-stroke-width": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sea-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p地名",
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"日本語地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"英文字地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSerifCJK-Regular"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
15
|
||||
],
|
||||
"text-anchor": "center",
|
||||
"text-max-width": 10
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#264858",
|
||||
"text-halo-color": "#f8fcff",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "land-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p地名陸",
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"日本語地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"英文字地名"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSerifCJK-Regular"
|
||||
],
|
||||
"text-size": [
|
||||
"interpolate",
|
||||
[
|
||||
"linear"
|
||||
],
|
||||
[
|
||||
"zoom"
|
||||
],
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
15
|
||||
],
|
||||
"text-anchor": "center",
|
||||
"text-max-width": 10
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#3a3228",
|
||||
"text-halo-color": "#fff8ef",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nav-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航路標識群",
|
||||
"minzoom": 10,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"名称"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSansCJK-Regular"
|
||||
],
|
||||
"text-size": 11,
|
||||
"text-offset": [
|
||||
0,
|
||||
1.1
|
||||
],
|
||||
"text-anchor": "top"
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#303030",
|
||||
"text-halo-color": "#fff8df",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hazard-labels",
|
||||
"type": "symbol",
|
||||
"source": "navsea",
|
||||
"source-layer": "p航行危険障害物",
|
||||
"minzoom": 10,
|
||||
"layout": {
|
||||
"text-field": [
|
||||
"coalesce",
|
||||
[
|
||||
"get",
|
||||
"名称"
|
||||
],
|
||||
[
|
||||
"get",
|
||||
"canonical_object_type"
|
||||
]
|
||||
],
|
||||
"text-font": [
|
||||
"NotoSansCJK-Regular"
|
||||
],
|
||||
"text-size": 11,
|
||||
"text-offset": [
|
||||
0,
|
||||
1.1
|
||||
],
|
||||
"text-anchor": "top"
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#7c2219",
|
||||
"text-halo-color": "#fff4ec",
|
||||
"text-halo-width": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
1391
src/pbf/style.navsea-v2.json
Normal file
1391
src/pbf/style.navsea-v2.json
Normal file
File diff suppressed because it is too large
Load Diff
2232
src/pbf/style.pruned.json
Normal file
2232
src/pbf/style.pruned.json
Normal file
File diff suppressed because it is too large
Load Diff
136
src/vector_tile_generator.py
Normal file
136
src/vector_tile_generator.py
Normal 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()
|
||||
86
src/weather_pipeline.py
Normal file
86
src/weather_pipeline.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
DOWNLOADER = SCRIPT_DIR / "gfs_downloader.py"
|
||||
GRID_BUILDER = SCRIPT_DIR / "grid_builder_v2.py"
|
||||
TILE_GENERATOR = SCRIPT_DIR / "vector_tile_generator.py"
|
||||
|
||||
STEP_DEPENDENCIES = {
|
||||
"GFS Downloader": ("requests",),
|
||||
"Grid Builder v2": ("numpy", "cfgrib"),
|
||||
"Vector Tile Generator": ("mercantile", "mapbox_vector_tile"),
|
||||
}
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
missing = []
|
||||
for step_name, modules in STEP_DEPENDENCIES.items():
|
||||
missing_modules = [module for module in modules if importlib.util.find_spec(module) is None]
|
||||
if missing_modules:
|
||||
missing.append(f"{step_name}: {', '.join(missing_modules)}")
|
||||
return missing
|
||||
|
||||
|
||||
def run_step(name, script_path):
|
||||
print("\n==========================")
|
||||
print("Running:", name)
|
||||
print("==========================\n")
|
||||
|
||||
start = time.time()
|
||||
command = [sys.executable, "-u", str(script_path)]
|
||||
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
cwd=str(SCRIPT_DIR.parent),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
|
||||
assert process.stdout is not None
|
||||
for line in process.stdout:
|
||||
print(line, end="")
|
||||
|
||||
return_code = process.wait()
|
||||
if return_code != 0:
|
||||
raise RuntimeError(f"{name} failed with exit code {return_code}")
|
||||
|
||||
end = time.time()
|
||||
print("\nFinished:", name)
|
||||
print("Time:", round(end - start, 2), "seconds")
|
||||
|
||||
|
||||
def main():
|
||||
print("Weather Pipeline Starting...")
|
||||
print("Date:", time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
missing_dependencies = check_dependencies()
|
||||
if missing_dependencies:
|
||||
print("\n" + "!" * 50)
|
||||
print("Pipeline Failed: missing runtime dependencies")
|
||||
for item in missing_dependencies:
|
||||
print("-", item)
|
||||
print("!" * 50)
|
||||
raise SystemExit(1)
|
||||
|
||||
try:
|
||||
run_step("GFS Downloader", DOWNLOADER)
|
||||
run_step("Grid Builder v2", GRID_BUILDER)
|
||||
run_step("Vector Tile Generator", TILE_GENERATOR)
|
||||
print("\n" + "=" * 50)
|
||||
print("Weather Pipeline Completed Successfully!")
|
||||
print("=" * 50)
|
||||
except RuntimeError as exc:
|
||||
print("\n" + "!" * 50)
|
||||
print("Pipeline Failed:", str(exc))
|
||||
print("!" * 50)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user