mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
perf(wind): decode forecast GRIB in a worker and reuse the cycle's wind
Each /api/wind refresh decoded two or three 0.25 degree GRIB2 messages with ecCodes (WASM), validated ~1.04 M doubles per message and resampled them, all synchronously on the server thread. Measured with cached upstream bytes: 43 to 164 ms of event-loop block per warm refresh, 230 to 381 ms for the first one in a process, 316 to 974 ms when all six keys refresh together. Decode, validation and resampling now run on one lazily started worker thread, which transfers back only the display-grid samples. The server keeps the sampled U/V of the last forecast object per model (1.04 MB each), so an overlay change, or a refresh within the same forecast step, fetches and decodes only the scalar. Manifests and grid bodies are byte-identical to before for the same upstream bytes. Co-Authored-By: Claude Fable 5.2 <[email protected]>
This commit is contained in:
1 parent
140f7ebd21
commit
ef6c30d4c3
9 files changed
+888
-81
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { fetchText, fetchRange, loadOptionalScalar } from './gfs.js';
|
||||
import { decodeWindGribMessage } from './decode.js';
|
||||
import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js';
|
||||
import { fetchText, loadWeatherSnapshot } from './gfs.js';
|
||||
import { decodeWindGribInWorker } from './decoder.js';
|
||||
import { weatherScalarMetadata } from './grid.js';
|
||||
|
||||
/** Keyless ECMWF Open Data root for real-time IFS forecasts. */
|
||||
const IFS_BASE = 'https://data.ecmwf.int/forecasts';
|
||||
@@ -110,9 +110,10 @@ export async function fetchIfsWind({
|
||||
fetchImpl = fetch,
|
||||
now = () => Date.now(),
|
||||
targetDx = 1,
|
||||
decodeImpl = decodeWindGribMessage,
|
||||
decodeImpl = decodeWindGribInWorker,
|
||||
signal,
|
||||
overlay = 'none',
|
||||
windCache,
|
||||
} = {}) {
|
||||
weatherScalarMetadata(overlay);
|
||||
const nowMs = now();
|
||||
@@ -137,24 +138,18 @@ export async function fetchIfsWind({
|
||||
/* A missing optional field does not discard valid wind. */
|
||||
}
|
||||
}
|
||||
// All selected messages come from this one issue/valid-time object.
|
||||
const load = async (range) =>
|
||||
decodeImpl(
|
||||
await fetchRange({ url: urls.grib, ...range, fetchImpl, signal }),
|
||||
);
|
||||
const [u, v, scalar] = await Promise.all([
|
||||
load(ranges.u),
|
||||
load(ranges.v),
|
||||
loadOptionalScalar({
|
||||
range: scalarRange,
|
||||
url: urls.grib,
|
||||
fetchImpl,
|
||||
decodeImpl,
|
||||
signal,
|
||||
}),
|
||||
]);
|
||||
signal?.throwIfAborted();
|
||||
const fields = resampleWeatherSnapshot({ u, v, scalar, overlay, targetDx });
|
||||
const fields = await loadWeatherSnapshot({
|
||||
model: 'ifs',
|
||||
url: urls.grib,
|
||||
ranges,
|
||||
scalarRange,
|
||||
overlay,
|
||||
targetDx,
|
||||
fetchImpl,
|
||||
decodeImpl,
|
||||
signal,
|
||||
windCache,
|
||||
});
|
||||
const runIso = new Date(runMs).toISOString();
|
||||
const validIso = new Date(runMs + forecastHour * 3600_000).toISOString();
|
||||
return {
|
||||
|
||||
Reference in new issue
Block a user