@aeroswarm/weather 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -93,3 +93,22 @@ Honest limits: AIRMET, NIL and SNOWTAM were written from the ICAO specification
93
93
  messages could be collected (aviationweather.gov does not publish them; the Bureau of Meteorology
94
94
  blocks automated access, so Darwin VAAC advisories were not collected). Treat those three as
95
95
  specification-checked, not field-proven, until real samples are added to `test/fixtures`.
96
+
97
+ ## Operational information
98
+
99
+ Pure functions that turn conditions into what an aerodrome operation needs. Decisions never rely on
100
+ a guessed threshold: limits, minima and LVP values are passed in by the caller.
101
+
102
+ | Function | Gives |
103
+ |---|---|
104
+ | `flightCategory(conditions, thresholds?)` | VFR / MVFR / IFR / LIFR from visibility and ceiling (lowest BKN/OVC/VV), what decided it, and whether the ceiling is unknown. Defaults follow the FAA definition (ICAO has no categories); replace with `thresholds` |
105
+ | `windComponents(wind, runwayHeadingTrue)` | headwind, tailwind, crosswind and the side it comes from. A variable wind gives only the upper bound |
106
+ | `runwayAdvice(wind, runways, limits?)` | per runway and direction: steady and gust components, the more favourable direction, `exceeds` flags only for limits you give |
107
+ | `runwayFromHeading`, `runwayFromPolygon`, `headingFromPolygon`, `magneticToTrue` | build runways. METAR wind is **true**; runway headings are usually magnetic, so pass the magnetic variation |
108
+ | `evaluateMinima(conditions, minima)` | below/above, with every reason, and `unknown` for data that is missing or cannot decide (`P2000`, `M0050`). Missing data is never treated as safe. The same function serves LVP thresholds |
109
+ | `upcomingChanges(taf, from, hours, options?)` | category changes, thunderstorm/CB start and end, heavy precipitation, and (when you give the threshold) low visibility and wind changes, each with the TAF group that causes it. What is already happening at `from` is marked `ongoing` |
110
+ | `qnhTrend(previous, current, options?)` | change, rate and direction; `rapid` only against a rate you give |
111
+ | `stationOperations(store, station, at, config?)` | all of the above for one station from a `WeatherStore` |
112
+
113
+ Decision aid, not an official source.
114
+
@@ -78,9 +78,25 @@ export interface SnapshotInput {
78
78
  maxObservationAgeMs?: number;
79
79
  includeProbable?: boolean;
80
80
  }
81
+ /** Kondisi yang berlaku di satu stasiun pada satu waktu, dan dari mana asalnya. */
82
+ export interface SelectedConditions {
83
+ conditions: Conditions;
84
+ source: AnimationSource;
85
+ station: string;
86
+ /** Waktu yang diwakili data ini. */
87
+ validAt: Date;
88
+ /** Pengamatan lebih tua dari batas, dan tidak ada TAF yang berlaku sebagai gantinya. */
89
+ stale: boolean;
90
+ /** Dari TAF: kondisi sementara (TEMPO) sedang berlaku. */
91
+ temporary: boolean;
92
+ /** Dari TAF: 0-1 selama transisi BECMG berjalan. */
93
+ transition: number | null;
94
+ }
81
95
  /**
82
96
  * Pilih sumber terbaik untuk waktu `at`: pengamatan (METAR/SPECI) yang masih segar, lalu TAF yang
83
97
  * berlaku, lalu pengamatan lama (ditandai stale). Null bila tidak ada data sama sekali.
84
98
  */
99
+ export declare function selectConditions(input: SnapshotInput): SelectedConditions | null;
100
+ /** Seperti `selectConditions`, tetapi sudah diubah menjadi parameter animasi. */
85
101
  export declare function animationSnapshot(input: SnapshotInput): AnimationSnapshot | null;
86
102
  //# sourceMappingURL=animation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,UAAU,EAAE,KAAK,EAAkB,GAAG,EAAE,MAAM,YAAY,CAAC;AAErF,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE1F,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uDAAuD;IACvD,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7B,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QACJ,yDAAyD;QACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,wEAAwE;QACxE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,mFAAmF;QACnF,SAAS,EAAE,MAAM,CAAC;QAClB,+EAA+E;QAC/E,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,MAAM,EAAE;QACN,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,iDAAiD;QACjD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,UAAU,EAAE;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,6DAA6D;QAC7D,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,WAAW,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D,aAAa,EAAE;QACb,IAAI,EAAE,iBAAiB,CAAC;QACxB,+CAA+C;QAC/C,SAAS,EAAE,MAAM,CAAC;QAClB,kEAAkE;QAClE,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,OAAO,CAAC;KACnB,GAAG,IAAI,CAAC;IACT,YAAY,EAAE;QACZ,oCAAoC;QACpC,QAAQ,EAAE,OAAO,CAAC;QAClB,uCAAuC;QACvC,QAAQ,EAAE,OAAO,CAAC;QAClB,kBAAkB,EAAE,MAAM,CAAC;KAC5B,GAAG,IAAI,CAAC;CACV;AAuED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,cAAc,CA+D9E;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,OAAO,EAAE,IAAI,CAAC;IACd,wFAAwF;IACxF,KAAK,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IACtB,yFAAyF;IACzF,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,iGAAiG;IACjG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,GAAG,IAAI,CA6ChF"}
1
+ {"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,9 @@ export * from "./gridSeries.js";
8
8
  export * from "./openMeteo.js";
9
9
  export * from "./metar.js";
10
10
  export * from "./nil.js";
11
+ export * from "./operations.js";
12
+ export * from "./ops.js";
13
+ export * from "./runway.js";
11
14
  export * from "./sigmet.js";
12
15
  export * from "./snowtam.js";
13
16
  export * from "./taf.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AAEzB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,IAAiB,GAC3B,MAAM,EAAE,CA0BV"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AAEzB;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,IAAiB,GAC3B,MAAM,EAAE,CA0BV"}
package/dist/index.js CHANGED
@@ -1302,14 +1302,14 @@ function animationFromConditions(conditions) {
1302
1302
  thunderstorm
1303
1303
  };
1304
1304
  }
1305
- function animationSnapshot(input) {
1305
+ function selectConditions(input) {
1306
1306
  const at = input.at ?? /* @__PURE__ */ new Date();
1307
1307
  const maxAge = input.maxObservationAgeMs ?? 3 * 36e5;
1308
1308
  const { observation, forecast } = input;
1309
1309
  const age = observation ? at.getTime() - observation.observedAt.getTime() : null;
1310
1310
  if (observation && age !== null && age >= 0 && age <= maxAge) {
1311
1311
  return {
1312
- state: animationFromConditions(observation.conditions),
1312
+ conditions: observation.conditions,
1313
1313
  source: observation.kind,
1314
1314
  station: observation.station,
1315
1315
  validAt: observation.observedAt,
@@ -1321,7 +1321,7 @@ function animationSnapshot(input) {
1321
1321
  const predicted = forecast ? forecastAt(forecast, at, { includeProbable: input.includeProbable }) : null;
1322
1322
  if (forecast && predicted) {
1323
1323
  return {
1324
- state: animationFromConditions(predicted.conditions),
1324
+ conditions: predicted.conditions,
1325
1325
  source: "TAF",
1326
1326
  station: forecast.station,
1327
1327
  validAt: at,
@@ -1332,7 +1332,7 @@ function animationSnapshot(input) {
1332
1332
  }
1333
1333
  if (observation) {
1334
1334
  return {
1335
- state: animationFromConditions(observation.conditions),
1335
+ conditions: observation.conditions,
1336
1336
  source: observation.kind,
1337
1337
  station: observation.station,
1338
1338
  validAt: observation.observedAt,
@@ -1343,6 +1343,12 @@ function animationSnapshot(input) {
1343
1343
  }
1344
1344
  return null;
1345
1345
  }
1346
+ function animationSnapshot(input) {
1347
+ const selected = selectConditions(input);
1348
+ if (!selected) return null;
1349
+ const { conditions, ...meta } = selected;
1350
+ return { state: animationFromConditions(conditions), ...meta };
1351
+ }
1346
1352
 
1347
1353
  // src/grid.ts
1348
1354
  var KT_TO_MS = 0.514444;
@@ -1805,6 +1811,323 @@ function weatherGridFromOpenMeteo(response, lattice, options = {}) {
1805
1811
  };
1806
1812
  }
1807
1813
 
1814
+ // src/ops.ts
1815
+ var METERS_PER_MILE2 = 1609.344;
1816
+ var FAA_CATEGORY_THRESHOLDS = {
1817
+ lifrCeilingFt: 500,
1818
+ ifrCeilingFt: 1e3,
1819
+ mvfrCeilingFt: 3e3,
1820
+ lifrVisibilityM: 1 * METERS_PER_MILE2,
1821
+ ifrVisibilityM: 3 * METERS_PER_MILE2,
1822
+ mvfrVisibilityM: 5 * METERS_PER_MILE2
1823
+ };
1824
+ var RANK = { LIFR: 0, IFR: 1, MVFR: 2, VFR: 3 };
1825
+ var BY_RANK = ["LIFR", "IFR", "MVFR", "VFR"];
1826
+ function ceilingOf(clouds) {
1827
+ let lowest = null;
1828
+ let unknown = false;
1829
+ for (const layer of clouds) {
1830
+ if (layer.cover !== "BKN" && layer.cover !== "OVC" && layer.cover !== "VV") continue;
1831
+ if (layer.baseFt === null) {
1832
+ unknown = true;
1833
+ continue;
1834
+ }
1835
+ lowest = lowest === null ? layer.baseFt : Math.min(lowest, layer.baseFt);
1836
+ }
1837
+ return { ceilingFt: lowest, unknown };
1838
+ }
1839
+ var rankByCeiling = (ceilingFt, t) => {
1840
+ if (ceilingFt === null) return RANK.VFR;
1841
+ if (ceilingFt < t.lifrCeilingFt) return RANK.LIFR;
1842
+ if (ceilingFt < t.ifrCeilingFt) return RANK.IFR;
1843
+ if (ceilingFt <= t.mvfrCeilingFt) return RANK.MVFR;
1844
+ return RANK.VFR;
1845
+ };
1846
+ var rankByVisibility = (visibilityM, t) => {
1847
+ if (visibilityM < t.lifrVisibilityM) return RANK.LIFR;
1848
+ if (visibilityM < t.ifrVisibilityM) return RANK.IFR;
1849
+ if (visibilityM <= t.mvfrVisibilityM) return RANK.MVFR;
1850
+ return RANK.VFR;
1851
+ };
1852
+ function flightCategory(conditions, thresholds = FAA_CATEGORY_THRESHOLDS) {
1853
+ if (conditions.cavok) {
1854
+ return { category: "VFR", ceilingFt: null, ceilingUnknown: false, visibilityM: 1e4, limitedBy: null };
1855
+ }
1856
+ const { ceilingFt, unknown } = ceilingOf(conditions.clouds);
1857
+ const visibilityM = conditions.visibilityM;
1858
+ const ceilingRank = unknown && ceilingFt === null ? null : rankByCeiling(ceilingFt, thresholds);
1859
+ const visibilityRank = visibilityM === null ? null : rankByVisibility(visibilityM, thresholds);
1860
+ if (ceilingRank === null && visibilityRank === null) {
1861
+ return { category: null, ceilingFt, ceilingUnknown: unknown, visibilityM, limitedBy: null };
1862
+ }
1863
+ const rank = Math.min(ceilingRank ?? Number.POSITIVE_INFINITY, visibilityRank ?? Number.POSITIVE_INFINITY);
1864
+ const limitedBy = ceilingRank !== null && visibilityRank !== null ? ceilingRank === visibilityRank ? "both" : ceilingRank < visibilityRank ? "ceiling" : "visibility" : ceilingRank !== null ? "ceiling" : "visibility";
1865
+ return {
1866
+ category: BY_RANK[rank] ?? null,
1867
+ ceilingFt,
1868
+ ceilingUnknown: unknown,
1869
+ visibilityM,
1870
+ limitedBy
1871
+ };
1872
+ }
1873
+ var round1 = (value) => Math.round(value * 10) / 10;
1874
+ function windComponents(wind, runwayHeadingTrueDeg) {
1875
+ if (wind.directionDeg === null) {
1876
+ return { headwindKt: null, tailwindKt: null, crosswindKt: round1(wind.speedKt), crosswindFrom: null, variable: true };
1877
+ }
1878
+ const angle = (wind.directionDeg - runwayHeadingTrueDeg) * Math.PI / 180;
1879
+ const head = wind.speedKt * Math.cos(angle);
1880
+ const cross = wind.speedKt * Math.sin(angle);
1881
+ return {
1882
+ headwindKt: round1(Math.max(0, head)),
1883
+ tailwindKt: round1(Math.max(0, -head)),
1884
+ crosswindKt: round1(Math.abs(cross)),
1885
+ crosswindFrom: Math.abs(cross) < 0.05 ? null : cross > 0 ? "right" : "left",
1886
+ variable: false
1887
+ };
1888
+ }
1889
+ function runwayAdvice(wind, runways, limits = {}) {
1890
+ const advise = (direction) => {
1891
+ const steady = windComponents(wind, direction.headingTrueDeg);
1892
+ const gust = wind.gustKt !== null && wind.gustKt !== void 0 ? windComponents({ directionDeg: wind.directionDeg, speedKt: wind.gustKt }, direction.headingTrueDeg) : void 0;
1893
+ const worst = gust ?? steady;
1894
+ return {
1895
+ direction,
1896
+ steady,
1897
+ ...gust ? { gust } : {},
1898
+ exceeds: {
1899
+ crosswind: limits.crosswindKt === void 0 ? null : worst.crosswindKt > limits.crosswindKt,
1900
+ tailwind: limits.tailwindKt === void 0 ? null : worst.tailwindKt === null ? null : worst.tailwindKt > limits.tailwindKt
1901
+ }
1902
+ };
1903
+ };
1904
+ return runways.map((runway) => {
1905
+ const first = advise(runway.directions[0]);
1906
+ const second = advise(runway.directions[1]);
1907
+ const headA = first.steady.headwindKt ?? Number.NEGATIVE_INFINITY;
1908
+ const headB = second.steady.headwindKt ?? Number.NEGATIVE_INFINITY;
1909
+ const preferred = headA === headB ? first.steady.crosswindKt <= second.steady.crosswindKt ? first : second : headA > headB ? first : second;
1910
+ return { runway, directions: [first, second], preferred };
1911
+ });
1912
+ }
1913
+ function rvrAgainst(rvr, minimum) {
1914
+ const value = rvr.variableMinM ?? rvr.meters;
1915
+ if (rvr.modifier === "above") return value >= minimum ? "above" : "unknown";
1916
+ if (rvr.modifier === "below") return value <= minimum ? "below" : "unknown";
1917
+ return value < minimum ? "below" : "above";
1918
+ }
1919
+ function evaluateMinima(conditions, minima) {
1920
+ const reasons = [];
1921
+ const unknown = [];
1922
+ if (minima.visibilityM !== void 0) {
1923
+ const visibility = conditions.cavok ? 1e4 : conditions.visibilityM;
1924
+ if (visibility === null) unknown.push("visibility");
1925
+ else if (visibility < minima.visibilityM) {
1926
+ reasons.push({ kind: "visibility", value: visibility, minimum: minima.visibilityM });
1927
+ }
1928
+ }
1929
+ if (minima.ceilingFt !== void 0 && !conditions.cavok) {
1930
+ const { ceilingFt, unknown: ceilingUnknown } = ceilingOf(conditions.clouds);
1931
+ if (ceilingFt !== null && ceilingFt < minima.ceilingFt) {
1932
+ reasons.push({ kind: "ceiling", value: ceilingFt, minimum: minima.ceilingFt });
1933
+ } else if (ceilingUnknown && ceilingFt === null) unknown.push("ceiling");
1934
+ }
1935
+ if (minima.rvrM !== void 0) {
1936
+ const wanted = minima.rvrRunways;
1937
+ const reports = (conditions.rvr ?? []).filter((r) => !wanted || wanted.includes(r.runway));
1938
+ if (reports.length === 0) unknown.push("rvr");
1939
+ for (const rvr of reports) {
1940
+ const verdict = rvrAgainst(rvr, minima.rvrM);
1941
+ if (verdict === "below") {
1942
+ reasons.push({ kind: "rvr", value: rvr.variableMinM ?? rvr.meters, minimum: minima.rvrM, runway: rvr.runway });
1943
+ } else if (verdict === "unknown" && !unknown.includes("rvr")) unknown.push("rvr");
1944
+ }
1945
+ }
1946
+ return { below: reasons.length > 0, reasons, unknown };
1947
+ }
1948
+ var PRECIPITATION2 = /* @__PURE__ */ new Set(["DZ", "RA", "SN", "SG", "IC", "PL", "GR", "GS", "UP"]);
1949
+ var hasThunderstorm = (c) => c.weather.some((w) => w.descriptor === "TS" && w.intensity !== "vicinity") || c.clouds.some((layer) => layer.type === "CB");
1950
+ var hasHeavyPrecipitation = (weather) => weather.some((w) => w.intensity === "heavy" && w.phenomena.some((p) => PRECIPITATION2.has(p)));
1951
+ function sourceAt(taf, time, includeProbable) {
1952
+ const covers = (p) => time >= p.from.getTime() && time < p.to.getTime();
1953
+ if (taf.periods.some((p) => p.change === "TEMPO" && covers(p) && (p.probability === void 0 || includeProbable))) {
1954
+ return "TEMPO";
1955
+ }
1956
+ if (includeProbable && taf.periods.some((p) => p.change === "PROB" && covers(p))) return "PROB";
1957
+ let latest = null;
1958
+ for (const p of taf.periods) {
1959
+ if ((p.change === "FM" || p.change === "BECMG") && p.from.getTime() <= time) {
1960
+ if (!latest || p.from.getTime() >= latest.from.getTime()) latest = p;
1961
+ }
1962
+ }
1963
+ return latest ? latest.change : "BASE";
1964
+ }
1965
+ function upcomingChanges(taf, from, hours, options = {}) {
1966
+ if (taf.cancelled) return [];
1967
+ const thresholds = options.thresholds ?? FAA_CATEGORY_THRESHOLDS;
1968
+ const includeProbable = options.includeProbable === true;
1969
+ const step = (options.stepMinutes ?? 30) * 6e4;
1970
+ const start = Math.max(from.getTime(), taf.validFrom.getTime());
1971
+ const end = Math.min(from.getTime() + hours * 36e5, taf.validTo.getTime());
1972
+ if (start >= end) return [];
1973
+ const times = /* @__PURE__ */ new Set();
1974
+ for (let t = start; t <= end; t += step) times.add(t);
1975
+ for (const p of taf.periods) {
1976
+ for (const edge of [p.from.getTime(), p.to.getTime()]) if (edge > start && edge < end) times.add(edge);
1977
+ }
1978
+ const events = [];
1979
+ let previous = null;
1980
+ let windBaseline = null;
1981
+ for (const time of [...times].sort((a, b) => a - b)) {
1982
+ const at = forecastAt(taf, new Date(time), { includeProbable });
1983
+ if (!at) continue;
1984
+ const c = at.conditions;
1985
+ const category = flightCategory(c, thresholds).category;
1986
+ const thunder = hasThunderstorm(c);
1987
+ const heavy = hasHeavyPrecipitation(c.weather);
1988
+ const lowVis = options.lowVisibilityM !== void 0 && c.visibilityM !== null && c.visibilityM < options.lowVisibilityM;
1989
+ const source = sourceAt(taf, time, includeProbable);
1990
+ const when = new Date(time);
1991
+ if (!previous) {
1992
+ if (thunder) {
1993
+ events.push({ kind: "THUNDERSTORM_START", at: when, source, ongoing: true, detail: "Badai petir/CB sudah berlangsung" });
1994
+ }
1995
+ windBaseline = c.wind.speedKt;
1996
+ } else {
1997
+ if (category !== previous.category && category !== null) {
1998
+ events.push({
1999
+ kind: "CATEGORY_CHANGE",
2000
+ at: when,
2001
+ source,
2002
+ from: previous.category,
2003
+ to: category,
2004
+ detail: `Kategori ${previous.category ?? "?"} menjadi ${category}`
2005
+ });
2006
+ }
2007
+ if (thunder && !previous.thunder) events.push({ kind: "THUNDERSTORM_START", at: when, source, detail: "Badai petir/CB mulai" });
2008
+ if (!thunder && previous.thunder) events.push({ kind: "THUNDERSTORM_END", at: when, source, detail: "Badai petir/CB berakhir" });
2009
+ if (heavy && !previous.heavy) events.push({ kind: "HEAVY_PRECIPITATION_START", at: when, source, detail: "Hujan atau presipitasi lebat" });
2010
+ if (lowVis && !previous.lowVis) {
2011
+ events.push({ kind: "LOW_VISIBILITY_START", at: when, source, detail: `Jarak pandang di bawah ${options.lowVisibilityM} m` });
2012
+ }
2013
+ if (options.windChangeKt !== void 0 && windBaseline !== null && Math.abs(c.wind.speedKt - windBaseline) >= options.windChangeKt) {
2014
+ events.push({
2015
+ kind: "WIND_CHANGE",
2016
+ at: when,
2017
+ source,
2018
+ detail: `Angin ${windBaseline} menjadi ${c.wind.speedKt} kt`
2019
+ });
2020
+ windBaseline = c.wind.speedKt;
2021
+ }
2022
+ }
2023
+ previous = { category, thunder, heavy, lowVis };
2024
+ }
2025
+ return events;
2026
+ }
2027
+ function qnhTrend(previous, current, options = {}) {
2028
+ if (previous.qnhHpa === null || current.qnhHpa === null) return null;
2029
+ const hours = (current.observedAt.getTime() - previous.observedAt.getTime()) / 36e5;
2030
+ if (hours <= 0) return null;
2031
+ const delta = current.qnhHpa - previous.qnhHpa;
2032
+ const steady = Math.abs(delta) < (options.steadyHpa ?? 0.5);
2033
+ const perHour = delta / hours;
2034
+ return {
2035
+ deltaHpa: delta,
2036
+ hours,
2037
+ hpaPerHour: Math.round(perHour * 100) / 100,
2038
+ direction: steady ? "steady" : delta > 0 ? "rising" : "falling",
2039
+ rapid: options.rapidHpaPerHour === void 0 ? null : Math.abs(perHour) >= options.rapidHpaPerHour
2040
+ };
2041
+ }
2042
+
2043
+ // src/operations.ts
2044
+ function stationOperations(store, station, at, config = {}) {
2045
+ const selected = store.conditionsAt(station, at, { maxObservationAgeMs: config.maxObservationAgeMs });
2046
+ if (!selected) return null;
2047
+ const { conditions } = selected;
2048
+ const taf = store.forecastFor(station, at);
2049
+ const upcoming = taf ? upcomingChanges(taf, at, config.horizonHours ?? 6, { ...config.upcoming, thresholds: config.thresholds }) : [];
2050
+ const [latest, previous] = store.observationsUntil(station, at, 2);
2051
+ return {
2052
+ station,
2053
+ at,
2054
+ basis: {
2055
+ source: selected.source,
2056
+ validAt: selected.validAt,
2057
+ stale: selected.stale,
2058
+ temporary: selected.temporary,
2059
+ transition: selected.transition
2060
+ },
2061
+ category: flightCategory(conditions, config.thresholds),
2062
+ ...config.runways ? { runways: runwayAdvice(conditions.wind, config.runways, config.limits) } : {},
2063
+ ...config.minima ? { minima: evaluateMinima(conditions, config.minima) } : {},
2064
+ upcoming,
2065
+ qnh: latest && previous ? qnhTrend(previous, latest, config.qnh) : null
2066
+ };
2067
+ }
2068
+
2069
+ // src/runway.ts
2070
+ var normalize = (degrees) => (degrees % 360 + 360) % 360;
2071
+ function magneticToTrue(magneticDeg, variationEastDeg) {
2072
+ return normalize(magneticDeg + variationEastDeg);
2073
+ }
2074
+ var reciprocal = (headingDeg) => normalize(headingDeg + 180);
2075
+ function reciprocalDesignator(designator) {
2076
+ const match = designator.trim().toUpperCase().match(/^(\d{2})([LRC])?$/);
2077
+ if (!match) return null;
2078
+ const number = Number(match[1]);
2079
+ if (number < 1 || number > 36) return null;
2080
+ const opposite = (number + 18 - 1) % 36 + 1;
2081
+ const side = match[2] === "L" ? "R" : match[2] === "R" ? "L" : match[2] ?? "";
2082
+ return `${String(opposite).padStart(2, "0")}${side}`;
2083
+ }
2084
+ function designatorHeadingDeg(designator) {
2085
+ const match = designator.trim().match(/^(\d{2})[LRC]?$/);
2086
+ return match ? Number(match[1]) * 10 : null;
2087
+ }
2088
+ function runwayFromHeading(designator, magneticHeadingDeg, variationEastDeg = 0) {
2089
+ const opposite = reciprocalDesignator(designator);
2090
+ const magnetic = magneticHeadingDeg ?? designatorHeadingDeg(designator);
2091
+ if (!opposite || magnetic === null) return null;
2092
+ const heading = magneticToTrue(magnetic, variationEastDeg);
2093
+ const [low, high] = [designator.toUpperCase(), opposite].sort();
2094
+ const first = { designator: designator.toUpperCase(), headingTrueDeg: heading };
2095
+ const second = { designator: opposite, headingTrueDeg: reciprocal(heading) };
2096
+ return {
2097
+ name: `${low}/${high}`,
2098
+ directions: [first, second]
2099
+ };
2100
+ }
2101
+ function headingFromPolygon(points) {
2102
+ if (points.length < 2) return null;
2103
+ const meanLat = points.reduce((sum, p) => sum + p.lat, 0) / points.length;
2104
+ const meanLon = points.reduce((sum, p) => sum + p.lon, 0) / points.length;
2105
+ const kx = Math.cos(meanLat * Math.PI / 180);
2106
+ let sxx = 0;
2107
+ let syy = 0;
2108
+ let sxy = 0;
2109
+ for (const p of points) {
2110
+ const x = (p.lon - meanLon) * kx;
2111
+ const y = p.lat - meanLat;
2112
+ sxx += x * x;
2113
+ syy += y * y;
2114
+ sxy += x * y;
2115
+ }
2116
+ if (sxx + syy === 0) return null;
2117
+ const theta = 0.5 * Math.atan2(2 * sxy, sxx - syy);
2118
+ const heading = Math.atan2(Math.cos(theta), Math.sin(theta)) * 180 / Math.PI;
2119
+ return normalize(heading) % 180;
2120
+ }
2121
+ function runwayFromPolygon(points, variationEastDeg = 0) {
2122
+ const trueHeading = headingFromPolygon(points);
2123
+ if (trueHeading === null) return null;
2124
+ const magnetic = normalize(trueHeading - variationEastDeg);
2125
+ const number = Math.round(magnetic / 10) % 36 || 36;
2126
+ const designator = String(number).padStart(2, "0");
2127
+ const spec = runwayFromHeading(designator, magnetic, variationEastDeg);
2128
+ return spec;
2129
+ }
2130
+
1808
2131
  // src/store.ts
1809
2132
  var MAX_OBSERVATIONS_PER_STATION = 48;
1810
2133
  var MAX_FORECASTS_PER_STATION = 8;
@@ -1903,6 +2226,25 @@ var WeatherStore = class {
1903
2226
  includeProbable: options.includeProbable
1904
2227
  });
1905
2228
  }
2229
+ /** Kondisi (belum diubah menjadi parameter animasi) di satu stasiun pada waktu `at`. */
2230
+ conditionsAt(station, at, options = {}) {
2231
+ return selectConditions({
2232
+ observation: this.pickObservation(station, at, options.maxObservationAgeMs),
2233
+ forecast: this.pickForecast(station, at),
2234
+ at,
2235
+ maxObservationAgeMs: options.maxObservationAgeMs,
2236
+ includeProbable: options.includeProbable
2237
+ });
2238
+ }
2239
+ /** TAF yang berlaku di satu stasiun pada waktu `at` (manual didahulukan), atau null. */
2240
+ forecastFor(station, at) {
2241
+ return this.pickForecast(station, at);
2242
+ }
2243
+ /** Pengamatan terakhir (paling baru dulu) di satu stasiun sampai waktu `at`, paling banyak `count`. */
2244
+ observationsUntil(station, at, count) {
2245
+ const list = this.observations.get(station) ?? [];
2246
+ return list.map((s) => s.report).filter((m) => m.observedAt.getTime() <= at.getTime()).sort((x, y) => y.observedAt.getTime() - x.observedAt.getTime()).slice(0, count);
2247
+ }
1906
2248
  /** Rentang waktu dan pembagian periode (BASE/BECMG/TEMPO/FM) dari TAF, untuk timeline. */
1907
2249
  timeline() {
1908
2250
  const segments = [];
@@ -2073,6 +2415,7 @@ function decodeWeatherMessage(message, reference = /* @__PURE__ */ new Date()) {
2073
2415
  }
2074
2416
  export {
2075
2417
  ALTITUDE_PATTERNS,
2418
+ FAA_CATEGORY_THRESHOLDS,
2076
2419
  POINT,
2077
2420
  WEATHER_GRID_STANDARD,
2078
2421
  WEATHER_GRID_VERSION,
@@ -2081,18 +2424,24 @@ export {
2081
2424
  animationSnapshot,
2082
2425
  blendConditions,
2083
2426
  blendWindGrids,
2427
+ ceilingOf,
2084
2428
  compassDegrees,
2085
2429
  decodeGridJson,
2086
2430
  decodeWeatherMessage,
2431
+ designatorHeadingDeg,
2087
2432
  directionFromUV,
2088
2433
  emptyConditions,
2434
+ evaluateMinima,
2089
2435
  extractReports,
2090
2436
  findAltitude,
2091
2437
  findMovement,
2438
+ flightCategory,
2092
2439
  forecastAt,
2440
+ headingFromPolygon,
2093
2441
  interpolateScalar,
2094
2442
  isCompass,
2095
2443
  latticePoints,
2444
+ magneticToTrue,
2096
2445
  mergeConditions,
2097
2446
  nudgeScalar,
2098
2447
  nudgeWind,
@@ -2110,16 +2459,26 @@ export {
2110
2459
  parseVaa,
2111
2460
  parseWeatherGridSeries,
2112
2461
  pointOf,
2462
+ qnhTrend,
2463
+ reciprocal,
2464
+ reciprocalDesignator,
2113
2465
  resolveDayTime,
2466
+ runwayAdvice,
2467
+ runwayFromHeading,
2468
+ runwayFromPolygon,
2114
2469
  sampleScalar,
2115
2470
  sampleWind,
2471
+ selectConditions,
2116
2472
  serializeWeatherGridSeries,
2117
2473
  seriesFieldsAt,
2118
2474
  seriesRange,
2119
2475
  speedOf,
2476
+ stationOperations,
2120
2477
  stationWindsFromMetars,
2121
2478
  stationWindsFromModelPoints,
2479
+ upcomingChanges,
2122
2480
  uvFromSpeedDirection,
2123
2481
  weatherGridFromOpenMeteo,
2482
+ windComponents,
2124
2483
  windGridFromStations
2125
2484
  };
@@ -0,0 +1,41 @@
1
+ import type { SelectedConditions } from "./animation.js";
2
+ import { type CategoryThresholds, type FlightCategoryResult, type Minima, type MinimaEvaluation, type QnhTrend, type RunwayAdvice, type RunwayLimits, type UpcomingChange, type UpcomingOptions } from "./ops.js";
3
+ import type { RunwaySpec } from "./runway.js";
4
+ import type { WeatherStore } from "./store.js";
5
+ export interface OperationsConfig {
6
+ thresholds?: CategoryThresholds;
7
+ /** Runway yang ditinjau (heading true); tanpa ini tidak ada saran runway. */
8
+ runways?: readonly RunwaySpec[];
9
+ /** Batas angin silang/belakang untuk menandai `exceeds`; tanpa ini tidak ada yang ditandai. */
10
+ limits?: RunwayLimits;
11
+ /** Minima atau ambang LVP; tanpa ini tidak ada evaluasi. */
12
+ minima?: Minima;
13
+ /** Berapa jam ke depan peristiwa TAF dicari. Bawaan 6. */
14
+ horizonHours?: number;
15
+ upcoming?: Omit<UpcomingOptions, "thresholds">;
16
+ qnh?: {
17
+ steadyHpa?: number;
18
+ rapidHpaPerHour?: number;
19
+ };
20
+ maxObservationAgeMs?: number;
21
+ }
22
+ export interface StationOperations {
23
+ station: string;
24
+ at: Date;
25
+ /** Dari mana kondisi diambil (METAR, SPECI, atau TAF) dan apakah sudah lama. */
26
+ basis: Pick<SelectedConditions, "source" | "validAt" | "stale" | "temporary" | "transition">;
27
+ category: FlightCategoryResult;
28
+ runways?: RunwayAdvice[];
29
+ minima?: MinimaEvaluation;
30
+ /** Peristiwa dari TAF dalam `horizonHours` jam; kosong bila tidak ada TAF yang berlaku. */
31
+ upcoming: UpcomingChange[];
32
+ /** Tren QNH dari dua pengamatan terakhir; null bila kurang dari dua atau QNH tidak ada. */
33
+ qnh: QnhTrend | null;
34
+ }
35
+ /**
36
+ * Ringkasan operasional satu stasiun pada waktu `at`, dari data yang dipegang `WeatherStore`: kategori
37
+ * penerbangan, saran runway menurut angin, evaluasi minima, peristiwa TAF berikutnya, dan tren QNH.
38
+ * Null bila stasiun tidak punya data. Hasilnya bantuan keputusan, bukan sumber resmi.
39
+ */
40
+ export declare function stationOperations(store: WeatherStore, station: string, at: Date, config?: OperationsConfig): StationOperations | null;
41
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,oBAAoB,EAEzB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,eAAe,EAErB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,+FAA+F;IAC/F,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;IAC/C,GAAG,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,IAAI,CAAC;IACT,gFAAgF;IAChF,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;IAC7F,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,2FAA2F;IAC3F,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,2FAA2F;IAC3F,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,IAAI,EACR,MAAM,GAAE,gBAAqB,GAC5B,iBAAiB,GAAG,IAAI,CA6B1B"}
package/dist/ops.d.ts ADDED
@@ -0,0 +1,181 @@
1
+ import type { CloudLayer, Conditions, Taf, TafChange } from "./types.js";
2
+ import type { RunwayDirection, RunwaySpec } from "./runway.js";
3
+ /**
4
+ * Cuaca menjadi informasi operasi bandara: kategori penerbangan, angin silang, minima, perubahan yang
5
+ * akan datang, dan tren tekanan. Semua murni. Nilai ambang yang menentukan keputusan operasi
6
+ * (angin silang maksimum, minima, ambang LVP) tidak pernah ditebak: pemanggil yang memberikannya.
7
+ * Hasilnya bantuan keputusan, bukan sumber resmi.
8
+ */
9
+ export type FlightCategory = "VFR" | "MVFR" | "IFR" | "LIFR";
10
+ /**
11
+ * Ambang kategori penerbangan. Bawaan mengikuti definisi FAA yang dipakai luas (ICAO tidak
12
+ * mendefinisikan kategori ini): LIFR di bawah 500 ft atau 1 mil, IFR di bawah 1.000 ft atau 3 mil, MVFR
13
+ * sampai 3.000 ft atau 5 mil, selain itu VFR. Ganti bila bandara memakai ambang lain.
14
+ */
15
+ export interface CategoryThresholds {
16
+ /** Plafon (ft) di bawah ini LIFR. */
17
+ lifrCeilingFt: number;
18
+ /** Plafon (ft) di bawah ini IFR. */
19
+ ifrCeilingFt: number;
20
+ /** Plafon (ft) sampai nilai ini (termasuk) MVFR; di atasnya VFR. */
21
+ mvfrCeilingFt: number;
22
+ /** Jarak pandang (m) di bawah ini LIFR. */
23
+ lifrVisibilityM: number;
24
+ /** Jarak pandang (m) di bawah ini IFR. */
25
+ ifrVisibilityM: number;
26
+ /** Jarak pandang (m) sampai nilai ini (termasuk) MVFR; di atasnya VFR. */
27
+ mvfrVisibilityM: number;
28
+ }
29
+ export declare const FAA_CATEGORY_THRESHOLDS: CategoryThresholds;
30
+ export interface FlightCategoryResult {
31
+ /** Null bila jarak pandang dan plafon sama-sama tidak diketahui. */
32
+ category: FlightCategory | null;
33
+ /** Plafon (ft di atas tanah): dasar awan BKN/OVC atau VV terendah; null = tidak ada plafon. */
34
+ ceilingFt: number | null;
35
+ /** Ada lapisan BKN/OVC yang dasarnya tidak dilaporkan (`BKN///`): plafon tidak bisa dipastikan. */
36
+ ceilingUnknown: boolean;
37
+ visibilityM: number | null;
38
+ /** Yang menentukan kategori: plafon, jarak pandang, atau keduanya sama. */
39
+ limitedBy: "ceiling" | "visibility" | "both" | null;
40
+ }
41
+ /** Plafon: dasar terendah dari awan BKN/OVC atau VV. `unknown` bila ada yang tidak diketahui dasarnya. */
42
+ export declare function ceilingOf(clouds: readonly CloudLayer[]): {
43
+ ceilingFt: number | null;
44
+ unknown: boolean;
45
+ };
46
+ /** Kategori penerbangan dari jarak pandang dan plafon. `CAVOK` selalu VFR. */
47
+ export declare function flightCategory(conditions: Pick<Conditions, "visibilityM" | "clouds" | "cavok">, thresholds?: CategoryThresholds): FlightCategoryResult;
48
+ export interface WindInput {
49
+ /** Arah ASAL angin, derajat TRUE; null = berubah-ubah (VRB). */
50
+ directionDeg: number | null;
51
+ speedKt: number;
52
+ gustKt?: number | null;
53
+ }
54
+ export interface WindComponents {
55
+ /** Angin depan (kt, >= 0). Null bila arah angin berubah-ubah. */
56
+ headwindKt: number | null;
57
+ /** Angin belakang (kt, >= 0). Null bila arah angin berubah-ubah. */
58
+ tailwindKt: number | null;
59
+ /** Angin silang (kt, >= 0). Bila arah berubah-ubah: batas atas (kecepatan penuh). */
60
+ crosswindKt: number;
61
+ /** Dari sisi mana angin silang datang, dilihat dari pesawat yang searah runway. */
62
+ crosswindFrom: "left" | "right" | null;
63
+ /** Arah angin berubah-ubah: angka hanya batas atas. */
64
+ variable: boolean;
65
+ }
66
+ /**
67
+ * Komponen angin terhadap arah runway (heading TRUE). Angin depan positif bila angin datang dari depan;
68
+ * angin silang datang dari kanan bila arah angin searah jarum jam dari heading.
69
+ */
70
+ export declare function windComponents(wind: Pick<WindInput, "directionDeg" | "speedKt">, runwayHeadingTrueDeg: number): WindComponents;
71
+ export interface RunwayLimits {
72
+ /** Angin silang maksimum (kt); tidak diisi = tidak diperiksa. */
73
+ crosswindKt?: number;
74
+ /** Angin belakang maksimum (kt); tidak diisi = tidak diperiksa. */
75
+ tailwindKt?: number;
76
+ }
77
+ export interface DirectionAdvice {
78
+ direction: RunwayDirection;
79
+ steady: WindComponents;
80
+ /** Dengan hembusan (gust), bila dilaporkan. */
81
+ gust?: WindComponents;
82
+ /** Melewati batas yang diberikan; null bila batasnya tidak diberikan. */
83
+ exceeds: {
84
+ crosswind: boolean | null;
85
+ tailwind: boolean | null;
86
+ };
87
+ }
88
+ export interface RunwayAdvice {
89
+ runway: RunwaySpec;
90
+ directions: [DirectionAdvice, DirectionAdvice];
91
+ /** Arah yang lebih menguntungkan: angin depan terbesar (seri: angin silang lebih kecil). */
92
+ preferred: DirectionAdvice;
93
+ }
94
+ /**
95
+ * Untuk tiap runway, hitung komponen angin di kedua arahnya dan tunjukkan arah yang lebih
96
+ * menguntungkan. `limits` hanya dipakai untuk menandai `exceeds`; tanpa batas, tidak ada yang ditandai.
97
+ * Pemilihan runway aktif tetap keputusan operasi, bukan hasil fungsi ini.
98
+ */
99
+ export declare function runwayAdvice(wind: WindInput, runways: readonly RunwaySpec[], limits?: RunwayLimits): RunwayAdvice[];
100
+ export interface Minima {
101
+ /** Jarak pandang minimum (m): di bawahnya = di bawah minima. */
102
+ visibilityM?: number;
103
+ /** Plafon minimum (ft): di bawahnya = di bawah minima. */
104
+ ceilingFt?: number;
105
+ /** RVR minimum (m): di bawahnya = di bawah minima. Bila `rvrRunways` diisi, hanya runway itu. */
106
+ rvrM?: number;
107
+ rvrRunways?: string[];
108
+ }
109
+ export interface MinimaReason {
110
+ kind: "visibility" | "ceiling" | "rvr";
111
+ value: number;
112
+ minimum: number;
113
+ runway?: string;
114
+ }
115
+ export interface MinimaEvaluation {
116
+ /** Ada satu atau lebih alasan pasti di bawah minima. */
117
+ below: boolean;
118
+ reasons: MinimaReason[];
119
+ /** Besaran yang diminta tetapi datanya tidak cukup untuk memutuskan (tidak dianggap aman). */
120
+ unknown: ("visibility" | "ceiling" | "rvr")[];
121
+ }
122
+ /**
123
+ * Bandingkan kondisi dengan minima (atau ambang prosedur visibilitas rendah; maknanya sama: salah satu
124
+ * besaran di bawah ambangnya). Data yang tidak ada tidak dianggap aman: dilaporkan di `unknown`.
125
+ */
126
+ export declare function evaluateMinima(conditions: Pick<Conditions, "visibilityM" | "clouds" | "cavok" | "rvr">, minima: Minima): MinimaEvaluation;
127
+ export type UpcomingKind = "CATEGORY_CHANGE" | "THUNDERSTORM_START" | "THUNDERSTORM_END" | "HEAVY_PRECIPITATION_START" | "LOW_VISIBILITY_START" | "WIND_CHANGE";
128
+ export interface UpcomingChange {
129
+ kind: UpcomingKind;
130
+ /** Saat perubahan mulai terlihat (resolusi `stepMinutes`, ditambah batas periode TAF). */
131
+ at: Date;
132
+ /** Bagian TAF yang menyebabkannya. */
133
+ source: TafChange;
134
+ /** Sudah berlangsung pada awal rentang (hanya untuk THUNDERSTORM_START). */
135
+ ongoing?: boolean;
136
+ from?: FlightCategory | null;
137
+ to?: FlightCategory | null;
138
+ /** Keterangan singkat yang bisa ditampilkan. */
139
+ detail: string;
140
+ }
141
+ export interface UpcomingOptions {
142
+ /** Langkah pencuplikan (menit). Bawaan 30. */
143
+ stepMinutes?: number;
144
+ /** Ikutkan grup PROB30/40. Bawaan false. */
145
+ includeProbable?: boolean;
146
+ thresholds?: CategoryThresholds;
147
+ /** Tandai jarak pandang di bawah nilai ini (m); tidak diisi = tidak ditandai. */
148
+ lowVisibilityM?: number;
149
+ /** Tandai perubahan kecepatan angin sebesar ini (kt) dari nilai terakhir yang ditandai; tidak diisi = tidak ditandai. */
150
+ windChangeKt?: number;
151
+ }
152
+ /**
153
+ * Peristiwa penting dari TAF dalam `hours` jam sejak `from`: perubahan kategori penerbangan, awal dan
154
+ * akhir badai petir/CB, hujan lebat, jarak pandang rendah, dan perubahan angin (dua yang terakhir hanya
155
+ * bila ambangnya diberikan). Dibandingkan dengan keadaan awal, jadi yang sudah berlangsung ditandai
156
+ * `ongoing`, bukan sebagai peristiwa baru.
157
+ */
158
+ export declare function upcomingChanges(taf: Taf, from: Date, hours: number, options?: UpcomingOptions): UpcomingChange[];
159
+ export interface QnhTrend {
160
+ deltaHpa: number;
161
+ hours: number;
162
+ hpaPerHour: number;
163
+ direction: "rising" | "falling" | "steady";
164
+ /** Cepat bila `rapidHpaPerHour` diberikan dan terlampaui; null bila ambangnya tidak diberikan. */
165
+ rapid: boolean | null;
166
+ }
167
+ /**
168
+ * Perubahan QNH antara dua pengamatan. `steadyHpa` (bawaan 0,5): selisih di bawah ini dianggap tetap.
169
+ * Kecepatan perubahan dibandingkan dengan `rapidHpaPerHour` hanya bila diberikan.
170
+ */
171
+ export declare function qnhTrend(previous: {
172
+ qnhHpa: number | null;
173
+ observedAt: Date;
174
+ }, current: {
175
+ qnhHpa: number | null;
176
+ observedAt: Date;
177
+ }, options?: {
178
+ steadyHpa?: number;
179
+ rapidHpaPerHour?: number;
180
+ }): QnhTrend | null;
181
+ //# sourceMappingURL=ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../src/ops.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAuB,GAAG,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;GAKG;AAIH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;CACzB;AAID,eAAO,MAAM,uBAAuB,EAAE,kBAOrC,CAAC;AAKF,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,+FAA+F;IAC/F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mGAAmG;IACnG,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,SAAS,EAAE,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,IAAI,CAAC;CACrD;AAED,0GAA0G;AAC1G,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAYvG;AAiBD,8EAA8E;AAC9E,wBAAgB,cAAc,CAC5B,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC,EAChE,UAAU,GAAE,kBAA4C,GACvD,oBAAoB,CA8BtB;AAID,MAAM,WAAW,SAAS;IACxB,gEAAgE;IAChE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACvC,uDAAuD;IACvD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC,EACjD,oBAAoB,EAAE,MAAM,GAC3B,cAAc,CAchB;AAED,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,yEAAyE;IACzE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;CAClE;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAC/C,4FAA4F;IAC5F,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,MAAM,GAAE,YAAiB,GACxB,YAAY,EAAE,CAoChB;AAID,MAAM,WAAW,MAAM;IACrB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,8FAA8F;IAC9F,OAAO,EAAE,CAAC,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC;CAC/C;AAUD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC,EACxE,MAAM,EAAE,MAAM,GACb,gBAAgB,CAgClB;AAID,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,oBAAoB,GACpB,kBAAkB,GAClB,2BAA2B,GAC3B,sBAAsB,GACtB,aAAa,CAAC;AAElB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,0FAA0F;IAC1F,EAAE,EAAE,IAAI,CAAC;IACT,sCAAsC;IACtC,MAAM,EAAE,SAAS,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,EAAE,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3B,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yHAAyH;IACzH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8BD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,GAC5B,cAAc,EAAE,CAsElB;AAID,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC3C,kGAAkG;IAClG,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,EACrD,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,EACpD,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,QAAQ,GAAG,IAAI,CAcjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ops.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.test.d.ts","sourceRoot":"","sources":["../src/ops.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import type { GeoPosition } from "./geo.js";
2
+ /**
3
+ * Satu arah pemakaian runway. `headingTrueDeg` adalah arah TRUE (dari utara geografis), karena angin
4
+ * pada METAR/SPECI dilaporkan dalam derajat true. Heading runway biasanya diberikan magnetik; ubah dulu
5
+ * dengan `magneticToTrue`.
6
+ */
7
+ export interface RunwayDirection {
8
+ /** Penunjuk, mis. `28`, `10L`. */
9
+ designator: string;
10
+ headingTrueDeg: number;
11
+ }
12
+ /** Sebuah runway dengan kedua arah pakainya. */
13
+ export interface RunwaySpec {
14
+ /** Nama, mis. `10/28`. */
15
+ name: string;
16
+ directions: [RunwayDirection, RunwayDirection];
17
+ }
18
+ /** Heading magnetik menjadi true. `variationEastDeg` positif bila deklinasi ke timur (true = magnetik + variasi). */
19
+ export declare function magneticToTrue(magneticDeg: number, variationEastDeg: number): number;
20
+ /** Arah balik (+180). */
21
+ export declare const reciprocal: (headingDeg: number) => number;
22
+ /** Pasangan penunjuk lawan arah: `10` -> `28`, `09L` -> `27R`, `36` -> `18`. */
23
+ export declare function reciprocalDesignator(designator: string): string | null;
24
+ /** Perkiraan heading magnetik dari penunjuk (`28` -> 280). Hanya pendekatan: dibulatkan ke 10 derajat. */
25
+ export declare function designatorHeadingDeg(designator: string): number | null;
26
+ /**
27
+ * Susun `RunwaySpec` dari penunjuk salah satu arah dan heading magnetiknya (arah itu), mis.
28
+ * `runwayFromHeading("10", 98, 1)` -> runway `10/28`. Bila `magneticHeadingDeg` tidak diberikan dipakai
29
+ * pendekatan dari penunjuk (dibulatkan 10 derajat), sehingga angin silang ikut tidak teliti.
30
+ */
31
+ export declare function runwayFromHeading(designator: string, magneticHeadingDeg?: number, variationEastDeg?: number): RunwaySpec | null;
32
+ /**
33
+ * Heading true sumbu panjang sebuah poligon runway (0 sampai 180, dari utara searah jarum jam): sumbu
34
+ * utama sebaran titik (PCA), dengan bujur diskalakan menurut lintang. Berguna bila data aerodrome
35
+ * hanya punya poligon runway tanpa heading. Null bila kurang dari dua titik berbeda.
36
+ */
37
+ export declare function headingFromPolygon(points: readonly GeoPosition[]): number | null;
38
+ /**
39
+ * Runway dari poligonnya: sumbu panjang menjadi heading, dan penunjuk dipilih dari heading itu
40
+ * (pembulatan 10 derajat, dikurangi variasi untuk mendapat magnetik). `variationEastDeg` positif ke
41
+ * timur. Null bila poligon tidak cukup.
42
+ */
43
+ export declare function runwayFromPolygon(points: readonly GeoPosition[], variationEastDeg?: number): RunwaySpec | null;
44
+ //# sourceMappingURL=runway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runway.d.ts","sourceRoot":"","sources":["../src/runway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;CAChD;AAID,qHAAqH;AACrH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,yBAAyB;AACzB,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,KAAG,MAAqC,CAAC;AAEtF,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,0GAA0G;AAC1G,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,kBAAkB,CAAC,EAAE,MAAM,EAC3B,gBAAgB,SAAI,GACnB,UAAU,GAAG,IAAI,CAYnB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAoBhF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,EAAE,gBAAgB,SAAI,GAAG,UAAU,GAAG,IAAI,CAQzG"}
package/dist/store.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { type AnimationSnapshot } from "./animation.js";
1
+ import { type AnimationSnapshot, type SelectedConditions } from "./animation.js";
2
2
  import { type GeoPoint, type GridBounds, type NudgeOptions, type ScalarGrid, type WindGrid } from "./grid.js";
3
- import type { TafChange } from "./types.js";
3
+ import type { Metar, Taf, TafChange } from "./types.js";
4
4
  export type Locate = (icao: string) => GeoPoint | undefined;
5
5
  export interface IngestOptions {
6
6
  /** Waktu pesan diterima (menentukan bulan/tahun laporan). Bawaan: sekarang. */
@@ -87,6 +87,15 @@ export declare class WeatherStore {
87
87
  maxObservationAgeMs?: number;
88
88
  includeProbable?: boolean;
89
89
  }): AnimationSnapshot | null;
90
+ /** Kondisi (belum diubah menjadi parameter animasi) di satu stasiun pada waktu `at`. */
91
+ conditionsAt(station: string, at: Date, options?: {
92
+ maxObservationAgeMs?: number;
93
+ includeProbable?: boolean;
94
+ }): SelectedConditions | null;
95
+ /** TAF yang berlaku di satu stasiun pada waktu `at` (manual didahulukan), atau null. */
96
+ forecastFor(station: string, at: Date): Taf | null;
97
+ /** Pengamatan terakhir (paling baru dulu) di satu stasiun sampai waktu `at`, paling banyak `count`. */
98
+ observationsUntil(station: string, at: Date, count: number): Metar[];
90
99
  /** Rentang waktu dan pembagian periode (BASE/BECMG/TEMPO/FM) dari TAF, untuk timeline. */
91
100
  timeline(): Timeline | null;
92
101
  /**
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeroswarm/weather",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Decode raw AFTN/METAR/SPECI/TAF messages and turn them into animation parameters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",