@aeroswarm/weather 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog: @aeroswarm/weather
2
+
3
+ Format: [Keep a Changelog](https://keepachangelog.com/). Versioning: see [STABILITY.md](./STABILITY.md). Entries for
4
+ 0.1.1 to 0.4.0 were written afterwards from the git history; every one of them was released on 2026-11-02.
5
+
6
+ ## Unreleased
7
+
8
+ ### Changed (performance; results are the same within float rounding, checked by a test)
9
+ - `fieldsAt`, `blendInto`, `interpolateScalar`, `windGridFromStations`, `nudgeScalar` and `nudgeWind` are much faster:
10
+ distances and weights are computed once per grid cell and shared between wind, cloud and rain, with no
11
+ trigonometry in the inner loop. 60 stations on a 70 x 60 grid: `fieldsAt` 40 ms to 2.4 ms, `blendInto` 37 ms to
12
+ 4 ms; 200 x 200: 340 ms to 19 ms.
13
+
14
+ ### Added
15
+ - `PERFORMANCE.md` (measured numbers and how to re-run them), `pnpm bench`, `pnpm size`, `size-limits.json`
16
+ (the build fails if the package grows past its size budget), `sideEffects: false`.
17
+
18
+ ## 0.4.1 (2026-11-02)
19
+
20
+ - Documentation and tests only: `STABILITY.md`, `LIMITS.md`, `CHANGELOG.md`, public API surface test.
21
+
22
+ ## 0.4.0 (2026-11-02)
23
+
24
+ - Release only: no source change since 0.3.0.
25
+
26
+ ## 0.3.0 (2026-11-02)
27
+
28
+ ### Added
29
+ - Operational functions: `flightCategory` (FAA thresholds), `windComponents`, `runwayAdvice`, `evaluateMinima`,
30
+ `upcomingChanges`, `qnhTrend`, `stationOperations`, and runway helpers (`runwayFromHeading`, `runwayFromPolygon`).
31
+ - Parsers for SIGMET, AIRMET, volcanic ash advisories (VAA), SNOWTAM and NIL reports.
32
+ - Real-data test corpus (METAR/SPECI, 200+ TAF, SIGMET, VAA) and the fixtures README.
33
+
34
+ ### Behaviour
35
+ - A TAF change group with a malformed period (for example `TEMPO 0214/02014`) is no longer merged silently into
36
+ the base conditions: it is dropped and reported in `Taf.unparsed`.
37
+ - Several tokens that were silently ignored are now decoded or reported in `unparsed` (found by the real-data corpus).
38
+
39
+ ## 0.2.0 (2026-11-02)
40
+
41
+ ### Added
42
+ - Real-data corpus and the first versions of the NIL, SIGMET, AIRMET, VAA and SNOWTAM parsers.
43
+
44
+ ## 0.1.5 (2026-11-02)
45
+
46
+ ### Added
47
+ - Blend a model series with station reports (`WeatherStore.blendInto`).
48
+
49
+ ## 0.1.4 (2026-11-02)
50
+
51
+ ### Added
52
+ - The `aeroswarm.weather-grid` v1 standard (`parseWeatherGridSeries`, `seriesFieldsAt`, `GRID_STANDARD.md`) and the
53
+ Open-Meteo adapter (`weatherGridFromOpenMeteo`, `latticePoints`).
54
+
55
+ ## 0.1.3 (2026-11-02)
56
+
57
+ ### Added
58
+ - `WeatherStore`: ingest raw messages (including AFTN), manual override, `snapshotAt`, `timeline`.
59
+
60
+ ## 0.1.2 (2026-11-02)
61
+
62
+ ### Added
63
+ - Gridded wind and scalar fields (`WindGrid`, `ScalarGrid`, sampling and interpolation).
64
+
65
+ ## 0.1.1 (2026-11-02)
66
+
67
+ - First release: METAR/SPECI/TAF parsers, AFTN message extraction, animation parameters.
package/LIMITS.md ADDED
@@ -0,0 +1,51 @@
1
+ # Limits of use
2
+
3
+ Read this before putting weather data in front of operators.
4
+
5
+ ## Not an official source
6
+
7
+ The packages decode and display weather messages. They are not a meteorological authority. METAR, SPECI, TAF and
8
+ other messages issued by the responsible meteorological service are the official information, and a displayed
9
+ value is only as good as the message it came from and the parser that read it.
10
+
11
+ **Do not use output of these packages as the only basis for a safety decision** (take-off, landing, runway choice,
12
+ diversion, closing an aerodrome). Verify against the official message. Operational helpers (flight category, runway
13
+ advice, minima checks, alerts) are decision aids, and they assume the thresholds you configured are correct.
14
+
15
+ ## What the display shows
16
+
17
+ - METAR and TAF describe one point (the aerodrome). Wind, cloud and rain drawn around an airport are a
18
+ representation of those reports, not the real position of clouds or rain.
19
+ - A grid built from several stations is an interpolation between points; between stations it is an estimate.
20
+ - A model forecast (Open-Meteo and others) is a model, with its own resolution and errors. Blending with reports
21
+ adjusts it near airports only.
22
+ - With a tilted or 3D map the direction of wind on screen is an approximation.
23
+ - There is no terrain or land mask: wind over mountains is not corrected.
24
+
25
+ ## What is read, and what is not verified
26
+
27
+ Decoding is tested on real messages for METAR, SPECI, TAF (200+), international SIGMET and Tokyo VAAC
28
+ advisories. These are supported but have **not** been checked against real messages, only against the ICAO
29
+ description: AIRMET, NIL reports and SNOWTAM. Not covered by any test: Darwin VAAC, raw AFTN envelopes from a
30
+ live circuit, raw BMKG messages. Tokens a parser does not understand are kept in the `unparsed` field of the
31
+ result instead of being dropped; check it.
32
+
33
+ Known simplifications:
34
+
35
+ - METAR wind is in degrees true; runway headings are usually magnetic. Pass the magnetic variation, or the
36
+ crosswind is off by that angle.
37
+ - Flight category follows the FAA definition (ICAO does not define one).
38
+ - Runway headings taken from a polygon are the long axis of the shape, so they are only as accurate as the polygon.
39
+ - Report time has no month or year; they are taken from the time you give as "received".
40
+
41
+ ## Data sources
42
+
43
+ Each source has its own licence and limits; see `SOURCES.md` in `@aeroswarm/weather-sources`. In short: the free
44
+ Open-Meteo endpoint is for non-commercial use only; BMKG access follows your agreement and the token is personal;
45
+ aviationweather.gov data is public but limited to 100 requests a minute. Respect the limits, credit the source,
46
+ and do not redistribute data you are not allowed to.
47
+
48
+ ## No warranty
49
+
50
+ The software is provided as is, without warranty of any kind. (No licence file is included in the packages yet; the
51
+ licence terms are set by the package owner.)
package/PERFORMANCE.md ADDED
@@ -0,0 +1,78 @@
1
+ # Performance and size
2
+
3
+ Measured numbers, not claims. Re-run them on your own hardware; the commands are below.
4
+
5
+ **Machine used for these numbers:** Intel Core Ultra 7 155H (22 threads), 31 GB RAM, Node 22.14, Windows 11, run
6
+ on a laptop with other programs open (hence the spread). A weaker machine will be slower. Times are the mean of a
7
+ `vitest bench` run.
8
+
9
+ ## Computing weather fields (`@aeroswarm/weather`)
10
+
11
+ `pnpm --filter @aeroswarm/weather bench`
12
+
13
+ | Operation | Time |
14
+ |---|---|
15
+ | `fieldsAt`, 5 stations, 70 x 60 grid | 0.4 ms |
16
+ | `fieldsAt`, 60 stations, 70 x 60 grid | 2.4 ms |
17
+ | `fieldsAt`, 60 stations, 200 x 200 grid | 19 ms |
18
+ | `seriesFieldsAt` (model series, 120 hours), 70 x 60 | 1.3 ms |
19
+ | `seriesFieldsAt` (model series, 120 hours), 200 x 200 | 15 ms |
20
+ | `blendInto` (model + 60 stations), 70 x 60 | 4.1 ms |
21
+ | `blendInto` (model + 60 stations), 200 x 200 | 40 ms (one run varied up to 97 ms) |
22
+
23
+ Target agreed for the product: `fieldsAt` under 20 ms for a 70 x 60 grid. Met, with 8x room, up to 60 stations.
24
+
25
+ Before this change `fieldsAt` with 60 stations on 70 x 60 took about 40 ms and `blendInto` about 37 ms; the cost came from
26
+ recomputing distances and trigonometry for every station and every quantity. They are now computed once per cell and
27
+ shared (results are identical to the plain definition, checked by a test; differences are below float rounding).
28
+
29
+ **Memory of a model series** (`WeatherGridSeries`, five quantities as `Float32Array`, one frame per hour):
30
+
31
+ | Grid | 120 hours |
32
+ |---|---|
33
+ | 70 x 60 | 9.6 MB |
34
+ | 200 x 200 | 91.6 MB |
35
+
36
+ Ask the model source for a coarser grid or fewer quantities before asking for more hours.
37
+
38
+ ## Drawing (`@aeroswarm/weather-layer`)
39
+
40
+ `pnpm --filter @aeroswarm/weather-layer bench` measures remapping a field to the screen, which runs each time the
41
+ map moves (at most about 10 times a second). It uses a cheap linear projection, so a real map projection costs more:
42
+ treat these as a lower bound.
43
+
44
+ | Screen | Wind field (24 px cells) | Colour field (8 px cells) |
45
+ |---|---|---|
46
+ | 1080p | 0.56 ms | 0.88 ms |
47
+ | 4K | 2.3 ms | 3.6 ms |
48
+
49
+ **Frames per second of the particle renderer have not been measured by the author.** Canvas drawing needs a real
50
+ browser. Run `examples/fps-benchmark.html` (instructions at the top of the file) on the target machine, and record the
51
+ JSON it prints. Until then there is no fps number to quote, and the earlier target (30 fps on an ordinary laptop at
52
+ 1080p) is unverified.
53
+
54
+ Behaviours built in:
55
+
56
+ - A hidden tab draws nothing.
57
+ - `prefers-reduced-motion`: the overlay draws a still image. The user can turn motion back on with
58
+ `WeatherMotionToggle` (or `FlowRenderer.setReducedMotion`), and the renderer follows later changes of the system
59
+ setting.
60
+ - Adaptive quality: when a frame takes more than 12 ms to prepare, or frames are drawn less than two thirds as often
61
+ as requested, the particle count drops by 20% (down to 25%); when well under budget it rises again by 10%. The
62
+ current level is `overlay.quality` (1 = full). The rule is tested (`nextQualityScale`); the renderer integration needs
63
+ a browser and is not covered by an automated test.
64
+
65
+ ## Package size
66
+
67
+ `pnpm --filter <package> size` after a build. Limits are in each package's `size-limits.json`; `prepublishOnly` fails
68
+ when a number goes over its limit, so growth needs a deliberate change with a note in the changelog.
69
+
70
+ | Package | All of `dist` (gzip per file) | Example: what an app pays (bundled, minified, gzip) |
71
+ |---|---|---|
72
+ | `@aeroswarm/weather` | 26.0 kB | `parseMetar` only 2.8 kB, `WeatherStore` 8.3 kB, everything 19.5 kB |
73
+ | `@aeroswarm/weather-layer` | 24.1 kB | `legendFor` only 0.8 kB, field overlay + MapLibre adapter 4.9 kB, all of `/react-ui` 9.3 kB |
74
+ | `@aeroswarm/weather-sources` | 4.1 kB | `aviationWeatherSource` 1.6 kB, everything 3.1 kB |
75
+
76
+ Tree-shaking works (importing one function costs a fraction of the package), and a MapLibre app does not pay for
77
+ Cesium, Leaflet or RIA because each map has its own entry point. `@aeroswarm/weather-layer` now splits shared code into
78
+ chunks; before that, using `.` and `/react-ui` together shipped the core twice (about 12 kB gzip extra).
package/README.md CHANGED
@@ -26,6 +26,14 @@ const snapshot = animationSnapshot({ observation: metar, forecast: taf, at: new
26
26
  METAR/TAF describe one point (the aerodrome), not a weather field, so the animation is a
27
27
  representation around the airport, not the real position of clouds or rain.
28
28
 
29
+ ## Documentation
30
+
31
+ - [STABILITY.md](./STABILITY.md): what is public, semver policy, deprecation, the weather-grid standard.
32
+ - [LIMITS.md](./LIMITS.md): **read before operational use**: not an official source, not for safety decisions
33
+ without verification, what is and is not verified.
34
+ - [PERFORMANCE.md](./PERFORMANCE.md): measured speed, memory and bundle size, and how to re-measure.
35
+ - [CHANGELOG.md](./CHANGELOG.md), [GRID_STANDARD.md](./GRID_STANDARD.md).
36
+
29
37
  ## Gridded fields
30
38
 
31
39
  `grid.ts` turns wind and scalar data into grids, whatever the source:
package/STABILITY.md ADDED
@@ -0,0 +1,62 @@
1
+ # Stability and versioning
2
+
3
+ Applies to `@aeroswarm/weather`, `@aeroswarm/weather-layer` and `@aeroswarm/weather-sources`.
4
+
5
+ ## What is public
6
+
7
+ Public means exported from a documented entry point:
8
+
9
+ | Package | Entry points |
10
+ |---|---|
11
+ | `@aeroswarm/weather` | `@aeroswarm/weather` |
12
+ | `@aeroswarm/weather-layer` | `@aeroswarm/weather-layer`, `/react`, `/react-ui`, `/maplibre`, `/leaflet`, `/cesium`, `/ria`, and the stylesheet `/react-ui/weather-ui.css` (class names `aw-*` and variables `--aw-*`) |
13
+ | `@aeroswarm/weather-sources` | `@aeroswarm/weather-sources` |
14
+
15
+ Not public, may change in any release: anything you reach by importing a file inside `dist/`, anything not exported
16
+ from an entry point, internal CSS selectors other than the documented `aw-*` classes, the exact wording of default
17
+ UI texts, and the text of error messages.
18
+
19
+ The runtime export names of every entry point are pinned by a test in each package (`src/api.test.ts` with a
20
+ snapshot in `src/__snapshots__/`). Adding, removing or renaming an export fails the test until the snapshot is
21
+ updated on purpose, so an API change cannot slip in unnoticed. The test covers exported names, not type shapes.
22
+
23
+ ## Versioning (semver)
24
+
25
+ We follow [semver](https://semver.org/). All three packages are currently `0.x`, which semver treats as unstable;
26
+ we hold ourselves to this stricter rule anyway:
27
+
28
+ | Change | Release while `0.x` | Release from `1.0` |
29
+ |---|---|---|
30
+ | Bug fix, no API change | patch | patch |
31
+ | New export, new option, new report type understood | minor | minor |
32
+ | Removed or renamed export, changed signature, changed default | minor, listed under **Breaking** in the changelog | major |
33
+ | Decoded values change because a parser bug was fixed | minor, listed under **Behaviour** in the changelog | minor |
34
+ | Dependency range change, new peer dependency | minor, listed in the changelog | major if it forces consumers to upgrade |
35
+
36
+ Every release has an entry in the package's `CHANGELOG.md`. A change that can break a consumer must say so there.
37
+ Parser fixes can change what a message decodes to; that is intended, and is why it is called out separately.
38
+
39
+ ## Deprecation
40
+
41
+ - A public export is deprecated before it is removed: it keeps working, is marked `@deprecated` in its type
42
+ declaration with the replacement, and is listed in the changelog.
43
+ - While `0.x`: removed no earlier than the next minor release.
44
+ - From `1.0`: removed no earlier than the next major release, and not within 90 days of the deprecation.
45
+
46
+ ## Peer dependencies
47
+
48
+ `@aeroswarm/weather-layer` and `@aeroswarm/weather-sources` declare `@aeroswarm/weather` as a peer dependency
49
+ (`>=0.4.0 <1.0.0`), so your app has one copy of it. Install it yourself next to them. React (18 or newer) is an
50
+ optional peer of `@aeroswarm/weather-layer`, needed only for `/react` and `/react-ui`.
51
+
52
+ ## The weather-grid standard
53
+
54
+ `aeroswarm.weather-grid` version 1 (see `GRID_STANDARD.md`) is frozen: the meaning of existing fields does not
55
+ change, and a document valid today stays valid. Optional fields may be added in version 1. Anything that would
56
+ invalidate an existing document is a new standard version (2), supported alongside 1 for at least one minor
57
+ release of the packages. The parser rejects documents with an unknown version instead of guessing.
58
+
59
+ ## Migration
60
+
61
+ Breaking changes are described in the changelog entry of the release that introduces them, with what to change.
62
+ When a release needs more than a few lines, a `MIGRATION.md` section is added to the same package.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../src/grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,YAAY,CAAC;IAChB,CAAC,EAAE,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAKD,eAAO,MAAM,OAAO,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAA0B,CAAC;AAE1E,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,6DAA6D;AAC7D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAG1B;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAKtG;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,UAAU,CA2BZ;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,EAAE,oBAAoB,GAC5B,QAAQ,CAUV;AA2BD,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEtF;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIpG;AAED,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsCD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,OAAO,GAAE,YAAiB,GACzB,UAAU,CAOZ;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,GAAE,YAAiB,GACzB,QAAQ,CAgBV;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ,CAK5E;AAgCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAuCvD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,KAAK,EAAE,EACzB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,SAAS,GAC7C,WAAW,EAAE,CAcf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,CAiBnE;AAED,QAAA,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKlD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,OAAO,sBAAsB,CAAA;CAAO,GACpF,WAAW,EAAE,CAuBf;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzC;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,oBAAoB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,OAAO,sBAAsB,CAAA;CAAE,GACxG,WAAW,CAiBb"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../src/grid.ts"],"names":[],"mappings":"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"}
package/dist/idw.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Inti hitung interpolasi stasiun ke grid (internal: tidak diekspor dari paket). Jarak antar sel dan stasiun
3
+ * dihitung tanpa trigonometri di loop dalam, dan satu putaran jarak dipakai untuk beberapa besaran sekaligus.
4
+ */
5
+ import type { GridBounds } from "./grid.js";
6
+ export declare const KM_PER_DEG_LAT = 111.32;
7
+ export declare const KT_TO_MS = 0.514444;
8
+ interface Located {
9
+ lat: number;
10
+ lon: number;
11
+ }
12
+ export interface IdwOptions {
13
+ bounds: GridBounds;
14
+ nx: number;
15
+ ny: number;
16
+ power?: number;
17
+ maxDistanceKm?: number;
18
+ }
19
+ /**
20
+ * IDW (bobot 1/d^p) untuk beberapa besaran di titik yang sama. Tepat di sebuah titik (jarak < 0,5 km) hasilnya
21
+ * persis nilai titik itu; sel yang lebih jauh dari semua titik dari `maxDistanceKm` (bawaan 400) diberi NaN.
22
+ */
23
+ export declare function interpolateMany(points: readonly Located[], valueSets: readonly (readonly number[])[], options: IdwOptions): Float32Array[];
24
+ /**
25
+ * Tambahkan selisih (pengamatan dikurangi model) di titik-titik ke beberapa grid sekaligus: bobot dihitung
26
+ * sekali per sel dan dipakai untuk semua grid. Sel tanpa data (NaN) dilewati. Di luar `radius` grid tidak berubah.
27
+ */
28
+ export declare function nudgeMany(grids: readonly Float32Array[], points: readonly Located[], deltaSets: readonly (readonly number[])[], bounds: GridBounds, nx: number, ny: number, radius: number): Float32Array[];
29
+ /** Komponen u/v (m/s) dari angin stasiun; arah null (VRB) dianggap tenang. */
30
+ export declare function stationComponents(stations: readonly {
31
+ speedKt: number;
32
+ directionDeg: number | null;
33
+ }[], ktToMs: number): {
34
+ u: number[];
35
+ v: number[];
36
+ };
37
+ export {};
38
+ //# sourceMappingURL=idw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idw.d.ts","sourceRoot":"","sources":["../src/idw.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,QAAQ,WAAY,CAAC;AAGlC,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuBD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,OAAO,EAAE,EAC1B,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EACzC,OAAO,EAAE,UAAU,GAClB,YAAY,EAAE,CAiDhB;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,SAAS,OAAO,EAAE,EAC1B,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EACzC,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,GACb,YAAY,EAAE,CAuChB;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EACrE,MAAM,EAAE,MAAM,GACb;IAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAe9B"}
package/dist/index.js CHANGED
@@ -1350,9 +1350,128 @@ function animationSnapshot(input) {
1350
1350
  return { state: animationFromConditions(conditions), ...meta };
1351
1351
  }
1352
1352
 
1353
- // src/grid.ts
1354
- var KT_TO_MS = 0.514444;
1353
+ // src/idw.ts
1355
1354
  var KM_PER_DEG_LAT = 111.32;
1355
+ var KT_TO_MS = 0.514444;
1356
+ var RAD = Math.PI / 180;
1357
+ function halfLatTrig(lats) {
1358
+ const sin = new Float64Array(lats.length);
1359
+ const cos = new Float64Array(lats.length);
1360
+ for (let i = 0; i < lats.length; i++) {
1361
+ const half = lats[i] / 2 * RAD;
1362
+ sin[i] = Math.sin(half);
1363
+ cos[i] = Math.cos(half);
1364
+ }
1365
+ return { sin, cos };
1366
+ }
1367
+ function axes(bounds, nx, ny) {
1368
+ const lats = new Float64Array(ny);
1369
+ const lons = new Float64Array(nx);
1370
+ for (let row = 0; row < ny; row++) lats[row] = bounds.north - (bounds.north - bounds.south) * row / Math.max(1, ny - 1);
1371
+ for (let col = 0; col < nx; col++) lons[col] = bounds.west + (bounds.east - bounds.west) * col / Math.max(1, nx - 1);
1372
+ return { lats, lons };
1373
+ }
1374
+ function interpolateMany(points, valueSets, options) {
1375
+ const { bounds, nx, ny } = options;
1376
+ const power = options.power ?? 2;
1377
+ const maxKm = options.maxDistanceKm ?? 400;
1378
+ const sets = valueSets.length;
1379
+ const outs = valueSets.map(() => new Float32Array(nx * ny).fill(Number.NaN));
1380
+ const sums = new Float64Array(sets);
1381
+ const { lats, lons } = axes(bounds, nx, ny);
1382
+ const cellTrig = halfLatTrig(lats);
1383
+ const pointTrig = halfLatTrig(points.map((p) => p.lat));
1384
+ for (let row = 0; row < ny; row++) {
1385
+ const cellLat = lats[row];
1386
+ const sinC = cellTrig.sin[row];
1387
+ const cosC = cellTrig.cos[row];
1388
+ for (let col = 0; col < nx; col++) {
1389
+ const cellLon = lons[col];
1390
+ let weightSum = 0;
1391
+ let exact = -1;
1392
+ sums.fill(0);
1393
+ for (let i = 0; i < points.length; i++) {
1394
+ const p = points[i];
1395
+ const dLat = (cellLat - p.lat) * KM_PER_DEG_LAT;
1396
+ if (dLat > maxKm || dLat < -maxKm) continue;
1397
+ const cosMean = cosC * pointTrig.cos[i] - sinC * pointTrig.sin[i];
1398
+ const dLon = (cellLon - p.lon) * KM_PER_DEG_LAT * cosMean;
1399
+ const d = Math.sqrt(dLat * dLat + dLon * dLon);
1400
+ if (d > maxKm) continue;
1401
+ if (d < 0.5) {
1402
+ exact = i;
1403
+ break;
1404
+ }
1405
+ const w = power === 2 ? 1 / (d * d) : 1 / d ** power;
1406
+ weightSum += w;
1407
+ for (let k = 0; k < sets; k++) sums[k] = sums[k] + w * valueSets[k][i];
1408
+ }
1409
+ const index = row * nx + col;
1410
+ for (let k = 0; k < sets; k++) {
1411
+ outs[k][index] = exact >= 0 ? valueSets[k][exact] : weightSum > 0 ? sums[k] / weightSum : Number.NaN;
1412
+ }
1413
+ }
1414
+ }
1415
+ return outs;
1416
+ }
1417
+ var influence = (distance, radius) => distance >= radius ? 0 : (radius * radius - distance * distance) / (radius * radius + distance * distance);
1418
+ function nudgeMany(grids, points, deltaSets, bounds, nx, ny, radius) {
1419
+ const outs = grids.map((g) => Float32Array.from(g));
1420
+ if (points.length === 0) return outs;
1421
+ const sets = grids.length;
1422
+ const weighted = new Float64Array(sets);
1423
+ const { lats, lons } = axes(bounds, nx, ny);
1424
+ const cellTrig = halfLatTrig(lats);
1425
+ const pointTrig = halfLatTrig(points.map((p) => p.lat));
1426
+ for (let row = 0; row < ny; row++) {
1427
+ const cellLat = lats[row];
1428
+ const sinC = cellTrig.sin[row];
1429
+ const cosC = cellTrig.cos[row];
1430
+ for (let col = 0; col < nx; col++) {
1431
+ const cellLon = lons[col];
1432
+ const index = row * nx + col;
1433
+ let weights = 0;
1434
+ weighted.fill(0);
1435
+ for (let i = 0; i < points.length; i++) {
1436
+ const p = points[i];
1437
+ const dLat = (cellLat - p.lat) * KM_PER_DEG_LAT;
1438
+ if (dLat >= radius || dLat <= -radius) continue;
1439
+ const cosMean = cosC * pointTrig.cos[i] - sinC * pointTrig.sin[i];
1440
+ const dLon = (cellLon - p.lon) * KM_PER_DEG_LAT * cosMean;
1441
+ const w = influence(Math.sqrt(dLat * dLat + dLon * dLon), radius);
1442
+ if (w === 0) continue;
1443
+ weights += w;
1444
+ for (let k = 0; k < sets; k++) weighted[k] = weighted[k] + w * deltaSets[k][i];
1445
+ }
1446
+ if (weights <= 0) continue;
1447
+ const scale = Math.min(1, weights);
1448
+ for (let k = 0; k < sets; k++) {
1449
+ const out = outs[k];
1450
+ if (Number.isNaN(out[index])) continue;
1451
+ out[index] = out[index] + weighted[k] / weights * scale;
1452
+ }
1453
+ }
1454
+ }
1455
+ return outs;
1456
+ }
1457
+ function stationComponents(stations, ktToMs) {
1458
+ const u = [];
1459
+ const v = [];
1460
+ for (const s of stations) {
1461
+ if (s.directionDeg === null) {
1462
+ u.push(0);
1463
+ v.push(0);
1464
+ continue;
1465
+ }
1466
+ const rad = s.directionDeg * Math.PI / 180;
1467
+ const speed = s.speedKt * ktToMs;
1468
+ u.push(-speed * Math.sin(rad));
1469
+ v.push(-speed * Math.cos(rad));
1470
+ }
1471
+ return { u, v };
1472
+ }
1473
+
1474
+ // src/grid.ts
1356
1475
  var speedOf = (u, v) => Math.hypot(u, v);
1357
1476
  function directionFromUV(u, v) {
1358
1477
  return (Math.atan2(-u, -v) * 180 / Math.PI + 360) % 360;
@@ -1367,37 +1486,9 @@ function pointOf(bounds, nx, ny, col, row) {
1367
1486
  lat: bounds.north - (bounds.north - bounds.south) * row / Math.max(1, ny - 1)
1368
1487
  };
1369
1488
  }
1370
- function distanceKm(a, b) {
1371
- const dLat = (a.lat - b.lat) * KM_PER_DEG_LAT;
1372
- const dLon = (a.lon - b.lon) * KM_PER_DEG_LAT * Math.cos((a.lat + b.lat) / 2 * (Math.PI / 180));
1373
- return Math.hypot(dLat, dLon);
1374
- }
1375
1489
  function interpolateScalar(points, options) {
1376
- const { bounds, nx, ny } = options;
1377
- const power = options.power ?? 2;
1378
- const maxKm = options.maxDistanceKm ?? 400;
1379
- const values = new Float32Array(nx * ny).fill(Number.NaN);
1380
- for (let row = 0; row < ny; row++) {
1381
- for (let col = 0; col < nx; col++) {
1382
- const cell = pointOf(bounds, nx, ny, col, row);
1383
- let weightSum = 0;
1384
- let sum = 0;
1385
- let exact = null;
1386
- for (const p of points) {
1387
- const d = distanceKm(cell, p);
1388
- if (d > maxKm) continue;
1389
- if (d < 0.5) {
1390
- exact = p.value;
1391
- break;
1392
- }
1393
- const w = 1 / d ** power;
1394
- weightSum += w;
1395
- sum += w * p.value;
1396
- }
1397
- values[row * nx + col] = exact ?? (weightSum > 0 ? sum / weightSum : Number.NaN);
1398
- }
1399
- }
1400
- return { bounds, nx, ny, values };
1490
+ const [values] = interpolateMany(points, [points.map((p) => p.value)], options);
1491
+ return { bounds: options.bounds, nx: options.nx, ny: options.ny, values };
1401
1492
  }
1402
1493
  function windGridFromStations(stations, options) {
1403
1494
  const components = stations.map((s) => {
@@ -1405,9 +1496,8 @@ function windGridFromStations(stations, options) {
1405
1496
  const { u: u2, v: v2 } = s.directionDeg === null ? { u: 0, v: 0 } : uvFromSpeedDirection(speedMs, s.directionDeg);
1406
1497
  return { lat: s.lat, lon: s.lon, u: u2, v: v2 };
1407
1498
  });
1408
- const u = interpolateScalar(components.map((c) => ({ lat: c.lat, lon: c.lon, value: c.u })), options);
1409
- const v = interpolateScalar(components.map((c) => ({ lat: c.lat, lon: c.lon, value: c.v })), options);
1410
- return { bounds: options.bounds, nx: options.nx, ny: options.ny, u: u.values, v: v.values };
1499
+ const [u, v] = interpolateMany(components, [components.map((c) => c.u), components.map((c) => c.v)], options);
1500
+ return { bounds: options.bounds, nx: options.nx, ny: options.ny, u, v };
1411
1501
  }
1412
1502
  function bilinear(values, bounds, nx, ny, lon, lat) {
1413
1503
  if (lon < bounds.west || lon > bounds.east || lat < bounds.south || lat > bounds.north) return null;
@@ -1434,51 +1524,32 @@ function sampleWind(grid, lon, lat) {
1434
1524
  const v = bilinear(grid.v, grid.bounds, grid.nx, grid.ny, lon, lat);
1435
1525
  return u === null || v === null ? null : { u, v };
1436
1526
  }
1437
- var influence = (distance, radius) => distance >= radius ? 0 : (radius * radius - distance * distance) / (radius * radius + distance * distance);
1438
- function nudgeValues(values, bounds, nx, ny, deltas, radius) {
1439
- const out = Float32Array.from(values);
1440
- if (deltas.length === 0) return out;
1441
- for (let row = 0; row < ny; row++) {
1442
- for (let col = 0; col < nx; col++) {
1443
- const index = row * nx + col;
1444
- if (Number.isNaN(out[index])) continue;
1445
- const cell = pointOf(bounds, nx, ny, col, row);
1446
- let weights = 0;
1447
- let weighted = 0;
1448
- for (const d of deltas) {
1449
- const w = influence(distanceKm(cell, d), radius);
1450
- weights += w;
1451
- weighted += w * d.delta;
1452
- }
1453
- if (weights > 0) out[index] = out[index] + weighted / weights * Math.min(1, weights);
1454
- }
1455
- }
1456
- return out;
1457
- }
1458
1527
  function nudgeScalar(grid, points, options = {}) {
1528
+ const located = [];
1459
1529
  const deltas = [];
1460
1530
  for (const p of points) {
1461
1531
  const model = sampleScalar(grid, p.lon, p.lat);
1462
- if (model !== null) deltas.push({ lat: p.lat, lon: p.lon, delta: p.value - model });
1532
+ if (model === null) continue;
1533
+ located.push({ lat: p.lat, lon: p.lon });
1534
+ deltas.push(p.value - model);
1463
1535
  }
1464
- return { ...grid, values: nudgeValues(grid.values, grid.bounds, grid.nx, grid.ny, deltas, options.radiusKm ?? 80) };
1536
+ const [values] = nudgeMany([grid.values], located, [deltas], grid.bounds, grid.nx, grid.ny, options.radiusKm ?? 80);
1537
+ return { ...grid, values };
1465
1538
  }
1466
1539
  function nudgeWind(grid, stations, options = {}) {
1540
+ const located = [];
1467
1541
  const du = [];
1468
1542
  const dv = [];
1469
1543
  for (const s of stations) {
1470
1544
  const model = sampleWind(grid, s.lon, s.lat);
1471
1545
  if (!model) continue;
1472
1546
  const obs = s.directionDeg === null ? { u: 0, v: 0 } : uvFromSpeedDirection(s.speedKt * KT_TO_MS, s.directionDeg);
1473
- du.push({ lat: s.lat, lon: s.lon, delta: obs.u - model.u });
1474
- dv.push({ lat: s.lat, lon: s.lon, delta: obs.v - model.v });
1547
+ located.push({ lat: s.lat, lon: s.lon });
1548
+ du.push(obs.u - model.u);
1549
+ dv.push(obs.v - model.v);
1475
1550
  }
1476
- const radius = options.radiusKm ?? 80;
1477
- return {
1478
- ...grid,
1479
- u: nudgeValues(grid.u, grid.bounds, grid.nx, grid.ny, du, radius),
1480
- v: nudgeValues(grid.v, grid.bounds, grid.nx, grid.ny, dv, radius)
1481
- };
1551
+ const [u, v] = nudgeMany([grid.u, grid.v], located, [du, dv], grid.bounds, grid.nx, grid.ny, options.radiusKm ?? 80);
1552
+ return { ...grid, u, v };
1482
1553
  }
1483
1554
  function blendWindGrids(a, b, t) {
1484
1555
  if (a.nx !== b.nx || a.ny !== b.ny) throw new Error("Grid angin harus berukuran sama");
@@ -2282,12 +2353,19 @@ var WeatherStore = class {
2282
2353
  fieldsAt(at, grid, options = {}) {
2283
2354
  const points = this.pointsAt(at, options);
2284
2355
  if (points.stations.length === 0) return null;
2356
+ const { u, v } = stationComponents(points.winds, KT_TO_MS);
2357
+ const [windU, windV, cloud, rain] = interpolateMany(
2358
+ points.winds,
2359
+ [u, v, points.clouds.map((c) => c.value), points.rains.map((r) => r.value)],
2360
+ grid
2361
+ );
2362
+ const { bounds, nx, ny } = grid;
2285
2363
  return {
2286
2364
  at,
2287
2365
  stations: points.stations,
2288
- wind: windGridFromStations(points.winds, grid),
2289
- cloud: interpolateScalar(points.clouds, grid),
2290
- rain: interpolateScalar(points.rains, grid),
2366
+ wind: { bounds, nx, ny, u: windU, v: windV },
2367
+ cloud: { bounds, nx, ny, values: cloud },
2368
+ rain: { bounds, nx, ny, values: rain },
2291
2369
  temperature: points.temperatures.length > 0 ? interpolateScalar(points.temperatures, grid) : null
2292
2370
  };
2293
2371
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,41 +1,48 @@
1
- {
2
- "name": "@aeroswarm/weather",
3
- "version": "0.4.0",
4
- "description": "Decode raw AFTN/METAR/SPECI/TAF messages and turn them into animation parameters.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
- "main": "./dist/index.js"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "GRID_STANDARD.md"
18
- ],
19
- "publishConfig": {
20
- "access": "public",
21
- "registry": "https://registry.npmjs.org/"
22
- },
23
- "scripts": {
24
- "build": "tsup src/index.ts --format esm --clean && tsc --emitDeclarationOnly",
25
- "dev": "tsup src/index.ts --format esm --dts --watch",
26
- "test": "vitest run --passWithNoTests",
27
- "typecheck": "tsc --noEmit",
28
- "clean": "rm -rf dist",
29
- "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build",
30
- "release": "release-it",
31
- "release:dry": "release-it --dry-run",
32
- "release:patch": "release-it patch --no-git.requireCleanWorkingDir",
33
- "release:minor": "release-it minor --no-git.requireCleanWorkingDir",
34
- "release:major": "release-it major --no-git.requireCleanWorkingDir"
35
- },
36
- "devDependencies": {
37
- "release-it": "^18.1.2",
38
- "tsup": "^8.5.1",
39
- "vitest": "^4.1.6"
40
- }
41
- }
1
+ {
2
+ "name": "@aeroswarm/weather",
3
+ "version": "0.4.2",
4
+ "description": "Decode raw AFTN/METAR/SPECI/TAF messages and turn them into animation parameters.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "main": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "GRID_STANDARD.md",
18
+ "CHANGELOG.md",
19
+ "STABILITY.md",
20
+ "LIMITS.md",
21
+ "PERFORMANCE.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org/"
26
+ },
27
+ "scripts": {
28
+ "build": "tsup src/index.ts --format esm --clean && tsc --emitDeclarationOnly",
29
+ "dev": "tsup src/index.ts --format esm --dts --watch",
30
+ "test": "vitest run --passWithNoTests",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rm -rf dist",
33
+ "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build && pnpm run size",
34
+ "release": "release-it",
35
+ "release:dry": "release-it --dry-run",
36
+ "release:patch": "release-it patch --no-git.requireCleanWorkingDir",
37
+ "release:minor": "release-it minor --no-git.requireCleanWorkingDir",
38
+ "release:major": "release-it major --no-git.requireCleanWorkingDir",
39
+ "bench": "vitest bench --run",
40
+ "size": "node ../../../scripts/package-size.mjs"
41
+ },
42
+ "devDependencies": {
43
+ "release-it": "^18.1.2",
44
+ "tsup": "^8.5.1",
45
+ "vitest": "^4.1.6"
46
+ },
47
+ "sideEffects": false
48
+ }