metar-taf-parser 7.0.0 → 7.1.1

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
@@ -175,6 +175,10 @@ function findSeaLevelPressure(remarks: Remark[]): number | undefined {
175
175
  }
176
176
  ```
177
177
 
178
+ ## Determining flight category, ceiling, etc
179
+
180
+ Because certain abstractions such as flight category and flight ceiling can vary by country, this logic is left up to you to implement. However, if you're looking for somewhere to start, check out the example site (based on United States flight rules) in [example/src/helpers/metarTaf.ts](https://github.com/aeharding/metar-taf-parser/blob/main/example/src/helpers/metarTaf.ts). Feel free to copy - it's MIT licensed.
181
+
178
182
  ## Development
179
183
 
180
184
  ### Example site
@@ -275,6 +275,57 @@ declare enum RunwayInfoUnit {
275
275
  Feet = "FT",
276
276
  Meters = "m"
277
277
  }
278
+ declare enum IcingIntensity {
279
+ /**
280
+ * Trace Icing or None.
281
+ *
282
+ * Air Force code 0 means a trace of icing.
283
+ * World Meteorological Organization code 0 means no icing
284
+ */
285
+ None = "0",
286
+ /** Light Mixed Icing. */
287
+ Light = "1",
288
+ /** Light Rime Icing In Cloud. */
289
+ LightRimeIcingCloud = "2",
290
+ /** Light Clear Icing In Precipitation. */
291
+ LightClearIcingPrecipitation = "3",
292
+ /** Moderate Mixed Icing. */
293
+ ModerateMixedIcing = "4",
294
+ /** Moderate Rime Icing In Cloud. */
295
+ ModerateRimeIcingCloud = "5",
296
+ /** Moderate Clear Icing In Precipitation. */
297
+ ModerateClearIcingPrecipitation = "6",
298
+ /** Severe Mixed Icing. */
299
+ SevereMixedIcing = "7",
300
+ /** Severe Rime Icing In Cloud. */
301
+ SevereRimeIcingCloud = "8",
302
+ /** Severe Clear Icing In Precipitation. */
303
+ SevereClearIcingPrecipitation = "9"
304
+ }
305
+ declare enum TurbulenceIntensity {
306
+ /** None. */
307
+ None = "0",
308
+ /** Light turbulence. */
309
+ Light = "1",
310
+ /** Moderate turbulence in clear air, occasional. */
311
+ ModerateClearAirOccasional = "2",
312
+ /** Moderate turbulence in clear air, frequent. */
313
+ ModerateClearAirFrequent = "3",
314
+ /** Moderate turbulence in cloud, occasional. */
315
+ ModerateCloudOccasional = "4",
316
+ /** Moderate turbulence in cloud, frequent. */
317
+ ModerateCloudFrequent = "5",
318
+ /** Severe turbulence in clear air, occasional. */
319
+ SevereClearAirOccasional = "6",
320
+ /** Severe turbulence in clear air, frequent. */
321
+ SevereClearAirFrequent = "7",
322
+ /** Severe turbulence in cloud, occasional. */
323
+ SevereCloudOccasional = "8",
324
+ /** Severe turbulence in cloud, frequent. */
325
+ SevereCloudFrequent = "9",
326
+ /** Extreme turbulence */
327
+ Extreme = "X"
328
+ }
278
329
 
279
330
  declare const _default: {
280
331
  CloudQuantity: {
@@ -1042,22 +1093,6 @@ declare enum RemarkType {
1042
1093
  }
1043
1094
  declare type Remark = IUnknownRemark | IDefaultCommandRemark | ICeilingHeightRemark | ICeilingSecondLocationRemark | IHailSizeRemark | IHourlyMaximumMinimumTemperatureRemark | IHourlyMaximumTemperatureRemark | IHourlyMinimumTemperatureRemark | IHourlyPrecipitationAmountRemark | IHourlyPressureRemark | IHourlyTemperatureDewPointRemark | IIceAccretionRemark | IObscurationRemark | IPrecipitationAmount24HourRemark | IPrecipitationAmount36HourRemark | IPrecipitationAmount36HourRemark | IPrecipitationBegRemark | IPrecipitationBegEndRemark | IPrecipitationEndRemark | IPrevailingVisibilityRemark | ISeaLevelPressureRemark | ISecondLocationVisibilityRemark | ISectorVisibilityRemark | ISmallHailSizeRemark | ISnowDepthRemark | ISnowIncreaseRemark | ISnowPelletsRemark | ISunshineDurationRemark | ISurfaceVisibilityRemark | IThunderStormLocationRemark | IThunderStormLocationMovingRemark | ITornadicActivityBegRemark | ITornadicActivityBegEndRemark | ITornadicActivityEndRemark | ITowerVisibilityRemark | IVariableSkyRemark | IVariableSkyHeightRemark | IVirgaDirectionRemark | IWaterEquivalentSnowRemark | IWindPeakCommandRemark | IWindShiftRemark | IWindShiftFropaRemark;
1044
1095
 
1045
- interface ICountry {
1046
- name: string;
1047
- }
1048
- interface IAirport {
1049
- name: string;
1050
- city: string;
1051
- country: string;
1052
- iata: string;
1053
- icao: string;
1054
- latitude: string;
1055
- longitude: string;
1056
- altitude: string;
1057
- timezone: string;
1058
- dst: boolean;
1059
- tzDatabase: unknown;
1060
- }
1061
1096
  interface IWind {
1062
1097
  speed: number;
1063
1098
  direction: string;
@@ -1122,6 +1157,12 @@ interface ICloud {
1122
1157
  height?: number;
1123
1158
  quantity: CloudQuantity;
1124
1159
  type?: CloudType;
1160
+ /**
1161
+ * Very uncommon. For example "FEW025TCU/CB" seen at airport VOTR.
1162
+ *
1163
+ * This property can be ignored in almost all cases.
1164
+ */
1165
+ secondaryType?: CloudType;
1125
1166
  }
1126
1167
  interface IFlags {
1127
1168
  /**
@@ -1169,11 +1210,14 @@ interface ITime {
1169
1210
  }
1170
1211
  interface IAbstractWeatherCode extends IAbstractWeatherContainer, ITime {
1171
1212
  day?: number;
1172
- airport?: IAirport;
1173
1213
  message: string;
1174
1214
  station: string;
1175
1215
  trends: IAbstractTrend[];
1176
1216
  }
1217
+ interface ITafGroups {
1218
+ turbulence?: ITurbulence[];
1219
+ icing?: IIcing[];
1220
+ }
1177
1221
  interface IAbstractWeatherCodeDated extends IAbstractWeatherCode {
1178
1222
  issued: Date;
1179
1223
  }
@@ -1188,7 +1232,7 @@ interface IMetar extends IAbstractWeatherCode {
1188
1232
  */
1189
1233
  trends: IMetarTrend[];
1190
1234
  }
1191
- interface ITAF extends IAbstractWeatherCode {
1235
+ interface ITAF extends IAbstractWeatherCode, ITafGroups {
1192
1236
  validity: IValidity;
1193
1237
  maxTemperature?: ITemperature;
1194
1238
  minTemperature?: ITemperature;
@@ -1208,7 +1252,7 @@ interface IMetarTrendTime extends ITime {
1208
1252
  interface IMetarTrend extends IAbstractTrend {
1209
1253
  times: IMetarTrendTime[];
1210
1254
  }
1211
- interface IBaseTAFTrend extends IAbstractTrend {
1255
+ interface IBaseTAFTrend extends IAbstractTrend, ITafGroups {
1212
1256
  /**
1213
1257
  * Will not be found on FM trends. May exist on others.
1214
1258
  *
@@ -1260,6 +1304,36 @@ interface IValidityDated extends IAbstractValidity, IEndValidity {
1260
1304
  interface IFMValidity extends IAbstractValidity {
1261
1305
  startMinutes: number;
1262
1306
  }
1307
+ /**
1308
+ * Represents icing in a TAF.
1309
+ *
1310
+ * http://prnfc.org/wp-content/uploads/2016/12/AF-METAR-TAF-Codes.pdf#page=28
1311
+ *
1312
+ * Top of icing = `baseHeight` + `depth`
1313
+ */
1314
+ interface IIcing {
1315
+ /** The intensity of the icing. */
1316
+ intensity: IcingIntensity;
1317
+ /** The base of the icing layer in feet. */
1318
+ baseHeight: number;
1319
+ /** The icing layer depth in feet. */
1320
+ depth: number;
1321
+ }
1322
+ /**
1323
+ * Represents turbulence in a TAF.
1324
+ *
1325
+ * http://prnfc.org/wp-content/uploads/2016/12/AF-METAR-TAF-Codes.pdf#page=29
1326
+ *
1327
+ * Top of icing = `baseHeight` + `depth`
1328
+ */
1329
+ interface ITurbulence {
1330
+ /** The intensity of the turbulence. */
1331
+ intensity: TurbulenceIntensity;
1332
+ /** The base limit of the turbulence layer in feet. */
1333
+ baseHeight: number;
1334
+ /** The turbulence layer depth in feet. */
1335
+ depth: number;
1336
+ }
1263
1337
 
1264
1338
  interface IMetarDated extends IMetar {
1265
1339
  issued: Date;
@@ -1388,4 +1462,4 @@ declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptions): ITA
1388
1462
  declare function parseTAF(rawTAF: string, options?: IMetarTAFParserOptionsDated): ITAFDated;
1389
1463
  declare function parseTAFAsForecast(rawTAF: string, options: IMetarTAFParserOptionsDated): IForecastContainer;
1390
1464
 
1391
- 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, SpeedUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
1465
+ export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, Distance, DistanceUnit, Forecast, IAbstractTrend, IAbstractValidity, IAbstractWeatherCode, IAbstractWeatherCodeDated, IAbstractWeatherContainer, IBaseRemark, IBaseTAFTrend, ICeilingHeightRemark, ICeilingSecondLocationRemark, ICloud, ICompositeForecast, IEndValidity, IFMValidity, IFlags, IForecastContainer, IHourlyMaximumMinimumTemperatureRemark, IHourlyMaximumTemperatureRemark, IHourlyMinimumTemperatureRemark, IHourlyPrecipitationAmountRemark, IHourlyPressureRemark, IHourlyTemperatureDewPointRemark, IIceAccretionRemark, IIcing, IMetar, IMetarTAFParserOptions, IMetarTAFParserOptionsDated, IMetarTrend, IMetarTrendTime, IObscurationRemark, IPrecipitationAmount24HourRemark, IPrecipitationAmount36HourRemark, IPrecipitationBegEndRemark, IPrevailingVisibilityRemark, IRunwayInfo, ISeaLevelPressureRemark, ISecondLocationVisibilityRemark, ISectorVisibilityRemark, ISmallHailSizeRemark, ISnowIncreaseRemark, ISnowPelletsRemark, ISunshineDurationRemark, ISurfaceVisibilityRemark, ITAF, ITAFDated, ITafGroups, ITemperature, ITemperatureDated, IThunderStormLocationMovingRemark, IThunderStormLocationRemark, ITime, ITornadicActivityBegEndRemark, ITornadicActivityBegRemark, ITornadicActivityEndRemark, ITowerVisibilityRemark, ITurbulence, IUnknownRemark, IValidity, IValidityDated, IVariableSkyHeightRemark, IVariableSkyRemark, IVirgaDirectionRemark, IWaterEquivalentSnowRemark, IWeatherCondition, IWind, IWindPeakCommandRemark, IWindShear, IWindShiftFropaRemark, IcingIntensity, Intensity, InvalidWeatherStatementError, Locale, ParseError, Phenomenon, Remark, RemarkType, RunwayInfoTrend, RunwayInfoUnit, SpeedUnit, TAFTrend, TAFTrendDated, TimeIndicator, TimestampOutOfBoundsError, TurbulenceIntensity, UnexpectedParseError, ValueIndicator, Visibility, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
@@ -473,6 +473,59 @@ var RunwayInfoUnit;
473
473
  RunwayInfoUnit["Feet"] = "FT";
474
474
  RunwayInfoUnit["Meters"] = "m";
475
475
  })(RunwayInfoUnit || (RunwayInfoUnit = {}));
476
+ var IcingIntensity;
477
+ (function (IcingIntensity) {
478
+ /**
479
+ * Trace Icing or None.
480
+ *
481
+ * Air Force code 0 means a trace of icing.
482
+ * World Meteorological Organization code 0 means no icing
483
+ */
484
+ IcingIntensity["None"] = "0";
485
+ /** Light Mixed Icing. */
486
+ IcingIntensity["Light"] = "1";
487
+ /** Light Rime Icing In Cloud. */
488
+ IcingIntensity["LightRimeIcingCloud"] = "2";
489
+ /** Light Clear Icing In Precipitation. */
490
+ IcingIntensity["LightClearIcingPrecipitation"] = "3";
491
+ /** Moderate Mixed Icing. */
492
+ IcingIntensity["ModerateMixedIcing"] = "4";
493
+ /** Moderate Rime Icing In Cloud. */
494
+ IcingIntensity["ModerateRimeIcingCloud"] = "5";
495
+ /** Moderate Clear Icing In Precipitation. */
496
+ IcingIntensity["ModerateClearIcingPrecipitation"] = "6";
497
+ /** Severe Mixed Icing. */
498
+ IcingIntensity["SevereMixedIcing"] = "7";
499
+ /** Severe Rime Icing In Cloud. */
500
+ IcingIntensity["SevereRimeIcingCloud"] = "8";
501
+ /** Severe Clear Icing In Precipitation. */
502
+ IcingIntensity["SevereClearIcingPrecipitation"] = "9";
503
+ })(IcingIntensity || (IcingIntensity = {}));
504
+ var TurbulenceIntensity;
505
+ (function (TurbulenceIntensity) {
506
+ /** None. */
507
+ TurbulenceIntensity["None"] = "0";
508
+ /** Light turbulence. */
509
+ TurbulenceIntensity["Light"] = "1";
510
+ /** Moderate turbulence in clear air, occasional. */
511
+ TurbulenceIntensity["ModerateClearAirOccasional"] = "2";
512
+ /** Moderate turbulence in clear air, frequent. */
513
+ TurbulenceIntensity["ModerateClearAirFrequent"] = "3";
514
+ /** Moderate turbulence in cloud, occasional. */
515
+ TurbulenceIntensity["ModerateCloudOccasional"] = "4";
516
+ /** Moderate turbulence in cloud, frequent. */
517
+ TurbulenceIntensity["ModerateCloudFrequent"] = "5";
518
+ /** Severe turbulence in clear air, occasional. */
519
+ TurbulenceIntensity["SevereClearAirOccasional"] = "6";
520
+ /** Severe turbulence in clear air, frequent. */
521
+ TurbulenceIntensity["SevereClearAirFrequent"] = "7";
522
+ /** Severe turbulence in cloud, occasional. */
523
+ TurbulenceIntensity["SevereCloudOccasional"] = "8";
524
+ /** Severe turbulence in cloud, frequent. */
525
+ TurbulenceIntensity["SevereCloudFrequent"] = "9";
526
+ /** Extreme turbulence */
527
+ TurbulenceIntensity["Extreme"] = "X";
528
+ })(TurbulenceIntensity || (TurbulenceIntensity = {}));
476
529
 
477
530
  function degreesToCardinal(input) {
478
531
  const degrees = +input;
@@ -1741,7 +1794,7 @@ function isWeatherConditionValid(weather) {
1741
1794
  weather.descriptive == Descriptive.SHOWERS));
1742
1795
  }
1743
1796
 
1744
- var _CloudCommand_cloudRegex, _MainVisibilityCommand_regex, _WindCommand_regex, _WindVariationCommand_regex, _WindShearCommand_regex, _VerticalVisibilityCommand_regex, _MinimalVisibilityCommand_regex, _MainVisibilityNauticalMilesCommand_regex, _CommandSupplier_commands$1;
1797
+ var _CloudCommand_cloudRegex, _MainVisibilityCommand_regex, _WindCommand_regex, _WindVariationCommand_regex, _WindShearCommand_regex, _VerticalVisibilityCommand_regex, _MinimalVisibilityCommand_regex, _MainVisibilityNauticalMilesCommand_regex, _CommandSupplier_commands$2;
1745
1798
  /**
1746
1799
  * This function creates a wind element.
1747
1800
  * @param wind The wind object
@@ -1761,18 +1814,17 @@ function makeWind(direction, speed, gust, unit) {
1761
1814
  }
1762
1815
  class CloudCommand {
1763
1816
  constructor() {
1764
- _CloudCommand_cloudRegex.set(this, /^([A-Z]{3})(\d{3})?([A-Z]{2,3})?$/);
1817
+ _CloudCommand_cloudRegex.set(this, /^([A-Z]{3})(?:\/{3}|(\d{3}))?(?:\/{3}|(?:([A-Z]{2,3})(?:\/([A-Z]{2,3}))?))?$/);
1765
1818
  }
1766
1819
  parse(cloudString) {
1767
1820
  const m = cloudString.match(__classPrivateFieldGet(this, _CloudCommand_cloudRegex, "f"));
1768
1821
  if (!m)
1769
1822
  return;
1770
- const quantity = CloudQuantity[m[1]];
1823
+ const quantity = as(m[1], CloudQuantity);
1771
1824
  const height = 100 * +m[2] || undefined;
1772
- const type = CloudType[m[3]];
1773
- if (!quantity)
1774
- return;
1775
- return { quantity, height, type };
1825
+ const type = m[3] ? as(m[3], CloudType) : undefined;
1826
+ const secondaryType = m[4] ? as(m[4], CloudType) : undefined;
1827
+ return { quantity, height, type, secondaryType };
1776
1828
  }
1777
1829
  execute(container, cloudString) {
1778
1830
  const cloud = this.parse(cloudString);
@@ -1811,7 +1863,7 @@ class MainVisibilityCommand {
1811
1863
  _MainVisibilityCommand_regex = new WeakMap();
1812
1864
  class WindCommand {
1813
1865
  constructor() {
1814
- _WindCommand_regex.set(this, /^(VRB|\d{3})(\d{2})G?(\d{2})?(KT|MPS|KM\/H)?/);
1866
+ _WindCommand_regex.set(this, /^(VRB|[0-3]\d{2})(\d{2})G?(\d{2})?(KT|MPS|KM\/H)?/);
1815
1867
  }
1816
1868
  canParse(windString) {
1817
1869
  return __classPrivateFieldGet(this, _WindCommand_regex, "f").test(windString);
@@ -1924,9 +1976,9 @@ class MainVisibilityNauticalMilesCommand {
1924
1976
  }
1925
1977
  }
1926
1978
  _MainVisibilityNauticalMilesCommand_regex = new WeakMap();
1927
- class CommandSupplier$1 {
1979
+ class CommandSupplier$2 {
1928
1980
  constructor() {
1929
- _CommandSupplier_commands$1.set(this, [
1981
+ _CommandSupplier_commands$2.set(this, [
1930
1982
  new WindShearCommand(),
1931
1983
  new WindCommand(),
1932
1984
  new WindVariationCommand(),
@@ -1938,13 +1990,13 @@ class CommandSupplier$1 {
1938
1990
  ]);
1939
1991
  }
1940
1992
  get(input) {
1941
- for (const command of __classPrivateFieldGet(this, _CommandSupplier_commands$1, "f")) {
1993
+ for (const command of __classPrivateFieldGet(this, _CommandSupplier_commands$2, "f")) {
1942
1994
  if (command.canParse(input))
1943
1995
  return command;
1944
1996
  }
1945
1997
  }
1946
1998
  }
1947
- _CommandSupplier_commands$1 = new WeakMap();
1999
+ _CommandSupplier_commands$2 = new WeakMap();
1948
2000
 
1949
2001
  var _AltimeterCommand_regex;
1950
2002
  class AltimeterCommand {
@@ -2048,16 +2100,76 @@ class TemperatureCommand {
2048
2100
  }
2049
2101
  _TemperatureCommand_regex = new WeakMap();
2050
2102
 
2051
- var _CommandSupplier_commands;
2052
- class CommandSupplier {
2103
+ var _CommandSupplier_commands$1;
2104
+ class CommandSupplier$1 {
2053
2105
  constructor() {
2054
- _CommandSupplier_commands.set(this, [
2106
+ _CommandSupplier_commands$1.set(this, [
2055
2107
  new RunwayCommand(),
2056
2108
  new TemperatureCommand(),
2057
2109
  new AltimeterCommand(),
2058
2110
  new AltimeterMercuryCommand(),
2059
2111
  ]);
2060
2112
  }
2113
+ get(input) {
2114
+ for (const command of __classPrivateFieldGet(this, _CommandSupplier_commands$1, "f")) {
2115
+ if (command.canParse(input))
2116
+ return command;
2117
+ }
2118
+ }
2119
+ }
2120
+ _CommandSupplier_commands$1 = new WeakMap();
2121
+
2122
+ var _IcingCommand_regex;
2123
+ class IcingCommand {
2124
+ constructor() {
2125
+ _IcingCommand_regex.set(this, /^6(\d)(\d{3})(\d)$/);
2126
+ }
2127
+ canParse(input) {
2128
+ return __classPrivateFieldGet(this, _IcingCommand_regex, "f").test(input);
2129
+ }
2130
+ execute(container, input) {
2131
+ const matches = input.match(__classPrivateFieldGet(this, _IcingCommand_regex, "f"));
2132
+ if (!matches)
2133
+ throw new UnexpectedParseError("Match not found");
2134
+ if (!container.icing)
2135
+ container.icing = [];
2136
+ container.icing.push({
2137
+ intensity: as(matches[1], IcingIntensity),
2138
+ baseHeight: +matches[2] * 100,
2139
+ depth: +matches[3] * 1000,
2140
+ });
2141
+ }
2142
+ }
2143
+ _IcingCommand_regex = new WeakMap();
2144
+
2145
+ var _TurbulenceCommand_regex;
2146
+ class TurbulenceCommand {
2147
+ constructor() {
2148
+ _TurbulenceCommand_regex.set(this, /^5(\d|X)(\d{3})(\d)$/);
2149
+ }
2150
+ canParse(input) {
2151
+ return __classPrivateFieldGet(this, _TurbulenceCommand_regex, "f").test(input);
2152
+ }
2153
+ execute(container, input) {
2154
+ const matches = input.match(__classPrivateFieldGet(this, _TurbulenceCommand_regex, "f"));
2155
+ if (!matches)
2156
+ throw new UnexpectedParseError("Match not found");
2157
+ if (!container.turbulence)
2158
+ container.turbulence = [];
2159
+ container.turbulence.push({
2160
+ intensity: as(matches[1], TurbulenceIntensity),
2161
+ baseHeight: +matches[2] * 100,
2162
+ depth: +matches[3] * 1000,
2163
+ });
2164
+ }
2165
+ }
2166
+ _TurbulenceCommand_regex = new WeakMap();
2167
+
2168
+ var _CommandSupplier_commands;
2169
+ class CommandSupplier {
2170
+ constructor() {
2171
+ _CommandSupplier_commands.set(this, [new TurbulenceCommand(), new IcingCommand()]);
2172
+ }
2061
2173
  get(input) {
2062
2174
  for (const command of __classPrivateFieldGet(this, _CommandSupplier_commands, "f")) {
2063
2175
  if (command.canParse(input))
@@ -2067,7 +2179,7 @@ class CommandSupplier {
2067
2179
  }
2068
2180
  _CommandSupplier_commands = new WeakMap();
2069
2181
 
2070
- var _AbstractParser_INTENSITY_REGEX, _AbstractParser_CAVOK, _AbstractParser_commonSupplier, _MetarParser_commandSupplier, _TAFParser_validityPattern, _RemarkParser_supplier;
2182
+ var _AbstractParser_INTENSITY_REGEX, _AbstractParser_CAVOK, _AbstractParser_commonSupplier, _MetarParser_commandSupplier, _TAFParser_commandSupplier, _TAFParser_validityPattern, _RemarkParser_supplier;
2071
2183
  /**
2072
2184
  * Parses the delivery time of a METAR/TAF
2073
2185
  * @param abstractWeatherCode The TAF or METAR object
@@ -2171,32 +2283,52 @@ class AbstractParser {
2171
2283
  // #TOKENIZE_REGEX = /\s((?=\d\/\dSM)(?<!\s\d\s)|(?!\d\/\dSM))|=/;
2172
2284
  _AbstractParser_INTENSITY_REGEX.set(this, /^(-|\+|VC)/);
2173
2285
  _AbstractParser_CAVOK.set(this, "CAVOK");
2174
- _AbstractParser_commonSupplier.set(this, new CommandSupplier$1());
2286
+ _AbstractParser_commonSupplier.set(this, new CommandSupplier$2());
2175
2287
  }
2176
2288
  parseWeatherCondition(input) {
2177
2289
  let intensity;
2178
2290
  if (input.match(__classPrivateFieldGet(this, _AbstractParser_INTENSITY_REGEX, "f"))) {
2179
2291
  const match = input.match(__classPrivateFieldGet(this, _AbstractParser_INTENSITY_REGEX, "f"))?.[0];
2180
- if (match)
2292
+ if (match) {
2181
2293
  intensity = match;
2294
+ input = input.slice(match.length);
2295
+ }
2182
2296
  }
2183
2297
  let descriptive;
2184
- for (const key of Object.values(Descriptive)) {
2185
- if (input.includes(key))
2298
+ const descriptives = Object.values(Descriptive);
2299
+ for (let i = 0; i < descriptives.length; i++) {
2300
+ const key = descriptives[i];
2301
+ if (input.startsWith(key)) {
2186
2302
  descriptive = key;
2303
+ input = input.slice(key.length);
2304
+ break;
2305
+ }
2187
2306
  }
2188
2307
  const weatherCondition = {
2189
2308
  intensity,
2190
2309
  descriptive,
2191
2310
  phenomenons: [],
2192
2311
  };
2193
- for (const key of Object.values(Phenomenon)) {
2312
+ const phenomenons = Object.values(Phenomenon);
2313
+ for (let i = 0; i < phenomenons.length; i++) {
2314
+ const key = phenomenons[i];
2194
2315
  // Thunderstorm as descriptive should not be added as a phenomenon
2195
2316
  if (descriptive === key)
2196
2317
  continue;
2197
- if (input.includes(key))
2318
+ // Phenomenons can be separated with a slash
2319
+ const conditionRegex = new RegExp(`^\/?${key}`);
2320
+ const inputMatch = input.match(conditionRegex)?.[0];
2321
+ if (inputMatch) {
2198
2322
  weatherCondition.phenomenons.push(key);
2323
+ input = input.slice(inputMatch.length);
2324
+ // Restart the search for an additional phenomenon
2325
+ i = -1;
2326
+ continue;
2327
+ }
2199
2328
  }
2329
+ // If anything is left unparsed, it's not a valid weather condition
2330
+ if (input.replace(/\//g, "").length)
2331
+ return;
2200
2332
  return weatherCondition;
2201
2333
  }
2202
2334
  /**
@@ -2238,15 +2370,22 @@ class AbstractParser {
2238
2370
  };
2239
2371
  return true;
2240
2372
  }
2241
- const command = __classPrivateFieldGet(this, _AbstractParser_commonSupplier, "f").get(input);
2242
- if (command) {
2243
- return command.execute(abstractWeatherContainer, input);
2244
- }
2245
2373
  const weatherCondition = this.parseWeatherCondition(input);
2246
- if (isWeatherConditionValid(weatherCondition)) {
2374
+ if (weatherCondition && isWeatherConditionValid(weatherCondition)) {
2247
2375
  abstractWeatherContainer.weatherConditions.push(weatherCondition);
2248
2376
  return true;
2249
2377
  }
2378
+ const command = __classPrivateFieldGet(this, _AbstractParser_commonSupplier, "f").get(input);
2379
+ if (command) {
2380
+ try {
2381
+ return command.execute(abstractWeatherContainer, input);
2382
+ }
2383
+ catch (error) {
2384
+ if (error instanceof CommandExecutionError)
2385
+ return false;
2386
+ throw error;
2387
+ }
2388
+ }
2250
2389
  return false;
2251
2390
  }
2252
2391
  }
@@ -2256,7 +2395,7 @@ class MetarParser extends AbstractParser {
2256
2395
  super(...arguments);
2257
2396
  this.AT = "AT";
2258
2397
  this.TL = "TL";
2259
- _MetarParser_commandSupplier.set(this, new CommandSupplier());
2398
+ _MetarParser_commandSupplier.set(this, new CommandSupplier$1());
2260
2399
  }
2261
2400
  /**
2262
2401
  * Parses a trend of a metar
@@ -2354,6 +2493,7 @@ class TAFParser extends AbstractParser {
2354
2493
  this.PROB = "PROB";
2355
2494
  this.TX = "TX";
2356
2495
  this.TN = "TN";
2496
+ _TAFParser_commandSupplier.set(this, new CommandSupplier());
2357
2497
  _TAFParser_validityPattern.set(this, /^\d{4}\/\d{4}$/);
2358
2498
  }
2359
2499
  /**
@@ -2406,13 +2546,17 @@ class TAFParser extends AbstractParser {
2406
2546
  };
2407
2547
  for (let i = index + 1; i < lines[0].length; i++) {
2408
2548
  const token = lines[0][i];
2549
+ const tafCommand = __classPrivateFieldGet(this, _TAFParser_commandSupplier, "f").get(token);
2409
2550
  if (token == this.RMK) {
2410
2551
  parseRemark(taf, lines[0], i, this.locale);
2411
2552
  break;
2412
2553
  }
2554
+ else if (tafCommand) {
2555
+ tafCommand.execute(taf, token);
2556
+ }
2413
2557
  else {
2414
- parseFlags(taf, token);
2415
2558
  this.generalParse(taf, token);
2559
+ parseFlags(taf, token);
2416
2560
  }
2417
2561
  }
2418
2562
  const minMaxTemperatureLines = [
@@ -2537,6 +2681,7 @@ class TAFParser extends AbstractParser {
2537
2681
  */
2538
2682
  parseTrend(index, line, trend) {
2539
2683
  for (let i = index; i < line.length; i++) {
2684
+ const tafCommand = __classPrivateFieldGet(this, _TAFParser_commandSupplier, "f").get(line[i]);
2540
2685
  if (line[i] === this.RMK) {
2541
2686
  parseRemark(trend, line, i, this.locale);
2542
2687
  break;
@@ -2544,6 +2689,9 @@ class TAFParser extends AbstractParser {
2544
2689
  // already parsed
2545
2690
  else if (__classPrivateFieldGet(this, _TAFParser_validityPattern, "f").test(line[i]))
2546
2691
  continue;
2692
+ else if (tafCommand) {
2693
+ tafCommand.execute(trend, line[i]);
2694
+ }
2547
2695
  else
2548
2696
  super.generalParse(trend, line[i]);
2549
2697
  }
@@ -2556,7 +2704,7 @@ class TAFParser extends AbstractParser {
2556
2704
  };
2557
2705
  }
2558
2706
  }
2559
- _TAFParser_validityPattern = new WeakMap();
2707
+ _TAFParser_commandSupplier = new WeakMap(), _TAFParser_validityPattern = new WeakMap();
2560
2708
  class RemarkParser {
2561
2709
  constructor(locale) {
2562
2710
  this.locale = locale;
@@ -2592,7 +2740,7 @@ _RemarkParser_supplier = new WeakMap();
2592
2740
  * @param minute Minute (from the report)
2593
2741
  * @returns
2594
2742
  */
2595
- function determineReportIssuedDate(date, day, hour, minute) {
2743
+ function determineReportDate(date, day, hour, minute = 0) {
2596
2744
  // Some TAF reports do not include a delivery time
2597
2745
  if (day == null || hour == null)
2598
2746
  return date;
@@ -2608,17 +2756,6 @@ function determineReportIssuedDate(date, day, hour, minute) {
2608
2756
  }))
2609
2757
  .sort((a, b) => a.difference - b.difference)[0].date;
2610
2758
  }
2611
- function getReportDate(issued, day, hour, minute = 0) {
2612
- let date = new Date(issued);
2613
- if (day < date.getUTCDate()) {
2614
- date = addMonthsUTC(date, 1);
2615
- }
2616
- date.setUTCDate(day);
2617
- date.setUTCHours(hour);
2618
- if (minute != null)
2619
- date.setUTCMinutes(minute);
2620
- return date;
2621
- }
2622
2759
  function setDateComponents(date, day, hour, minute) {
2623
2760
  date.setUTCDate(day);
2624
2761
  date.setUTCHours(hour);
@@ -2641,30 +2778,30 @@ function addMonthsUTC(date, count) {
2641
2778
  function metarDatesHydrator(report, date) {
2642
2779
  return {
2643
2780
  ...report,
2644
- issued: determineReportIssuedDate(date, report.day, report.hour, report.minute),
2781
+ issued: determineReportDate(date, report.day, report.hour, report.minute),
2645
2782
  };
2646
2783
  }
2647
2784
 
2648
2785
  function tafDatesHydrator(report, date) {
2649
- const issued = determineReportIssuedDate(date, report.day, report.hour, report.minute);
2786
+ const issued = determineReportDate(date, report.day, report.hour, report.minute);
2650
2787
  return {
2651
2788
  ...report,
2652
2789
  issued,
2653
2790
  validity: {
2654
2791
  ...report.validity,
2655
- start: getReportDate(issued, report.validity.startDay, report.validity.startHour),
2656
- end: getReportDate(issued, report.validity.endDay, report.validity.endHour),
2792
+ start: determineReportDate(issued, report.validity.startDay, report.validity.startHour),
2793
+ end: determineReportDate(issued, report.validity.endDay, report.validity.endHour),
2657
2794
  },
2658
2795
  minTemperature: report.minTemperature
2659
2796
  ? {
2660
2797
  ...report.minTemperature,
2661
- date: getReportDate(issued, report.minTemperature.day, report.minTemperature.hour),
2798
+ date: determineReportDate(issued, report.minTemperature.day, report.minTemperature.hour),
2662
2799
  }
2663
2800
  : undefined,
2664
2801
  maxTemperature: report.maxTemperature
2665
2802
  ? {
2666
2803
  ...report.maxTemperature,
2667
- date: getReportDate(issued, report.maxTemperature.day, report.maxTemperature.hour),
2804
+ date: determineReportDate(issued, report.maxTemperature.day, report.maxTemperature.hour),
2668
2805
  }
2669
2806
  : undefined,
2670
2807
  trends: report.trends.map((trend) => ({
@@ -2674,13 +2811,13 @@ function tafDatesHydrator(report, date) {
2674
2811
  case WeatherChangeType.FM:
2675
2812
  return {
2676
2813
  ...trend.validity,
2677
- start: getReportDate(issued, trend.validity.startDay, trend.validity.startHour, trend.validity.startMinutes),
2814
+ start: determineReportDate(issued, trend.validity.startDay, trend.validity.startHour, trend.validity.startMinutes),
2678
2815
  };
2679
2816
  default:
2680
2817
  return {
2681
2818
  ...trend.validity,
2682
- start: getReportDate(issued, trend.validity.startDay, trend.validity.startHour),
2683
- end: getReportDate(issued, trend.validity.endDay, trend.validity.endHour),
2819
+ start: determineReportDate(issued, trend.validity.startDay, trend.validity.startHour),
2820
+ end: determineReportDate(issued, trend.validity.endDay, trend.validity.endHour),
2684
2821
  };
2685
2822
  }
2686
2823
  })(),
@@ -2692,8 +2829,8 @@ function getForecastFromTAF(taf) {
2692
2829
  const { trends, wind, visibility, verticalVisibility, windShear, cavok, remark, remarks, clouds, weatherConditions, initialRaw, validity, ...tafWithoutBaseProperties } = taf;
2693
2830
  return {
2694
2831
  ...tafWithoutBaseProperties,
2695
- start: getReportDate(taf.issued, taf.validity.startDay, taf.validity.startHour),
2696
- end: getReportDate(taf.issued, taf.validity.endDay, taf.validity.endHour),
2832
+ start: determineReportDate(taf.issued, taf.validity.startDay, taf.validity.startHour),
2833
+ end: determineReportDate(taf.issued, taf.validity.endDay, taf.validity.endHour),
2697
2834
  forecast: hydrateEndDates([makeInitialForecast(taf), ...taf.trends], taf.validity),
2698
2835
  };
2699
2836
  }
@@ -2712,6 +2849,8 @@ function makeInitialForecast(taf) {
2712
2849
  clouds: taf.clouds,
2713
2850
  weatherConditions: taf.weatherConditions,
2714
2851
  raw: taf.initialRaw,
2852
+ turbulence: taf.turbulence,
2853
+ icing: taf.icing,
2715
2854
  validity: {
2716
2855
  // End day/hour are for end of the entire TAF
2717
2856
  startDay: taf.validity.startDay,
@@ -2781,6 +2920,8 @@ function hydrateEndDates(trends, reportValidity) {
2781
2920
  * it needs to be populated
2782
2921
  */
2783
2922
  function hydrateWithPreviousContextIfNeeded(forecast, context) {
2923
+ // BECMG is the only forecast type that inherits old conditions
2924
+ // Anything else starts anew
2784
2925
  if (forecast.type !== WeatherChangeType.BECMG || !context)
2785
2926
  return forecast;
2786
2927
  // Remarks should not be carried over
@@ -2790,6 +2931,12 @@ function hydrateWithPreviousContextIfNeeded(forecast, context) {
2790
2931
  // vertical visibility should not be carried over, if clouds exist
2791
2932
  if (forecast.clouds.length)
2792
2933
  delete context.verticalVisibility;
2934
+ // CAVOK should not propagate if anything other than wind changes
2935
+ if (forecast.clouds.length ||
2936
+ forecast.verticalVisibility ||
2937
+ forecast.weatherConditions.length ||
2938
+ forecast.visibility)
2939
+ delete context.cavok;
2793
2940
  forecast = {
2794
2941
  ...context,
2795
2942
  ...forecast,
@@ -2865,4 +3012,4 @@ function parse(rawReport, options, parser, datesHydrator) {
2865
3012
  }
2866
3013
  }
2867
3014
 
2868
- export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, DistanceUnit, Intensity, InvalidWeatherStatementError, ParseError, Phenomenon, RemarkType, RunwayInfoTrend, RunwayInfoUnit, SpeedUnit, TimeIndicator, TimestampOutOfBoundsError, UnexpectedParseError, ValueIndicator, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
3015
+ export { CloudQuantity, CloudType, CommandExecutionError, Descriptive, Direction, DistanceUnit, IcingIntensity, Intensity, InvalidWeatherStatementError, ParseError, Phenomenon, RemarkType, RunwayInfoTrend, RunwayInfoUnit, SpeedUnit, TimeIndicator, TimestampOutOfBoundsError, TurbulenceIntensity, UnexpectedParseError, ValueIndicator, WeatherChangeType, getCompositeForecastForDate, isWeatherConditionValid, parseMetar, parseTAF, parseTAFAsForecast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metar-taf-parser",
3
- "version": "7.0.0",
3
+ "version": "7.1.1",
4
4
  "description": "Parse METAR and TAF reports",
5
5
  "homepage": "https://aeharding.github.io/metar-taf-parser",
6
6
  "keywords": [