|
| 1 | +import marimo |
| 2 | + |
| 3 | +__generated_with = "0.16.5" |
| 4 | +app = marimo.App(width="medium") |
| 5 | + |
| 6 | + |
| 7 | +@app.cell |
| 8 | +def _(): |
| 9 | + import logging |
| 10 | + from argparse import ArgumentParser |
| 11 | + from pathlib import Path |
| 12 | + |
| 13 | + import earthkit.plots as ekp |
| 14 | + import matplotlib.pyplot as plt |
| 15 | + import numpy as np |
| 16 | + import pandas as pd |
| 17 | + import xarray as xr |
| 18 | + |
| 19 | + from meteodatalab import data_source, grib_decoder |
| 20 | + return ( |
| 21 | + ArgumentParser, |
| 22 | + Path, |
| 23 | + data_source, |
| 24 | + grib_decoder, |
| 25 | + logging, |
| 26 | + np, |
| 27 | + pd, |
| 28 | + plt, |
| 29 | + xr, |
| 30 | + ) |
| 31 | + |
| 32 | + |
| 33 | +@app.cell |
| 34 | +def _(logging): |
| 35 | + LOG = logging.getLogger(__name__) |
| 36 | + LOG_FMT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
| 37 | + logging.basicConfig(level=logging.INFO, format=LOG_FMT) |
| 38 | + return |
| 39 | + |
| 40 | + |
| 41 | +@app.cell |
| 42 | +def _(ArgumentParser, Path): |
| 43 | + ROOT = Path(__file__).parent |
| 44 | + |
| 45 | + parser = ArgumentParser() |
| 46 | + |
| 47 | + parser.add_argument( |
| 48 | + "--forecast", type=str, default=None, help="Directory to forecast grib data" |
| 49 | + ) |
| 50 | + parser.add_argument( |
| 51 | + "--analysis", type=str, default=None, help="Path to analysis zarr data" |
| 52 | + ) |
| 53 | + parser.add_argument("--date", type=str, default=None, help="reference datetime") |
| 54 | + parser.add_argument("--outfn", type=str, help="output filename") |
| 55 | + parser.add_argument("--param", type=str, help="parameter") |
| 56 | + parser.add_argument("--station", type=str, help="station") |
| 57 | + |
| 58 | + args = parser.parse_args() |
| 59 | + grib_dir = Path(args.forecast) |
| 60 | + zarr_dir = Path(args.analysis) |
| 61 | + init_time = args.date |
| 62 | + outfn = Path(args.outfn) |
| 63 | + station = args.station |
| 64 | + param = args.param |
| 65 | + return grib_dir, init_time, outfn, param, station, zarr_dir |
| 66 | + |
| 67 | + |
| 68 | +@app.cell |
| 69 | +def _(pd): |
| 70 | + stations = pd.DataFrame( |
| 71 | + [ |
| 72 | + ("BAS", "1_75", 1, 75, "BAS", "Basel / Binningen", 1, "MeteoSchweiz", 7.583, 47.541, 316.14, 12.0, 0.0), |
| 73 | + ("LUG", "1_47", 1, 47, "LUG", "Lugano", 1, "MeteoSchweiz", 8.960, 46.004, 272.56, 10.0, 27.34), |
| 74 | + ("GVE", "1_58", 1, 58, "GVE", "Gen\u00e8ve / Cointrin", 1, "MeteoSchweiz", 6.122, 46.248, 415.53, 10.0, 0.0), |
| 75 | + ("GUT", "1_79", 1, 79, "GUT", "G\u00fcttingen", 1, "MeteoSchweiz", 9.279, 47.602, 439.78, 12.0, 0.0), |
| 76 | + ("KLO", "1_59", 1, 59, "KLO", "Z\u00fcrich / Kloten", 1, "MeteoSchweiz", 8.536, 47.48, 435.92, 10.5, 0.0), |
| 77 | + ("SCU", "1_30", 1, 30, "SCU", "Scuol", 1, "MeteoSchweiz", 10.283, 46.793, 1304.42, 10.0, 0.0), |
| 78 | + ("LUZ", "1_68", 1, 68, "LUZ", "Luzern", 1, "MeteoSchweiz", 8.301, 47.036, 454.0, 8.41, 32.51), |
| 79 | + ("DIS", "1_54", 1, 54, "DIS", "Disentis", 1, "MeteoSchweiz", 8.853, 46.707, 1198.03, 10.0, 0.0), |
| 80 | + ("PMA", "1_862", 1, 862, "PMA", "Piz Martegnas", 1, "MeteoSchweiz", 9.529, 46.577, 2668.34, 10.0, 0.0), |
| 81 | + ("CEV", "1_843", 1, 843, "CEV", "Cevio", 1, "MeteoSchweiz", 8.603, 46.32, 420.0, 10.0, 6.85), |
| 82 | + ("MLS", "1_38", 1, 38, "MLS", "Le Mol\u00e9son", 1, "MeteoSchweiz", 7.018, 46.546, 1977.0, 10.0, 13.31), |
| 83 | + ("PAY", "1_32", 1, 32, "PAY", "Payerne", 1, "MeteoSchweiz", 6.942, 46.811, 489.17, 10.0, 0.0), |
| 84 | + ("NAP", "1_48", 1, 48, "NAP", "Napf", 1, "MeteoSchweiz", 7.94, 47.005, 1404.03, 15.0, 0.0), |
| 85 | + |
| 86 | + ], |
| 87 | + columns=[ |
| 88 | + "station", |
| 89 | + "name", |
| 90 | + "type_id", |
| 91 | + "point_id", |
| 92 | + "nat_abbr", |
| 93 | + "fullname", |
| 94 | + "owner_id", |
| 95 | + "owner_name", |
| 96 | + "longitude", |
| 97 | + "latitude", |
| 98 | + "height_masl", |
| 99 | + "pole_height", |
| 100 | + "roof_height", |
| 101 | + ], |
| 102 | + ) |
| 103 | + return (stations,) |
| 104 | + |
| 105 | + |
| 106 | +@app.cell |
| 107 | +def load_grib_data(data_source, grib_decoder, grib_dir, init_time, param): |
| 108 | + if param == "10sp": |
| 109 | + paramlist = ["10u", "10v"] |
| 110 | + elif param == "sp": |
| 111 | + paramlist = ["u", "v"] |
| 112 | + else: |
| 113 | + paramlist = [param] |
| 114 | + |
| 115 | + grib_files = sorted(grib_dir.glob(f"{init_time}*.grib")) |
| 116 | + fds = data_source.FileDataSource(datafiles=grib_files) |
| 117 | + ds_grib = grib_decoder.load(fds, {"param": paramlist}) |
| 118 | + ds_grib = ds_grib[param].squeeze() |
| 119 | + ds_grib |
| 120 | + return ds_grib, paramlist |
| 121 | + |
| 122 | + |
| 123 | +@app.cell |
| 124 | +def load_zarr_data(ds_grib, np, param, paramlist, xr, zarr_dir): |
| 125 | + ds_zarr = xr.open_zarr(zarr_dir, consolidated=False) |
| 126 | + ds_zarr = ds_zarr.set_index(time="dates") |
| 127 | + ds_zarr = ds_zarr.sel(time=ds_grib.valid_time.values) |
| 128 | + ds_zarr = ds_zarr.assign_coords({"variable": ds_zarr.attrs["variables"]}) |
| 129 | + ds_zarr = ds_zarr.sel(variable=[p for p in paramlist]).squeeze("ensemble", drop=True) |
| 130 | + |
| 131 | + # recover original 2D shape |
| 132 | + ny, nx = ds_zarr.attrs["field_shape"] |
| 133 | + y_idx, x_idx = np.unravel_index(np.arange(ny * nx), shape=(ny, nx)) |
| 134 | + ds_zarr = ds_zarr.assign_coords({"y": ("cell", y_idx), "x": ("cell", x_idx)}) |
| 135 | + ds_zarr = ds_zarr.set_index(cell=("y", "x")) |
| 136 | + ds_zarr = ds_zarr.unstack("cell") |
| 137 | + |
| 138 | + # set lat lon as coords |
| 139 | + ds_zarr = ds_zarr.rename({"latitudes": "lat", "longitudes": "lon"}) |
| 140 | + ds_zarr = ds_zarr.set_coords(["lat", "lon"]) |
| 141 | + ds_zarr = ds_zarr.sel(variable=param).squeeze()["data"] |
| 142 | + ds_zarr |
| 143 | + return (ds_zarr,) |
| 144 | + |
| 145 | + |
| 146 | +@app.cell |
| 147 | +def _(ds_grib, ds_zarr, np, pd, stations): |
| 148 | + def nearest_yx_euclid(ds_grib, lat_s, lon_s): |
| 149 | + """ |
| 150 | + Return (y_idx, x_idx) of the grid point nearest to (lat_s, lon_s) |
| 151 | + using Euclidean distance in degrees. |
| 152 | + """ |
| 153 | + lat2d = ds_grib['lat'] # (y, x) |
| 154 | + lon2d = ds_grib['lon'] # (y, x) |
| 155 | + |
| 156 | + dist2 = (lat2d - lat_s)**2 + (lon2d - lon_s)**2 |
| 157 | + flat_idx = np.nanargmin(dist2.values) |
| 158 | + y_idx, x_idx = np.unravel_index(flat_idx, dist2.shape) |
| 159 | + return int(y_idx), int(x_idx) |
| 160 | + |
| 161 | + def get_grib_idx_row(row): |
| 162 | + return nearest_yx_euclid(ds_grib, row['latitude'], row['longitude']) |
| 163 | + idxs_grib = stations.apply(get_grib_idx_row, axis=1, result_type='expand') |
| 164 | + idxs_grib.columns = ['grib_y_idx', 'grib_x_idx'] |
| 165 | + |
| 166 | + def get_zarr_idx_row(row): |
| 167 | + return nearest_yx_euclid(ds_zarr, row['latitude'], row['longitude']) |
| 168 | + idxs_zarr = stations.apply(get_zarr_idx_row, axis=1, result_type='expand') |
| 169 | + idxs_zarr.columns = ['zarr_y_idx', 'zarr_x_idx'] |
| 170 | + |
| 171 | + sta_idxs = pd.concat([stations, idxs_grib, idxs_zarr], axis=1).set_index("station") |
| 172 | + sta_idxs |
| 173 | + return (sta_idxs,) |
| 174 | + |
| 175 | + |
| 176 | +@app.cell |
| 177 | +def _(ds_grib, ds_zarr, outfn, plt, sta_idxs, station): |
| 178 | + grib_x_idx, grib_y_idx = sta_idxs.loc[station].grib_x_idx, sta_idxs.loc[station].grib_y_idx |
| 179 | + zarr_x_idx, zarr_y_idx = sta_idxs.loc[station].zarr_x_idx, sta_idxs.loc[station].zarr_y_idx |
| 180 | + |
| 181 | + # analysis |
| 182 | + ds_zarr.isel(x=zarr_x_idx, y=zarr_y_idx).plot(x="time", label="analysis", color="k", ls="--") |
| 183 | + |
| 184 | + # TODO: plot actual forecaster data... |
| 185 | + ds_grib.isel(x=grib_x_idx, y=grib_y_idx).isel(lead_time=list(range(0, 126, 6))).plot(x="valid_time", marker="o", linestyle='None', color="k", label="forecaster") |
| 186 | + |
| 187 | + # interpolator |
| 188 | + ds_grib.isel(x=grib_x_idx, y=grib_y_idx).plot(x="valid_time", label="interpolator") |
| 189 | + |
| 190 | + plt.legend() |
| 191 | + plt.ylabel(f"{ds_grib.attrs["parameter"]["shortName"]} ({ds_grib.attrs["parameter"]["units"]})") |
| 192 | + plt.title(f"{init_time} {ds_grib.attrs["parameter"]["name"]} at {station}") |
| 193 | + plt.savefig(outfn) |
| 194 | + return |
| 195 | + |
| 196 | + |
| 197 | +if __name__ == "__main__": |
| 198 | + app.run() |
0 commit comments