metar-taf-parser 5.0.0 → 6.0.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 +27 -6
- package/metar-taf-parser.d.ts +39 -6
- package/metar-taf-parser.js +107 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ const datedMetar = parseMetar(rawMetarString, { issued });
|
|
|
45
45
|
|
|
46
46
|
#### `parseTAF`
|
|
47
47
|
|
|
48
|
-
👉 **Note:** One of the common use cases for TAF reports is to get relevant forecast data for a given
|
|
48
|
+
> 👉 **Note:** One of the common use cases for TAF reports is to get relevant forecast data for a given `Date`, or display the various forecast groups to the user. Check out [the `Forecast` abstraction](#higher-level-parsing-the-forecast-abstraction) below which may provide TAF data in a more normalized and easier to use format, depending on your use case.
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
51
|
import { parseTAF } from "metar-taf-parser";
|
|
@@ -61,11 +61,32 @@ const datedTAF = parseTAF(rawTAFString, { issued });
|
|
|
61
61
|
|
|
62
62
|
### Higher level parsing: The Forecast abstraction
|
|
63
63
|
|
|
64
|
-
TAF reports are a little funky... FM, BECMG, PROB, etc. You may find the `Forecast` abstraction more helpful.
|
|
64
|
+
TAF reports are a little funky... FM, BECMG, PROB, weird validity periods, etc. You may find the higher level `Forecast` abstraction more helpful.
|
|
65
|
+
|
|
66
|
+
⚠️ **Important:** The `Forecast` abstraction makes some assumptions in order to make it easier to consume the TAF. If you want different behavior, you may want to use the lower level `parseTAF` function directly (see above). Below are some of the assumptions the `Forecast` API makes:
|
|
67
|
+
|
|
68
|
+
1. The `validity` object found from `parseTAF`'s `trends[]` is too low level, so it is removed. Instead, you will find `start` and `end` on the base `Forecast` object. The end of a `FM` and `BECMG` group is derived from the start of the next `FM`/`BECMG` trend, or the end of the report validity if the last.
|
|
69
|
+
|
|
70
|
+
Additionally, there is a property, `by`, on `BECMG` trends for when conditions are expected to finish transitioning. You will need to type guard `type = BECMG` to access this property.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const firstForecast = report.forecast[1];
|
|
74
|
+
if (firstForecast.type === WeatherChangeType.BECMG) {
|
|
75
|
+
// Can now access `by`
|
|
76
|
+
console.log(firstForecast.by);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. `BECMG` trends are hydrated with the context of previous trends. For example, if:
|
|
81
|
+
|
|
82
|
+
TAF SBBR 221500Z 2218/2318 15008KT 9999 FEW045
|
|
83
|
+
BECMG 2308/2310 09002KT
|
|
84
|
+
|
|
85
|
+
Then the `BECMG` group will also have visibility and clouds from previously found conditions, with updated winds.
|
|
65
86
|
|
|
66
87
|
#### `parseTAFAsForecast`
|
|
67
88
|
|
|
68
|
-
Returns a more normalized TAF report
|
|
89
|
+
Returns a more normalized TAF report than `parseTAF`. Most notably: while the `parseTAF` function returns initial weather conditions on the base of the returned result (and further conditions on `trends[]`), the `parseTAFAsForecast` function returns the initial weather conditions as the first element of the `forecast[]` property (with `type = undefined`), followed by subsequent trends. (For more, please see the above about the forecast abstraction.) This makes it much easier to render a UI similar to the [aviationweather.gov](https://www.aviationweather.gov/taf/data?ids=SBPJ&format=decoded&metars=off&layout=on) TAF decoder.
|
|
69
90
|
|
|
70
91
|
```ts
|
|
71
92
|
import { parseTAFAsForecast } from "metar-taf-parser";
|
|
@@ -80,11 +101,11 @@ console.log(report.forecast);
|
|
|
80
101
|
|
|
81
102
|
> ⚠️ **Warning:** Experimental API
|
|
82
103
|
|
|
83
|
-
Provides all relevant weather conditions for a given timestamp. It returns a `ICompositeForecast` with a `base` and `additional` component. The `base` component is the base weather condition period (
|
|
104
|
+
Provides all relevant weather conditions for a given timestamp. It returns a `ICompositeForecast` with a `base` and `additional` component. The `base` component is the base weather condition period (type = `FM`, `BECMG`, or `undefined`) - and there will always be one.
|
|
84
105
|
|
|
85
|
-
The `additional` property is an array of weather condition periods valid for the given timestamp (any `
|
|
106
|
+
The `additional` property is an array of weather condition periods valid for the given timestamp (any `PROB` and/or `TEMPO`)
|
|
86
107
|
|
|
87
|
-
You will still need to write some logic to use this API to determine what data to use - for example, if `additional[0].visibility` exists, use it over `base.visibility`.
|
|
108
|
+
You will still need to write some logic to use this API to determine what data to use - for example, if `additional[0].visibility` exists, you may want to use it over `base.visibility`.
|
|
88
109
|
|
|
89
110
|
#### Example
|
|
90
111
|
|
package/metar-taf-parser.d.ts
CHANGED
|
@@ -1045,6 +1045,9 @@ interface IAirport {
|
|
|
1045
1045
|
interface IWind {
|
|
1046
1046
|
speed: number;
|
|
1047
1047
|
direction: string;
|
|
1048
|
+
/**
|
|
1049
|
+
* If undefined, direction is variable
|
|
1050
|
+
*/
|
|
1048
1051
|
degrees?: number;
|
|
1049
1052
|
gust?: number;
|
|
1050
1053
|
minVariation?: number;
|
|
@@ -1077,11 +1080,14 @@ interface IWeatherCondition {
|
|
|
1077
1080
|
phenomenons: Phenomenon[];
|
|
1078
1081
|
}
|
|
1079
1082
|
declare function isWeatherConditionValid(weather: IWeatherCondition): boolean;
|
|
1080
|
-
interface
|
|
1083
|
+
interface ITemperature {
|
|
1081
1084
|
temperature: number;
|
|
1082
1085
|
day: number;
|
|
1083
1086
|
hour: number;
|
|
1084
1087
|
}
|
|
1088
|
+
interface ITemperatureDated extends ITemperature {
|
|
1089
|
+
date: Date;
|
|
1090
|
+
}
|
|
1085
1091
|
interface IRunwayInfo {
|
|
1086
1092
|
name: string;
|
|
1087
1093
|
minRange: number;
|
|
@@ -1168,12 +1174,17 @@ interface IMetar extends IAbstractWeatherCode {
|
|
|
1168
1174
|
}
|
|
1169
1175
|
interface ITAF extends IAbstractWeatherCode {
|
|
1170
1176
|
validity: IValidity;
|
|
1171
|
-
maxTemperature?:
|
|
1172
|
-
minTemperature?:
|
|
1177
|
+
maxTemperature?: ITemperature;
|
|
1178
|
+
minTemperature?: ITemperature;
|
|
1173
1179
|
trends: TAFTrend[];
|
|
1180
|
+
/**
|
|
1181
|
+
* Just the first part of the TAF message without trends (FM, BECMG, etc)
|
|
1182
|
+
*/
|
|
1183
|
+
initialRaw: string;
|
|
1174
1184
|
}
|
|
1175
1185
|
interface IAbstractTrend extends IAbstractWeatherContainer {
|
|
1176
1186
|
type: WeatherChangeType;
|
|
1187
|
+
raw: string;
|
|
1177
1188
|
}
|
|
1178
1189
|
interface IMetarTrendTime extends ITime {
|
|
1179
1190
|
type: TimeIndicator;
|
|
@@ -1260,6 +1271,8 @@ interface ITAFDated extends ITAF {
|
|
|
1260
1271
|
start: Date;
|
|
1261
1272
|
end: Date;
|
|
1262
1273
|
};
|
|
1274
|
+
minTemperature?: ITemperatureDated;
|
|
1275
|
+
maxTemperature?: ITemperatureDated;
|
|
1263
1276
|
trends: TAFTrendDated[];
|
|
1264
1277
|
}
|
|
1265
1278
|
|
|
@@ -1292,14 +1305,34 @@ declare class UnexpectedParseError extends ParseError {
|
|
|
1292
1305
|
* The initial forecast, extracted from the first line of the TAF, does not have
|
|
1293
1306
|
* a trend type (FM, BECMG, etc)
|
|
1294
1307
|
*/
|
|
1295
|
-
declare type
|
|
1296
|
-
|
|
1308
|
+
declare type ForecastWithoutDates = Omit<TAFTrendDated, "type"> & Partial<Pick<TAFTrendDated, "type">>;
|
|
1309
|
+
declare type ForecastWithoutValidity = Omit<ForecastWithoutDates, "validity">;
|
|
1310
|
+
declare type Forecast = Omit<ForecastWithoutValidity, "type"> & {
|
|
1311
|
+
start: Date;
|
|
1312
|
+
end: Date;
|
|
1313
|
+
} & ({
|
|
1314
|
+
type: Exclude<WeatherChangeType, WeatherChangeType.BECMG> | undefined;
|
|
1315
|
+
} | {
|
|
1316
|
+
type: WeatherChangeType.BECMG;
|
|
1317
|
+
/**
|
|
1318
|
+
* BECMG has a special date, `by`, for when the transition will finish
|
|
1319
|
+
*
|
|
1320
|
+
* For example, a BECMG trend may `start` at 1:00PM and `end` at 5:00PM, but
|
|
1321
|
+
* `by` may be `3:00PM` to denote that conditions will transition from a period of
|
|
1322
|
+
* 1:00PM to 3:00PM
|
|
1323
|
+
*/
|
|
1324
|
+
by: Date;
|
|
1325
|
+
});
|
|
1326
|
+
interface IForecastContainer extends IFlags {
|
|
1297
1327
|
station: string;
|
|
1298
1328
|
issued: Date;
|
|
1299
1329
|
start: Date;
|
|
1300
1330
|
end: Date;
|
|
1301
1331
|
message: string;
|
|
1302
1332
|
forecast: Forecast[];
|
|
1333
|
+
amendment?: true;
|
|
1334
|
+
maxTemperature?: ITemperatureDated;
|
|
1335
|
+
minTemperature?: ITemperatureDated;
|
|
1303
1336
|
}
|
|
1304
1337
|
interface ICompositeForecast {
|
|
1305
1338
|
/**
|
|
@@ -1339,4 +1372,4 @@ declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptions): ITA
|
|
|
1339
1372
|
declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptionsDated): ITAFDated;
|
|
1340
1373
|
declare function parseTAFAsForecast(rawTAF: string, options: IMetarTAFParserOptionsDated): IForecastContainer;
|
|
1341
1374
|
|
|
1342
|
-
export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, Distance, DistanceUnit, Forecast, IAbstractTrend, IAbstractValidity, IAbstractWeatherCode, IAbstractWeatherCodeDated, IAbstractWeatherContainer, IAirport, IBaseRemark, IBaseTAFTrend, ICeilingHeightRemark, ICeilingSecondLocationRemark, ICloud, ICompositeForecast, ICountry, IEndValidity, IFMValidity, IFlags, IForecastContainer, IHourlyMaximumMinimumTemperatureRemark, IHourlyMaximumTemperatureRemark, IHourlyMinimumTemperatureRemark, IHourlyPrecipitationAmountRemark, IHourlyPressureRemark, IHourlyTemperatureDewPointRemark, IIceAccretionRemark, IMetar, IMetarTAFParserOptions, IMetarTAFParserOptionsDated, IMetarTrend, IMetarTrendTime, IObscurationRemark, IPrecipitationAmount24HourRemark, IPrecipitationAmount36HourRemark, IPrecipitationBegEndRemark, IPrevailingVisibilityRemark, IRunwayInfo, ISeaLevelPressureRemark, ISecondLocationVisibilityRemark, ISectorVisibilityRemark, ISmallHailSizeRemark, ISnowIncreaseRemark, ISnowPelletsRemark, ISunshineDurationRemark, ISurfaceVisibilityRemark, ITAF, ITAFDated, ITemperatureDated, IThunderStormLocationMovingRemark, IThunderStormLocationRemark, ITime, ITornadicActivityBegEndRemark, ITornadicActivityBegRemark, ITornadicActivityEndRemark, ITowerVisibilityRemark, IUnknownRemark, IValidity, IValidityDated, IVariableSkyHeightRemark, IVariableSkyRemark, IVirgaDirectionRemark, IWaterEquivalentSnowRemark, IWeatherCondition, IWind, IWindPeakCommandRemark, IWindShear, IWindShiftFropaRemark, Intensity, InvalidWeatherStatementError, Locale, ParseError, Phenomenon, Remark, RemarkType, RunwayInfoTrend, RunwayInfoUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
|
|
1375
|
+
export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, Distance, DistanceUnit, Forecast, IAbstractTrend, IAbstractValidity, IAbstractWeatherCode, IAbstractWeatherCodeDated, IAbstractWeatherContainer, IAirport, IBaseRemark, IBaseTAFTrend, ICeilingHeightRemark, ICeilingSecondLocationRemark, ICloud, ICompositeForecast, ICountry, IEndValidity, IFMValidity, IFlags, IForecastContainer, IHourlyMaximumMinimumTemperatureRemark, IHourlyMaximumTemperatureRemark, IHourlyMinimumTemperatureRemark, IHourlyPrecipitationAmountRemark, IHourlyPressureRemark, IHourlyTemperatureDewPointRemark, IIceAccretionRemark, IMetar, IMetarTAFParserOptions, IMetarTAFParserOptionsDated, IMetarTrend, IMetarTrendTime, IObscurationRemark, IPrecipitationAmount24HourRemark, IPrecipitationAmount36HourRemark, IPrecipitationBegEndRemark, IPrevailingVisibilityRemark, IRunwayInfo, ISeaLevelPressureRemark, ISecondLocationVisibilityRemark, ISectorVisibilityRemark, ISmallHailSizeRemark, ISnowIncreaseRemark, ISnowPelletsRemark, ISunshineDurationRemark, ISurfaceVisibilityRemark, ITAF, ITAFDated, ITemperature, ITemperatureDated, IThunderStormLocationMovingRemark, IThunderStormLocationRemark, ITime, ITornadicActivityBegEndRemark, ITornadicActivityBegRemark, ITornadicActivityEndRemark, ITowerVisibilityRemark, IUnknownRemark, IValidity, IValidityDated, IVariableSkyHeightRemark, IVariableSkyRemark, IVirgaDirectionRemark, IWaterEquivalentSnowRemark, IWeatherCondition, IWind, IWindPeakCommandRemark, IWindShear, IWindShiftFropaRemark, Intensity, InvalidWeatherStatementError, Locale, ParseError, Phenomenon, Remark, RemarkType, RunwayInfoTrend, RunwayInfoUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
|
package/metar-taf-parser.js
CHANGED
|
@@ -2304,6 +2304,7 @@ class MetarParser extends AbstractParser {
|
|
|
2304
2304
|
clouds: [],
|
|
2305
2305
|
times: [],
|
|
2306
2306
|
remarks: [],
|
|
2307
|
+
raw: input,
|
|
2307
2308
|
};
|
|
2308
2309
|
index = this.parseTrend(index, trend, metarTab);
|
|
2309
2310
|
metar.trends.push(trend);
|
|
@@ -2369,6 +2370,7 @@ class TAFParser extends AbstractParser {
|
|
|
2369
2370
|
remarks: [],
|
|
2370
2371
|
clouds: [],
|
|
2371
2372
|
weatherConditions: [],
|
|
2373
|
+
initialRaw: lines[0].join(" "),
|
|
2372
2374
|
};
|
|
2373
2375
|
for (let i = index + 1; i < lines[0].length; i++) {
|
|
2374
2376
|
const token = lines[0][i];
|
|
@@ -2435,6 +2437,7 @@ class TAFParser extends AbstractParser {
|
|
|
2435
2437
|
...this.makeEmptyTAFTrend(),
|
|
2436
2438
|
type: WeatherChangeType.FM,
|
|
2437
2439
|
validity: parseFromValidity(lineTokens[0]),
|
|
2440
|
+
raw: lineTokens.join(" "),
|
|
2438
2441
|
};
|
|
2439
2442
|
}
|
|
2440
2443
|
else if (lineTokens[0].startsWith(this.PROB)) {
|
|
@@ -2445,12 +2448,14 @@ class TAFParser extends AbstractParser {
|
|
|
2445
2448
|
...this.makeEmptyTAFTrend(),
|
|
2446
2449
|
type: WeatherChangeType.PROB,
|
|
2447
2450
|
validity,
|
|
2451
|
+
raw: lineTokens.join(" "),
|
|
2448
2452
|
};
|
|
2449
2453
|
if (lineTokens.length > 1 && lineTokens[1] === this.TEMPO) {
|
|
2450
2454
|
trend = {
|
|
2451
2455
|
...this.makeEmptyTAFTrend(),
|
|
2452
2456
|
type: WeatherChangeType[lineTokens[1]],
|
|
2453
2457
|
validity,
|
|
2458
|
+
raw: lineTokens.join(" "),
|
|
2454
2459
|
};
|
|
2455
2460
|
index = 2;
|
|
2456
2461
|
}
|
|
@@ -2464,6 +2469,7 @@ class TAFParser extends AbstractParser {
|
|
|
2464
2469
|
...this.makeEmptyTAFTrend(),
|
|
2465
2470
|
type: WeatherChangeType[lineTokens[0]],
|
|
2466
2471
|
validity,
|
|
2472
|
+
raw: lineTokens.join(" "),
|
|
2467
2473
|
};
|
|
2468
2474
|
}
|
|
2469
2475
|
this.parseTrend(index, lineTokens, trend);
|
|
@@ -2609,6 +2615,18 @@ function tafDatesHydrator(report, date) {
|
|
|
2609
2615
|
start: getReportDate(issued, report.validity.startDay, report.validity.startHour),
|
|
2610
2616
|
end: getReportDate(issued, report.validity.endDay, report.validity.endHour),
|
|
2611
2617
|
},
|
|
2618
|
+
minTemperature: report.minTemperature
|
|
2619
|
+
? {
|
|
2620
|
+
...report.minTemperature,
|
|
2621
|
+
date: getReportDate(issued, report.minTemperature.day, report.minTemperature.hour),
|
|
2622
|
+
}
|
|
2623
|
+
: undefined,
|
|
2624
|
+
maxTemperature: report.maxTemperature
|
|
2625
|
+
? {
|
|
2626
|
+
...report.maxTemperature,
|
|
2627
|
+
date: getReportDate(issued, report.maxTemperature.day, report.maxTemperature.hour),
|
|
2628
|
+
}
|
|
2629
|
+
: undefined,
|
|
2612
2630
|
trends: report.trends.map((trend) => ({
|
|
2613
2631
|
...trend,
|
|
2614
2632
|
validity: (() => {
|
|
@@ -2632,12 +2650,10 @@ function tafDatesHydrator(report, date) {
|
|
|
2632
2650
|
|
|
2633
2651
|
function getForecastFromTAF(taf) {
|
|
2634
2652
|
return {
|
|
2635
|
-
|
|
2636
|
-
station: taf.station,
|
|
2637
|
-
message: taf.message,
|
|
2653
|
+
...taf,
|
|
2638
2654
|
start: getReportDate(taf.issued, taf.validity.startDay, taf.validity.startHour),
|
|
2639
2655
|
end: getReportDate(taf.issued, taf.validity.endDay, taf.validity.endHour),
|
|
2640
|
-
forecast: [makeInitialForecast(taf), ...taf.trends],
|
|
2656
|
+
forecast: hydrateEndDates([makeInitialForecast(taf), ...taf.trends], taf.validity),
|
|
2641
2657
|
};
|
|
2642
2658
|
}
|
|
2643
2659
|
/**
|
|
@@ -2654,6 +2670,7 @@ function makeInitialForecast(taf) {
|
|
|
2654
2670
|
remarks: taf.remarks,
|
|
2655
2671
|
clouds: taf.clouds,
|
|
2656
2672
|
weatherConditions: taf.weatherConditions,
|
|
2673
|
+
raw: taf.initialRaw,
|
|
2657
2674
|
validity: {
|
|
2658
2675
|
// End day/hour are for end of the entire TAF
|
|
2659
2676
|
startDay: taf.validity.startDay,
|
|
@@ -2663,6 +2680,80 @@ function makeInitialForecast(taf) {
|
|
|
2663
2680
|
},
|
|
2664
2681
|
};
|
|
2665
2682
|
}
|
|
2683
|
+
function hasImplicitEnd({ type }) {
|
|
2684
|
+
return (type === WeatherChangeType.FM ||
|
|
2685
|
+
// BECMG are special - the "end" date in the validity isn't actually
|
|
2686
|
+
// the end date, it's when the change that's "becoming" is expected to
|
|
2687
|
+
// finish transition. The actual "end" date of the BECMG is determined by
|
|
2688
|
+
// the next FM/BECMG/end of the report validity, just like a FM
|
|
2689
|
+
type === WeatherChangeType.BECMG ||
|
|
2690
|
+
// Special case for beginning of report conditions
|
|
2691
|
+
type === undefined);
|
|
2692
|
+
}
|
|
2693
|
+
function hydrateEndDates(trends, reportValidity) {
|
|
2694
|
+
function findNext(index) {
|
|
2695
|
+
for (let i = index; i < trends.length; i++) {
|
|
2696
|
+
if (hasImplicitEnd(trends[i]))
|
|
2697
|
+
return trends[i];
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
const forecasts = [];
|
|
2701
|
+
let previouslyHydratedTrend;
|
|
2702
|
+
for (let i = 0; i < trends.length; i++) {
|
|
2703
|
+
const currentTrend = trends[i];
|
|
2704
|
+
const nextTrend = findNext(i + 1);
|
|
2705
|
+
if (!hasImplicitEnd(currentTrend)) {
|
|
2706
|
+
forecasts.push({
|
|
2707
|
+
...currentTrend,
|
|
2708
|
+
start: currentTrend.validity.start,
|
|
2709
|
+
// Has a type and not a FM/BECMG/undefined, so always has an end
|
|
2710
|
+
end: currentTrend.validity.end,
|
|
2711
|
+
});
|
|
2712
|
+
continue;
|
|
2713
|
+
}
|
|
2714
|
+
let forecast;
|
|
2715
|
+
if (nextTrend === undefined) {
|
|
2716
|
+
forecast = hydrateWithPreviousContextIfNeeded({
|
|
2717
|
+
...currentTrend,
|
|
2718
|
+
start: currentTrend.validity.start,
|
|
2719
|
+
end: reportValidity.end,
|
|
2720
|
+
...byIfNeeded(currentTrend),
|
|
2721
|
+
}, previouslyHydratedTrend);
|
|
2722
|
+
}
|
|
2723
|
+
else {
|
|
2724
|
+
forecast = hydrateWithPreviousContextIfNeeded({
|
|
2725
|
+
...currentTrend,
|
|
2726
|
+
start: currentTrend.validity.start,
|
|
2727
|
+
end: new Date(nextTrend.validity.start),
|
|
2728
|
+
...byIfNeeded(currentTrend),
|
|
2729
|
+
}, previouslyHydratedTrend);
|
|
2730
|
+
}
|
|
2731
|
+
forecasts.push(forecast);
|
|
2732
|
+
previouslyHydratedTrend = forecast;
|
|
2733
|
+
}
|
|
2734
|
+
return forecasts;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* BECMG doesn't always have all the context for the period, so
|
|
2738
|
+
* it needs to be populated
|
|
2739
|
+
*/
|
|
2740
|
+
function hydrateWithPreviousContextIfNeeded(forecast, context) {
|
|
2741
|
+
if (forecast.type !== WeatherChangeType.BECMG || !context)
|
|
2742
|
+
return forecast;
|
|
2743
|
+
// Remarks should not be carried over
|
|
2744
|
+
context = { ...context };
|
|
2745
|
+
delete context.remark;
|
|
2746
|
+
context.remarks = [];
|
|
2747
|
+
forecast = {
|
|
2748
|
+
...context,
|
|
2749
|
+
...forecast,
|
|
2750
|
+
};
|
|
2751
|
+
if (!forecast.clouds.length)
|
|
2752
|
+
forecast.clouds = context.clouds;
|
|
2753
|
+
if (!forecast.weatherConditions.length)
|
|
2754
|
+
forecast.weatherConditions = context.weatherConditions;
|
|
2755
|
+
return forecast;
|
|
2756
|
+
}
|
|
2666
2757
|
class TimestampOutOfBoundsError extends ParseError {
|
|
2667
2758
|
constructor(message) {
|
|
2668
2759
|
super(message);
|
|
@@ -2678,15 +2769,16 @@ function getCompositeForecastForDate(date, forecastContainer) {
|
|
|
2678
2769
|
let base;
|
|
2679
2770
|
let additional = [];
|
|
2680
2771
|
for (const forecast of forecastContainer.forecast) {
|
|
2681
|
-
if (
|
|
2682
|
-
forecast.
|
|
2772
|
+
if (hasImplicitEnd(forecast) &&
|
|
2773
|
+
forecast.start.getTime() <= date.getTime()) {
|
|
2683
2774
|
// Is FM or initial forecast
|
|
2684
2775
|
base = forecast;
|
|
2685
2776
|
}
|
|
2686
|
-
if (forecast
|
|
2687
|
-
forecast.
|
|
2688
|
-
forecast.
|
|
2689
|
-
|
|
2777
|
+
if (!hasImplicitEnd(forecast) &&
|
|
2778
|
+
forecast.end &&
|
|
2779
|
+
forecast.end.getTime() - date.getTime() > 0 &&
|
|
2780
|
+
forecast.start.getTime() - date.getTime() <= 0) {
|
|
2781
|
+
// Is TEMPO, BECMG etc
|
|
2690
2782
|
additional.push(forecast);
|
|
2691
2783
|
}
|
|
2692
2784
|
}
|
|
@@ -2694,6 +2786,11 @@ function getCompositeForecastForDate(date, forecastContainer) {
|
|
|
2694
2786
|
throw new UnexpectedParseError("Unable to find trend for date");
|
|
2695
2787
|
return { base, additional };
|
|
2696
2788
|
}
|
|
2789
|
+
function byIfNeeded(forecast) {
|
|
2790
|
+
if (forecast.type !== WeatherChangeType.BECMG)
|
|
2791
|
+
return {};
|
|
2792
|
+
return { by: forecast.validity.end };
|
|
2793
|
+
}
|
|
2697
2794
|
|
|
2698
2795
|
function parseMetar(rawMetar, options) {
|
|
2699
2796
|
return parse(rawMetar, options, MetarParser, metarDatesHydrator);
|