metar-taf-parser 0.0.2 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -1
- package/README.md +26 -24
- package/dist/command/common.js +2 -0
- package/dist/command/remark.js +1 -1
- package/dist/commons/converter.d.ts +4 -1
- package/dist/commons/converter.js +5 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +13 -6
- package/dist/model/model.d.ts +20 -18
- package/dist/parser/parser.d.ts +41 -3
- package/dist/parser/parser.js +146 -22
- package/package.json +15 -6
package/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2022 Alexander Harding
|
|
4
|
+
Copyright 2021 Jean-Kevin KPADEY
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
1
|
-
# metar-taf-parser
|
|
1
|
+
# ✈️ [metar-taf-parser](https://aeharding.github.io/metar-taf-parser)
|
|
2
2
|
|
|
3
|
-
This is a port of [python-metar-taf-parser](https://github.com/mivek/python-metar-taf-parser) to
|
|
3
|
+
This is a port of [python-metar-taf-parser](https://github.com/mivek/python-metar-taf-parser) to Typescript. It's fully typed and tested with i18n support, and can run on Node or the browser.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Check out the demo here](https://aeharding.github.io/metar-taf-parser)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```sh
|
|
10
|
+
yarn add metar-taf-parser
|
|
11
|
+
# or
|
|
12
|
+
npm i --save metar-taf-parser
|
|
13
|
+
```
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
import { parseMetar } from 'metar-taf-parser'
|
|
15
|
+
## Usage
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
// For example: https://www.aviationweather.gov/dataserver
|
|
16
|
-
const { metar } = await myService.getAirportData('KMSN')
|
|
17
|
+
### Parse a METAR
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
```ts
|
|
20
|
+
import { parseMetar } from 'metar-taf-parser'
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
const metar = parseMetar(rawMetarString)
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
### Parse TAF
|
|
25
|
+
### Parse a TAF
|
|
25
26
|
|
|
26
27
|
```ts
|
|
27
28
|
import { parseTAF } from 'metar-taf-parser'
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
// For example: https://www.aviationweather.gov/dataserver
|
|
31
|
-
const { taf } = await myService.getAirportData('KMSN')
|
|
32
|
-
|
|
33
|
-
// Readily serializable
|
|
34
|
-
const tafResult = parseTAF(taf)
|
|
35
|
-
|
|
36
|
-
// Your code here 🚀
|
|
30
|
+
const taf = parseTAF(rawTAFString)
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
## i18n
|
|
40
34
|
|
|
41
35
|
```ts
|
|
42
36
|
import { parseMetar } from 'metar-taf-parser'
|
|
@@ -51,4 +45,12 @@ const metarResult = parseMetar(metar, { locale: de })
|
|
|
51
45
|
|
|
52
46
|
### Example site
|
|
53
47
|
|
|
54
|
-
Please see [the example site README.md](example/README.md).
|
|
48
|
+
Please see [the example site README.md](example/README.md).
|
|
49
|
+
|
|
50
|
+
## Contributing
|
|
51
|
+
|
|
52
|
+
This project is intended to provide feature parity with [python-metar-taf-parser](https://github.com/mivek/python-metar-taf-parser) and will only accept PRs to maintain feature parity or to fix inconsistencies with that project.
|
|
53
|
+
|
|
54
|
+
## Acknowledgment
|
|
55
|
+
|
|
56
|
+
This software port was made possible due to the fantastic work of [@mivek](https://github.com/mivek) in [python-metar-taf-parser](https://github.com/mivek/python-metar-taf-parser). If you like this project, please [consider buying @mivek a coffee](https://ko-fi.com/mivek).
|
package/dist/command/common.js
CHANGED
|
@@ -135,6 +135,8 @@ class WindVariationCommand {
|
|
|
135
135
|
wind.maxVariation = +matches[2];
|
|
136
136
|
}
|
|
137
137
|
execute(container, windString) {
|
|
138
|
+
if (!container.wind)
|
|
139
|
+
throw new errors_1.InvalidWeatherStatementError();
|
|
138
140
|
this.parseWindVariation(container.wind, windString);
|
|
139
141
|
return true;
|
|
140
142
|
}
|
package/dist/command/remark.js
CHANGED
|
@@ -158,7 +158,7 @@ class HourlyPressureCommand extends Command {
|
|
|
158
158
|
const matches = code.match(__classPrivateFieldGet(this, _HourlyPressureCommand_regex, "f"));
|
|
159
159
|
if (!matches)
|
|
160
160
|
throw new errors_1.UnexpectedParseError("Match not found");
|
|
161
|
-
remark.push(`${(0, i18n_1._)(`Remark.Barometer.${+matches[1]}`, this.locale)} ${(0, i18n_1.format)((0, i18n_1._)("Remark.Pressure.Tendency", this.locale), +matches[
|
|
161
|
+
remark.push(`${(0, i18n_1._)(`Remark.Barometer.${+matches[1]}`, this.locale)} ${(0, i18n_1.format)((0, i18n_1._)("Remark.Pressure.Tendency", this.locale), +matches[2] / 10)}`);
|
|
162
162
|
return [code.replace(__classPrivateFieldGet(this, _HourlyPressureCommand_regex, "f"), "").trim(), remark];
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -2,5 +2,8 @@ export declare function degreesToCardinal(input: number | string): string;
|
|
|
2
2
|
export declare function convertVisibility(input: string): string;
|
|
3
3
|
export declare function convertTemperature(input: string): number;
|
|
4
4
|
export declare function convertInchesMercuryToPascal(input: number): number;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Converts number `.toFixed(1)` before outputting to match python implementation
|
|
7
|
+
*/
|
|
8
|
+
export declare function convertTemperatureRemarks(sign: string, temperature: string): string;
|
|
6
9
|
export declare function convertPrecipitationAmount(amount: string): number;
|
|
@@ -44,11 +44,14 @@ function convertInchesMercuryToPascal(input) {
|
|
|
44
44
|
return 33.8639 * input;
|
|
45
45
|
}
|
|
46
46
|
exports.convertInchesMercuryToPascal = convertInchesMercuryToPascal;
|
|
47
|
+
/**
|
|
48
|
+
* Converts number `.toFixed(1)` before outputting to match python implementation
|
|
49
|
+
*/
|
|
47
50
|
function convertTemperatureRemarks(sign, temperature) {
|
|
48
51
|
const temp = +temperature / 10;
|
|
49
52
|
if (sign === "0")
|
|
50
|
-
return temp;
|
|
51
|
-
return -temp;
|
|
53
|
+
return temp.toFixed(1);
|
|
54
|
+
return (-temp).toFixed(1);
|
|
52
55
|
}
|
|
53
56
|
exports.convertTemperatureRemarks = convertTemperatureRemarks;
|
|
54
57
|
function convertPrecipitationAmount(amount) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { IMetar } from "./model/model";
|
|
1
|
+
import { IMetar, ITAF } from "./model/model";
|
|
2
2
|
import { Locale } from "./commons/i18n";
|
|
3
|
-
export interface IMetarTAFParserOptions {
|
|
4
|
-
locale?: Locale;
|
|
5
|
-
}
|
|
6
|
-
export declare function parseMetar(metar: string, options: IMetarTAFParserOptions): IMetar;
|
|
7
3
|
export { Locale } from "./commons/i18n";
|
|
8
4
|
export * from "./commons/errors";
|
|
9
5
|
export * from "./model/model";
|
|
10
6
|
export * from "./model/enum";
|
|
7
|
+
export interface IMetarTAFParserOptions {
|
|
8
|
+
locale?: Locale;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseMetar(metar: string, options?: IMetarTAFParserOptions): IMetar;
|
|
11
|
+
export declare function parseTAF(taf: string, options?: IMetarTAFParserOptions): ITAF;
|
package/dist/index.js
CHANGED
|
@@ -17,14 +17,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.parseMetar = void 0;
|
|
20
|
+
exports.parseTAF = exports.parseMetar = void 0;
|
|
21
21
|
const parser_1 = require("./parser/parser");
|
|
22
22
|
const errors_1 = require("./commons/errors");
|
|
23
23
|
const en_1 = __importDefault(require("./locale/en"));
|
|
24
|
+
__exportStar(require("./commons/errors"), exports);
|
|
25
|
+
__exportStar(require("./model/model"), exports);
|
|
26
|
+
__exportStar(require("./model/enum"), exports);
|
|
24
27
|
function parseMetar(metar, options) {
|
|
28
|
+
return parse(metar, options, parser_1.MetarParser);
|
|
29
|
+
}
|
|
30
|
+
exports.parseMetar = parseMetar;
|
|
31
|
+
function parseTAF(taf, options) {
|
|
32
|
+
return parse(taf, options, parser_1.TAFParser);
|
|
33
|
+
}
|
|
34
|
+
exports.parseTAF = parseTAF;
|
|
35
|
+
function parse(report, options, parser) {
|
|
25
36
|
const lang = (options === null || options === void 0 ? void 0 : options.locale) || en_1.default;
|
|
26
37
|
try {
|
|
27
|
-
return new
|
|
38
|
+
return new parser(lang).parse(report);
|
|
28
39
|
}
|
|
29
40
|
catch (e) {
|
|
30
41
|
if (e instanceof errors_1.ParseError)
|
|
@@ -32,7 +43,3 @@ function parseMetar(metar, options) {
|
|
|
32
43
|
throw new errors_1.InvalidWeatherStatementError(e);
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
|
-
exports.parseMetar = parseMetar;
|
|
36
|
-
__exportStar(require("./commons/errors"), exports);
|
|
37
|
-
__exportStar(require("./model/model"), exports);
|
|
38
|
-
__exportStar(require("./model/enum"), exports);
|
package/dist/model/model.d.ts
CHANGED
|
@@ -55,12 +55,12 @@ export interface ICloud {
|
|
|
55
55
|
type?: CloudType;
|
|
56
56
|
}
|
|
57
57
|
export interface IAbstractWeatherContainer {
|
|
58
|
-
wind
|
|
58
|
+
wind?: IWind;
|
|
59
59
|
visibility?: Visibility;
|
|
60
|
-
verticalVisibility
|
|
61
|
-
windShear
|
|
62
|
-
cavok
|
|
63
|
-
remark
|
|
60
|
+
verticalVisibility?: number;
|
|
61
|
+
windShear?: IWindShear;
|
|
62
|
+
cavok?: boolean;
|
|
63
|
+
remark?: string;
|
|
64
64
|
remarks: string[];
|
|
65
65
|
clouds: ICloud[];
|
|
66
66
|
weatherConditions: IWeatherCondition[];
|
|
@@ -78,25 +78,25 @@ export interface ITime {
|
|
|
78
78
|
}
|
|
79
79
|
export interface IAbstractWeatherCode extends IAbstractWeatherContainer, ITime {
|
|
80
80
|
day: number;
|
|
81
|
-
airport
|
|
81
|
+
airport?: IAirport;
|
|
82
82
|
message: string;
|
|
83
83
|
station: string;
|
|
84
84
|
trends: IAbstractTrend[];
|
|
85
85
|
}
|
|
86
86
|
export interface IMetar extends IAbstractWeatherCode {
|
|
87
|
-
temperature
|
|
88
|
-
dewPoint
|
|
89
|
-
altimeter
|
|
87
|
+
temperature?: number;
|
|
88
|
+
dewPoint?: number;
|
|
89
|
+
altimeter?: number;
|
|
90
90
|
nosig: boolean;
|
|
91
91
|
auto: boolean;
|
|
92
92
|
runwaysInfo: IRunwayInfo[];
|
|
93
93
|
trends: IMetarTrend[];
|
|
94
94
|
}
|
|
95
95
|
export interface ITAF extends IAbstractWeatherCode {
|
|
96
|
-
validity:
|
|
97
|
-
maxTemperature
|
|
98
|
-
minTemperature
|
|
99
|
-
amendment
|
|
96
|
+
validity: IValidity;
|
|
97
|
+
maxTemperature?: ITemperatureDated;
|
|
98
|
+
minTemperature?: ITemperatureDated;
|
|
99
|
+
amendment?: boolean;
|
|
100
100
|
trends: ITAFTrend[];
|
|
101
101
|
}
|
|
102
102
|
export interface IAbstractTrend extends IAbstractWeatherContainer {
|
|
@@ -109,13 +109,15 @@ export interface IMetarTrend extends IAbstractTrend {
|
|
|
109
109
|
times: IMetarTrendTime[];
|
|
110
110
|
}
|
|
111
111
|
export interface ITAFTrend extends IAbstractTrend {
|
|
112
|
-
probability
|
|
113
|
-
validity
|
|
112
|
+
probability?: number;
|
|
113
|
+
validity?: IFMValidity;
|
|
114
114
|
}
|
|
115
|
-
export interface
|
|
115
|
+
export interface IEndValidity {
|
|
116
116
|
endHour: number;
|
|
117
117
|
endDay: number;
|
|
118
118
|
}
|
|
119
|
-
export interface
|
|
120
|
-
|
|
119
|
+
export interface IValidity extends IAbstractValidity, IEndValidity {
|
|
120
|
+
}
|
|
121
|
+
export interface IFMValidity extends IAbstractValidity, Partial<IEndValidity> {
|
|
122
|
+
startMinutes?: number;
|
|
121
123
|
}
|
package/dist/parser/parser.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { IAbstractWeatherContainer, IMetar, IMetarTrend, ITemperatureDated, IValidity, IWeatherCondition } from "../model/model";
|
|
1
|
+
import { IAbstractWeatherContainer, IMetar, IMetarTrend, ITAF, ITAFTrend, ITemperatureDated, IValidity, IWeatherCondition } from "../model/model";
|
|
2
|
+
import { WeatherChangeType } from "../model/enum";
|
|
2
3
|
import { Locale } from "../commons/i18n";
|
|
3
4
|
/**
|
|
4
5
|
* Parses the temperature in a TAF
|
|
@@ -18,10 +19,12 @@ export declare function parseValidity(input: string): IValidity;
|
|
|
18
19
|
*/
|
|
19
20
|
export declare abstract class AbstractParser {
|
|
20
21
|
#private;
|
|
22
|
+
protected locale: Locale;
|
|
21
23
|
FM: string;
|
|
22
24
|
TEMPO: string;
|
|
23
25
|
BECMG: string;
|
|
24
26
|
RMK: string;
|
|
27
|
+
constructor(locale: Locale);
|
|
25
28
|
parseWeatherCondition(input: string): IWeatherCondition;
|
|
26
29
|
/**
|
|
27
30
|
* Parses the message into different tokens
|
|
@@ -39,10 +42,8 @@ export declare abstract class AbstractParser {
|
|
|
39
42
|
}
|
|
40
43
|
export declare class MetarParser extends AbstractParser {
|
|
41
44
|
#private;
|
|
42
|
-
private locale;
|
|
43
45
|
AT: string;
|
|
44
46
|
TL: string;
|
|
45
|
-
constructor(locale: Locale);
|
|
46
47
|
/**
|
|
47
48
|
* Parses a trend of a metar
|
|
48
49
|
* @param index the index starting the trend in the list
|
|
@@ -58,6 +59,43 @@ export declare class MetarParser extends AbstractParser {
|
|
|
58
59
|
*/
|
|
59
60
|
parse(input: string): IMetar;
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Parser for TAF messages
|
|
64
|
+
*/
|
|
65
|
+
export declare class TAFParser extends AbstractParser {
|
|
66
|
+
#private;
|
|
67
|
+
TAF: string;
|
|
68
|
+
PROB: string;
|
|
69
|
+
TX: string;
|
|
70
|
+
TN: string;
|
|
71
|
+
/**
|
|
72
|
+
* the message to parse
|
|
73
|
+
* @param input
|
|
74
|
+
* @returns a TAF object
|
|
75
|
+
* @throws ParseError if the message is invalid
|
|
76
|
+
*/
|
|
77
|
+
parse(input: string): ITAF;
|
|
78
|
+
/**
|
|
79
|
+
* Format the message as a multiple line code so each line can be parsed
|
|
80
|
+
* @param tafCode The base message
|
|
81
|
+
* @returns a list of string representing the lines of the message
|
|
82
|
+
*/
|
|
83
|
+
extractLinesTokens(tafCode: string): string[][];
|
|
84
|
+
/**
|
|
85
|
+
* Parses the tokens of the line and updates the TAF object
|
|
86
|
+
* @param taf TAF object to update
|
|
87
|
+
* @param lineTokens the array of tokens representing a line
|
|
88
|
+
*/
|
|
89
|
+
parseLine(taf: ITAF, lineTokens: string[]): void;
|
|
90
|
+
/**
|
|
91
|
+
* Parses a trend of the TAF
|
|
92
|
+
* @param index the index at which the array should be parsed
|
|
93
|
+
* @param line The array of string containing the line
|
|
94
|
+
* @param trend The trend object to update
|
|
95
|
+
*/
|
|
96
|
+
parseTrend(index: number, line: string[], trend: ITAFTrend): void;
|
|
97
|
+
makeEmptyTAFTrend(type: WeatherChangeType): ITAFTrend;
|
|
98
|
+
}
|
|
61
99
|
export declare class RemarkParser {
|
|
62
100
|
#private;
|
|
63
101
|
private locale;
|
package/dist/parser/parser.js
CHANGED
|
@@ -27,9 +27,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
27
27
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
28
28
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
29
29
|
};
|
|
30
|
-
var _AbstractParser_INTENSITY_REGEX, _AbstractParser_CAVOK, _AbstractParser_commonSupplier, _MetarParser_commandSupplier, _RemarkParser_supplier;
|
|
30
|
+
var _AbstractParser_INTENSITY_REGEX, _AbstractParser_CAVOK, _AbstractParser_commonSupplier, _MetarParser_commandSupplier, _TAFParser_validityPattern, _RemarkParser_supplier;
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.RemarkParser = exports.MetarParser = exports.AbstractParser = exports.parseValidity = exports.parseTemperature = void 0;
|
|
32
|
+
exports.RemarkParser = exports.TAFParser = exports.MetarParser = exports.AbstractParser = exports.parseValidity = exports.parseTemperature = void 0;
|
|
33
33
|
const remark_1 = require("../command/remark");
|
|
34
34
|
const model_1 = require("../model/model");
|
|
35
35
|
const converter = __importStar(require("../commons/converter"));
|
|
@@ -49,8 +49,6 @@ function parseDeliveryTime(timeString) {
|
|
|
49
49
|
hour: +timeString.slice(2, 4),
|
|
50
50
|
minute: +timeString.slice(4, 6),
|
|
51
51
|
};
|
|
52
|
-
// TODO
|
|
53
|
-
// abstractWeatherCode.time = new Date()
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
56
54
|
* This function parses the array containing the remark and concat the array into a string
|
|
@@ -109,7 +107,8 @@ function parseFromValidity(input) {
|
|
|
109
107
|
* Base parser.
|
|
110
108
|
*/
|
|
111
109
|
class AbstractParser {
|
|
112
|
-
constructor() {
|
|
110
|
+
constructor(locale) {
|
|
111
|
+
this.locale = locale;
|
|
113
112
|
this.FM = "FM";
|
|
114
113
|
this.TEMPO = "TEMPO";
|
|
115
114
|
this.BECMG = "BECMG";
|
|
@@ -200,9 +199,8 @@ class AbstractParser {
|
|
|
200
199
|
exports.AbstractParser = AbstractParser;
|
|
201
200
|
_AbstractParser_INTENSITY_REGEX = new WeakMap(), _AbstractParser_CAVOK = new WeakMap(), _AbstractParser_commonSupplier = new WeakMap();
|
|
202
201
|
class MetarParser extends AbstractParser {
|
|
203
|
-
constructor(
|
|
204
|
-
super();
|
|
205
|
-
this.locale = locale;
|
|
202
|
+
constructor() {
|
|
203
|
+
super(...arguments);
|
|
206
204
|
this.AT = "AT";
|
|
207
205
|
this.TL = "TL";
|
|
208
206
|
_MetarParser_commandSupplier.set(this, new metar_1.CommandSupplier());
|
|
@@ -224,7 +222,6 @@ class MetarParser extends AbstractParser {
|
|
|
224
222
|
trendParts[i].startsWith(this.AT)) {
|
|
225
223
|
const trendTime = {
|
|
226
224
|
type: enum_1.TimeIndicator[trendParts[i].slice(0, 2)],
|
|
227
|
-
// TODO implement time
|
|
228
225
|
hour: +trendParts[i].slice(2, 4),
|
|
229
226
|
minute: +trendParts[i].slice(4, 6),
|
|
230
227
|
};
|
|
@@ -244,18 +241,7 @@ class MetarParser extends AbstractParser {
|
|
|
244
241
|
*/
|
|
245
242
|
parse(input) {
|
|
246
243
|
const metarTab = this.tokenize(input);
|
|
247
|
-
const metar = Object.assign(Object.assign({}, parseDeliveryTime(metarTab[1])), { station: metarTab[0], message: input,
|
|
248
|
-
// wind: IWind,
|
|
249
|
-
// visibility?: Visibility,
|
|
250
|
-
// verticalVisibility: number,
|
|
251
|
-
// windShear: IWindShear,
|
|
252
|
-
cavok: false, remark: "", remarks: [], clouds: [], weatherConditions: [],
|
|
253
|
-
// airport: IAirport,
|
|
254
|
-
trends: [],
|
|
255
|
-
// temperature: number,
|
|
256
|
-
// dewPoint: number,
|
|
257
|
-
// altimeter: number,
|
|
258
|
-
nosig: false, auto: false, runwaysInfo: [] }); // TODO remove cast
|
|
244
|
+
const metar = Object.assign(Object.assign({}, parseDeliveryTime(metarTab[1])), { station: metarTab[0], message: input, cavok: false, remark: "", remarks: [], clouds: [], weatherConditions: [], trends: [], nosig: false, auto: false, runwaysInfo: [] });
|
|
259
245
|
let index = 2;
|
|
260
246
|
while (index < metarTab.length) {
|
|
261
247
|
if (!super.generalParse(metar, metarTab[index])) {
|
|
@@ -272,8 +258,8 @@ class MetarParser extends AbstractParser {
|
|
|
272
258
|
weatherConditions: [],
|
|
273
259
|
clouds: [],
|
|
274
260
|
times: [],
|
|
261
|
+
remarks: [],
|
|
275
262
|
};
|
|
276
|
-
// TODO - remove casting
|
|
277
263
|
index = this.parseTrend(index, trend, metarTab);
|
|
278
264
|
metar.trends.push(trend);
|
|
279
265
|
}
|
|
@@ -294,6 +280,144 @@ class MetarParser extends AbstractParser {
|
|
|
294
280
|
}
|
|
295
281
|
exports.MetarParser = MetarParser;
|
|
296
282
|
_MetarParser_commandSupplier = new WeakMap();
|
|
283
|
+
/**
|
|
284
|
+
* Parser for TAF messages
|
|
285
|
+
*/
|
|
286
|
+
class TAFParser extends AbstractParser {
|
|
287
|
+
constructor() {
|
|
288
|
+
super(...arguments);
|
|
289
|
+
this.TAF = "TAF";
|
|
290
|
+
this.PROB = "PROB";
|
|
291
|
+
this.TX = "TX";
|
|
292
|
+
this.TN = "TN";
|
|
293
|
+
_TAFParser_validityPattern.set(this, /^\d{4}\/\d{4}$/);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* the message to parse
|
|
297
|
+
* @param input
|
|
298
|
+
* @returns a TAF object
|
|
299
|
+
* @throws ParseError if the message is invalid
|
|
300
|
+
*/
|
|
301
|
+
parse(input) {
|
|
302
|
+
let amendment;
|
|
303
|
+
const lines = this.extractLinesTokens(input);
|
|
304
|
+
if (lines[0][0] !== this.TAF)
|
|
305
|
+
throw new errors_1.InvalidWeatherStatementError('TAF report must begin with string "TAF"');
|
|
306
|
+
let index = 1;
|
|
307
|
+
if (lines[0][1] === this.TAF)
|
|
308
|
+
index = 2;
|
|
309
|
+
if (lines[0][index] === "AMD") {
|
|
310
|
+
amendment = true;
|
|
311
|
+
index += 1;
|
|
312
|
+
}
|
|
313
|
+
const station = lines[0][index];
|
|
314
|
+
index += 1;
|
|
315
|
+
const time = parseDeliveryTime(lines[0][index]);
|
|
316
|
+
index += 1;
|
|
317
|
+
const validity = parseValidity(lines[0][index]);
|
|
318
|
+
const taf = Object.assign(Object.assign({ station,
|
|
319
|
+
amendment }, time), { validity, message: input, trends: [], remarks: [], clouds: [], weatherConditions: [] });
|
|
320
|
+
for (let i = index + 1; i < lines[0].length; i++) {
|
|
321
|
+
const token = lines[0][i];
|
|
322
|
+
if (token == this.RMK)
|
|
323
|
+
parseRemark(taf, lines[0], i, this.locale);
|
|
324
|
+
else if (token.startsWith(this.TX))
|
|
325
|
+
taf.maxTemperature = parseTemperature(token);
|
|
326
|
+
else if (token.startsWith(this.TN))
|
|
327
|
+
taf.minTemperature = parseTemperature(token);
|
|
328
|
+
else
|
|
329
|
+
this.generalParse(taf, token);
|
|
330
|
+
}
|
|
331
|
+
// Handle the other lines
|
|
332
|
+
for (let i = 1; i < lines.length; i++) {
|
|
333
|
+
this.parseLine(taf, lines[i]);
|
|
334
|
+
}
|
|
335
|
+
return taf;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Format the message as a multiple line code so each line can be parsed
|
|
339
|
+
* @param tafCode The base message
|
|
340
|
+
* @returns a list of string representing the lines of the message
|
|
341
|
+
*/
|
|
342
|
+
extractLinesTokens(tafCode) {
|
|
343
|
+
const singleLine = tafCode.replace(/\n/g, " ");
|
|
344
|
+
const cleanLine = singleLine.replace(/\s{2,}/g, " ");
|
|
345
|
+
const lines = joinProbIfNeeded(cleanLine
|
|
346
|
+
.replace(/\s(?=PROB\d{2}\sTEMPO|TEMPO|BECMG|FM|PROB)/g, "\n")
|
|
347
|
+
.split(/\n/));
|
|
348
|
+
// TODO cleanup
|
|
349
|
+
function joinProbIfNeeded(ls) {
|
|
350
|
+
for (let i = 0; i < ls.length; i++) {
|
|
351
|
+
if (/PROB\d{2}/.test(ls[i]) && /TEMPO/.test(ls[i + 1])) {
|
|
352
|
+
ls.splice(i, 2, `${ls[i]} ${ls[i + 1]}`);
|
|
353
|
+
i--;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return ls;
|
|
357
|
+
}
|
|
358
|
+
const linesToken = lines.map(this.tokenize);
|
|
359
|
+
if (linesToken.length > 1) {
|
|
360
|
+
const lastLine = linesToken[lines.length - 1];
|
|
361
|
+
const temperatures = lastLine.filter((l) => l.startsWith(this.TX) || l.startsWith(this.TN));
|
|
362
|
+
if (temperatures.length) {
|
|
363
|
+
linesToken[0] = linesToken[0].concat(temperatures);
|
|
364
|
+
linesToken[lines.length - 1] = lastLine.filter((l) => !l.startsWith(this.TX) && !l.startsWith(this.TN));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return linesToken;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Parses the tokens of the line and updates the TAF object
|
|
371
|
+
* @param taf TAF object to update
|
|
372
|
+
* @param lineTokens the array of tokens representing a line
|
|
373
|
+
*/
|
|
374
|
+
parseLine(taf, lineTokens) {
|
|
375
|
+
let index = 1;
|
|
376
|
+
let trend;
|
|
377
|
+
if (lineTokens[0].startsWith(this.FM)) {
|
|
378
|
+
trend = Object.assign(Object.assign({}, this.makeEmptyTAFTrend(enum_1.WeatherChangeType.FM)), { validity: parseFromValidity(lineTokens[0]) });
|
|
379
|
+
}
|
|
380
|
+
else if (lineTokens[0].startsWith(this.PROB)) {
|
|
381
|
+
trend = this.makeEmptyTAFTrend(enum_1.WeatherChangeType.PROB);
|
|
382
|
+
if (lineTokens.length > 1 && lineTokens[1] === this.TEMPO) {
|
|
383
|
+
trend = this.makeEmptyTAFTrend(enum_1.WeatherChangeType[lineTokens[1]]);
|
|
384
|
+
index = 2;
|
|
385
|
+
}
|
|
386
|
+
trend.probability = +lineTokens[0].slice(4);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
trend = this.makeEmptyTAFTrend(enum_1.WeatherChangeType[lineTokens[0]]);
|
|
390
|
+
}
|
|
391
|
+
this.parseTrend(index, lineTokens, trend);
|
|
392
|
+
taf.trends.push(trend);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Parses a trend of the TAF
|
|
396
|
+
* @param index the index at which the array should be parsed
|
|
397
|
+
* @param line The array of string containing the line
|
|
398
|
+
* @param trend The trend object to update
|
|
399
|
+
*/
|
|
400
|
+
parseTrend(index, line, trend) {
|
|
401
|
+
for (let i = index; i < line.length; i++) {
|
|
402
|
+
if (line[i] === this.RMK)
|
|
403
|
+
parseRemark(trend, line, i, this.locale);
|
|
404
|
+
else if (__classPrivateFieldGet(this, _TAFParser_validityPattern, "f").test(line[i]))
|
|
405
|
+
trend.validity = parseValidity(line[i]);
|
|
406
|
+
else
|
|
407
|
+
super.generalParse(trend, line[i]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
makeEmptyTAFTrend(type) {
|
|
411
|
+
return {
|
|
412
|
+
type,
|
|
413
|
+
remarks: [],
|
|
414
|
+
clouds: [],
|
|
415
|
+
weatherConditions: [],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
exports.TAFParser = TAFParser;
|
|
420
|
+
_TAFParser_validityPattern = new WeakMap();
|
|
297
421
|
class RemarkParser {
|
|
298
422
|
constructor(locale) {
|
|
299
423
|
this.locale = locale;
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metar-taf-parser",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Parse METAR and TAF
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Parse METAR and TAF reports",
|
|
5
|
+
"homepage": "https://aeharding.github.io/metar-taf-parser",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"aviation",
|
|
8
|
+
"weather",
|
|
9
|
+
"metar",
|
|
10
|
+
"taf",
|
|
11
|
+
"report",
|
|
12
|
+
"deserialize"
|
|
13
|
+
],
|
|
5
14
|
"main": "./dist/index.js",
|
|
6
15
|
"types": "./dist/index.d.ts",
|
|
7
16
|
"author": "Alexander Harding <2166114+aeharding@users.noreply.github.com>",
|
|
@@ -11,14 +20,14 @@
|
|
|
11
20
|
"url": "https://github.com/aeharding/metar-taf-parser.git"
|
|
12
21
|
},
|
|
13
22
|
"scripts": {
|
|
14
|
-
"build": "yarn clean; tsc && tsc-alias",
|
|
23
|
+
"build": "yarn clean; tsc --project tsconfig.build.json && tsc-alias",
|
|
15
24
|
"clean": "rm -rf ./dist",
|
|
16
25
|
"start": "node --experimental-specifier-resolution=node --loader ts-node/esm lib",
|
|
17
26
|
"check-types": "tsc --noEmit",
|
|
18
27
|
"check-formatting": "prettier --check '**/*.{js,json,css,md,scss,tsx,ts}'",
|
|
19
|
-
"watch": "watch 'yarn build'
|
|
20
|
-
"test": "jest",
|
|
21
|
-
"test-watch": "jest --watch",
|
|
28
|
+
"watch": "watch 'yarn build' src",
|
|
29
|
+
"test": "jest --coverage",
|
|
30
|
+
"test-watch": "jest --watch --coverage",
|
|
22
31
|
"prepublishOnly": "yarn build"
|
|
23
32
|
},
|
|
24
33
|
"files": [
|