@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 +19 -0
- package/dist/animation.d.ts +16 -0
- package/dist/animation.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +363 -4
- package/dist/operations.d.ts +41 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/ops.d.ts +181 -0
- package/dist/ops.d.ts.map +1 -0
- package/dist/ops.test.d.ts +2 -0
- package/dist/ops.test.d.ts.map +1 -0
- package/dist/runway.d.ts +44 -0
- package/dist/runway.d.ts.map +1 -0
- package/dist/store.d.ts +11 -2
- package/dist/store.d.ts.map +1 -1
- package/package.json +1 -1
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
|
+
|
package/dist/animation.d.ts
CHANGED
|
@@ -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
|
package/dist/animation.d.ts.map
CHANGED
|
@@ -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,
|
|
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,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,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;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,kBAAkB,GAAG,IAAI,CA6ChF;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,GAAG,IAAI,CAKhF"}
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops.test.d.ts","sourceRoot":"","sources":["../src/ops.test.ts"],"names":[],"mappings":""}
|
package/dist/runway.d.ts
ADDED
|
@@ -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
|
/**
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|