Four ready-to-run starting points — a live map like our explorer, Power BI, Excel, and a Python dashboard. Each one is a single downloadable file with the API key kept out of the URL, ready to edit into your own tooling.
The explorer's core in one readable HTML file: binary grid → GPU texture, time slider, click-for-forecast.
One row per site per day: min/max/mean °C for every location in your sites table, refreshable.
A wide table — valid_time plus a column per variable — straight into a workbook.
Line plots per site and a temperature map panel, generated by one uv run.
The whole trick of the explorer fits in one file: ask
/grid?format=bin for a step, decode the 72-byte header + float32 payload into a typed
array, colourise it onto a canvas, and hand that canvas to deck.gl as one GPU texture.
Scrubbing through forecast steps is just texture swaps — no tiles, no vector data, no build step.
API_BASE (and your key)./forecast call your backend would make, shown inline.// binary grid decode (matches /grid?format=bin)
function parseGrid(buf){
const dv = new DataView(buf);
const g = {
nlat: dv.getUint32(8,true), nlon: dv.getUint32(12,true),
lat0: dv.getFloat64(24,true), dlat: dv.getFloat64(32,true),
lon0: dv.getFloat64(40,true), dlon: dv.getFloat64(48,true),
vmin: dv.getFloat32(56,true), vmax: dv.getFloat32(60,true),
};
g.values = new Float32Array(buf, 72, g.nlat * g.nlon); // zero-copy
return g;
}
// …colourise into a canvas, then ONE BitmapLayer for the whole planet:
new deck.BitmapLayer({
image: art.canvas, bounds: art.bounds, opacity: 0.65,
// equirectangular source — deck reprojects per fragment
_imageCoordinateSystem: deck.COORDINATE_SYSTEM.LNGLAT,
textureParameters: { minFilter: 'linear', magFilter: 'linear' },
})Quota: one native global frame ≈ 4 MB decoded; use max_cells or a
region= crop while developing to stay light. Pin a cycle in the path for an immutable,
endlessly cacheable demo.
A Power Query function that calls /forecast/daily once per row of a
sites table and expands the result: one row per site per forecast day with min/max/mean in °C.
Drop Lat/Lon on a map visual, Date on an axis, and you have a weather panel in your existing report.
ApiKey (make it a parameter for the service) and replace the inline Sites table with yours.GetDaily = (lat as number, lon as number) as table =>
let
Resp = Json.Document(Web.Contents(ApiBase, [
RelativePath = "v1/gfs/latest/forecast/daily",
Query = [ lat = Number.ToText(lat), lon = Number.ToText(lon), #"var" = "t2m" ],
Headers = [#"Authorization" = "Bearer " & ApiKey]
])),
Days = Table.FromRecords(Resp[daily]),
Celsius = Table.TransformColumns(Days, {
{"min", each _ - 273.15, type number},
{"max", each _ - 273.15, type number},
{"mean", each _ - 273.15, type number}
})
in CelsiusQuota: one call per site per refresh (32 KiB each) — a 50-site report refreshed hourly uses ~37 MB/day, inside a Developer plan.
Discovers the live variable list from /vars, fetches the full
209-step series for each surface variable at your coordinates, and joins them on
valid_time — a wide, analysis-ready sheet: one row per forecast hour, one column per
variable. Refresh = one click.
Lat/Lon and your ApiKey; Close & Load.=[t2m]-273.15 for °C) — units come from /vars.SurfaceVars = List.Transform(
List.Select(VarsResp, each [kind] = "surface"), each [name]),
GetSeries = (v as text) as table =>
let Resp = Json.Document(Web.Contents(ApiBase, [
RelativePath = "v1/gfs/latest/forecast",
Query = [ lat = Number.ToText(Lat), lon = Number.ToText(Lon),
#"var" = v, interp = "bilinear" ],
Headers = Auth ]))
in Table.FromColumns({Resp[valid_times], Resp[values]}, {"valid_time", v}),
Joined = List.Accumulate(List.Skip(Tables, 1), Tables{0},
(acc, t) => Table.Join(acc, "valid_time", t, "valid_time"))Quota: ~7 point calls per refresh — about 230 KiB. You could refresh this sheet 40 times a day on the free tier.
One script, no project setup: the PEP 723 header lets uv run resolve
requests, pandas and plotly on the fly. It pulls multi-variable
series for your sites, derives wind speed from u/v, converts units, fetches a coarsened global
temperature field, and writes a dark-mode HTML dashboard.
WEATHERLABS_API_KEY=wl_live_… uv run weatherlabs_dashboard.pySITES list — everything else adapts (titles, legends, map pins).weatherlabs_dashboard.html — or schedule the script and publish the output.def point_df(lat, lon, var):
fc = get('/forecast', lat=lat, lon=lon, var=var, interp='bilinear')
return pd.DataFrame({'time': pd.to_datetime(fc['valid_times']), var: fc['values']})
for name, lat, lon in SITES:
df = (point_df(lat, lon, 't2m')
.merge(point_df(lat, lon, 'u10'), on='time')
.merge(point_df(lat, lon, 'v10'), on='time')
.merge(point_df(lat, lon, 'prate'), on='time'))
df['t2m_c'] = df['t2m'] - 273.15
df['wind'] = (df['u10']**2 + df['v10']**2) ** 0.5
df['precip_mmhr'] = df['prate'] * 3600Quota: 4 calls per site + one coarsened grid (~120 KiB) — three sites ≈ 0.5 MB per run.
Building something else? The API reference covers every endpoint and parameter, the playground writes curl/Python/JS for any query, and the explorer shows the exact call behind anything you click. Tell us what you're integrating — we'll help.