Giter Club home page Giter Club logo

metar-taf-parser's Introduction

Parser for METeorological Aerodrome Reports (METARs) and Terminal Aerodrome Forecasts (TAFs). This is a port of python-metar-taf-parser to Typescript with some additional features.

Check out the demo here

Features:

  • ✈️ Complete METAR and TAF parsing
  • 🛠 Fully typed
  • 🪶 Dependency free
  • 🧪 Full test suite
  • ✅ Runs anywhere: Web browser or Node
  • 🌎 i18n: Translations
  • 🌏 i18n: Handling international TAF & METAR report format differences
  • 🌪 Remark parsing to human and machine readable formats
  • 🗓 Forecast abstraction to easily query TAF reports by Date

Installation

pnpm i metar-taf-parser
# or
npm i --save metar-taf-parser

Usage

Parsing

The parseMetar & parseTAF functions are designed to parse the raw report string into an object representation of a METAR/TAF.

parseMetar

If the payload begins with METAR or SPECI, that will be added as the type.

import { parseMetar } from "metar-taf-parser";

const metar = parseMetar(rawMetarString);

// -or-

// Optionally pass the date issued to add it to the report
const datedMetar = parseMetar(rawMetarString, { issued });

parseTAF

👉 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 below which may provide TAF data in a more normalized and easier to use format, depending on your use case.

import { parseTAF } from "metar-taf-parser";

const taf = parseTAF(rawTAFString);

// -or-

// Optionally pass the date issued to get the report issued and
// trend validity dates (start/end) on the report:
const datedTAF = parseTAF(rawTAFString, { issued });

Higher level parsing: The Forecast abstraction

TAF reports are a little funky... FM, BECMG, PROB, weird validity periods, etc. You may find the higher level Forecast abstraction more helpful.

⚠️ 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:

  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.

    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.

    const firstForecast = report.forecast[1];
    if (firstForecast.type === WeatherChangeType.BECMG) {
      // Can now access `by`
      console.log(firstForecast.by);
    }
  2. BECMG trends are hydrated with the context of previous trends. For example, if:

    TAF SBBR 221500Z 2218/2318 15008KT 9999 FEW045
      BECMG 2308/2310 09002KT
    

    Then the BECMG group will also have visibility and clouds from previously found conditions, with updated winds.

parseTAFAsForecast

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 TAF decoder.

import { parseTAFAsForecast } from "metar-taf-parser";

// You must provide an issued date to use the Forecast abstraction
const report = parseTAFAsForecast(rawTAFString, { issued: tafIssuedDate });

console.log(report.forecast);

getCompositeForecastForDate

⚠️ Warning: Experimental API

Provides all relevant weather conditions for a given timestamp. It returns an ICompositeForecast with a prevailing and supplemental component. The prevailing component is the prevailing weather condition period (type = FM, BECMG, or undefined) - and there will always be one.

The supplemental property is an array of weather condition periods valid for the given timestamp (any PROB, TEMPO and/or INTER) - conditions that are ephemeral and/or lower probability.

You will still need to write some logic to determine what data to use - for example, if supplemental[0].visibility exists, you may want to use it over prevailing.visibility, or otherwise present it to the user.

This function throws a TimestampOutOfBoundsError if the provided date is outside of the report validity period.

Example

This example provides an array of hourly weather conditions over the duration of the TAF report.

import { eachHourOfInterval } from "date-fns";
import {
  parseTAFAsForecast,
  getCompositeForecastForDate,
} from "metar-taf-parser";

const report = parseTAFAsForecast(rawTAFString, { issued: tafIssuedDate });

const forecastPerHour = eachHourOfInterval({
  start: report.start,
  end: report.end,
}).map((hour) => ({
  hour,
  ...getCompositeForecastForDate(hour, report),
}));

i18n

The description property in the Remark is translated, if available.

import { parseMetar } from "metar-taf-parser";
import de from "metar-taf-parser/locale/de";

const rawMetarReport = "KTTN 051853Z 04011KT RMK SLP176";

const metarResult = parseMetar(rawMetarReport, {
  locale: de,
});

console.log(metarReport.remarks[0].description);

Handling remarks

Remarks may be found on base TAF and METARs, along with TAF trends.

Each Remark will have a description (if translated), type and raw properties. There are additional properties for each unique remark, depending on the remark's type. We can type guard on type to access these unique properties.

If the remark is not understood, it will have type as RemarkType.Unknown, with raw containing everything until the next understood remark.

Example

import { Remark, RemarkType } from "metar-taf-parser";

/**
 * Find the sea level pressure given remarks, if defined
 */
function findSeaLevelPressure(remarks: Remark[]): number | undefined {
  for (const remark of remarks) {
    switch (remark.type) {
      case RemarkType.SeaLevelPressure:
        // can now access remark.pressure
        return remark.pressure;
    }
  }
}

Determining flight category, ceiling, etc

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. Feel free to copy - it's MIT licensed.

Development

Example site

Please see the example site README.md.

Contributing

This project is based on python-metar-taf-parser and the parsing should be as similar to that project as possible. That being said, PRs are welcome.

Acknowledgment

This software port was made possible due to the fantastic work of @mivek in python-metar-taf-parser. If you like this project, please consider buying @mivek a coffee.

metar-taf-parser's People

Contributors

aeharding avatar mattj65817 avatar skysails avatar volodymyr-tomorrow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

metar-taf-parser's Issues

"Zero wind" (`00000KT`) scenario not completely handled

The following example METAR incorrectly parses to zero KT wind. While it is technically correct (0 MPS = 0 KT) I am fairly sure that the result is intended to show the parsed unit.

KATL 270200Z 00000MPS

Result:

"wind": {
  "speed": 0,
  "direction": "N",
  "degrees": 0,
  "unit": "KT"
}

The culprit seems to be this regex in the WindCommand, since its parsed result looks like this:

image

I don't think that this has any major impact since it is not a super common scenario, but I thought I should mention it at least!

[Spelling Error] local/en.ts

In local/en.ts there are two instances of the word mesured (line 161 for Remark.Ceiling.Second.Location & line 230 for Remark.Second.Location.Visibility) this should be corrected to measured.

Additionally, within the Remarks, no support for AO2A (automated station with a precipitation discriminator (Augmented))

Cannot parse where visibility is directional

According to this page:

Visibility can also be listed per direction. 1500SW 2000NE means the visibility to the southwest is 1500 meters and 2000 meters to the northeast.

It seems that this isn't accounted for by this parser... as per this example it gives the following error:

UnexpectedParseError: container.visibility not instantiated

Weather conditions are not showing up in remarks

I've tried demo example on website:

weatherConditions: [
    {
        "intensity": "VC",
        "descriptive": "TS",
        "phenomenons": []
    },
    {
        "phenomenons": [
            "SN"
        ]
    },
    {
        "descriptive": "FZ",
        "phenomenons": [
            "FG"
        ]
    }
]
remarks: [
    {
        "type": "AO2",
        "description": "automated station with a precipitation discriminator",
        "raw": "AO2"
    },
    {
        "type": "PrecipitationBeg",
        "description": "thunderstorm beginning at :40",
        "raw": "TSB40",
        "phenomenon": "TS",
        "startMin": 40
    },
    {
        "type": "SeaLevelPressure",
        "description": "sea level pressure of 1017.6 HPa",
        "raw": "SLP176",
        "pressure": 1017.6
    },
    {
        "type": "HourlyPrecipitationAmount",
        "description": "2/100 of an inch of precipitation fell in the last hour",
        "raw": "P0002",
        "amount": 0.02
    },
    {
        "type": "HourlyTemperatureDewPoint",
        "description": "hourly temperature of -1.7°C and dew point of -1.7°C",
        "raw": "T10171017",
        "temperature": -1.7,
        "dewPoint": -1.7
    }
]

Unfortunatelly remarks don't really contain any weather information, while there are some mappings in en locale to describe that. Is this a bug & is it fixable?

METARs with VCBL* codes are not parsing correctly

Discussed in #47

Originally posted by ManojAgrawal November 22, 2022
Hello,
METARs with VCBLDU, VCBLSA and VCBLSN such as
"CYVM 282100Z 36028G36KT 1SM -SN DRSN VCBLSN OVC008 M03/M04 A2935 RMK SN2ST8 LAST STFFD OBS/NXT 291200UTC SLP940"
are not being parsed correctly as VCBLSN is not being reported back. Is there a fix for this?

thanks,
Manoj

Parser crashes when COR is set

Hi,
thank you for this great module.
I found a small "error" because the parser can only handle the AMD flag.
COR, NIL, AUTO and CNL should be added
See example:
TAF COR EDDS 201148Z 2012/2112 31010KT CAVOK TEMPO…B BECMG 2018/2021 33004KT BECMG 2106/2109 07005KT

Suggestion: consistent treatment of units

Is there a way to ask the parser not to convert altitude from InHg to hPa? I'm not particularly fussed about this, but seeing as though there is no unit conversion for other values (like wind speed KT, temperature °C and visibility SM) it would seem more consistent if there were also no unit conversion for altitude... i.e. we either do all the conversion at our side, or all on your side, not a mix of the two.

In fact, I understand that the altitude value in the raw METAR can either be prefixed with A (meaning InHg) or Q (meaning hPa)... so I assume that in both cases the library returns a value in hPa? Would it be more consistent to specify a value and unit for altitude, like is the case for wind speed?

image

SPECI not parsed correctly

Seen recently:

SPECI KMSN 221627Z 01004KT 10SM -SN OVC008 M04/M06 A2990 = 

Causes station to be SPECI. Should be handled properly.

Suggestion: station info

It would be neat if the parser could decode e.g. CYVR to Vancouver International Airport (i.e. provide details of the station), which it doesn't seem to at present... need to get that from other information in the raw feed (if available), or look it up separately.

Error creating Trend

Hi,

In the following TAF, the last TEMPO definition ist appended to the previous PROB30 TEMPO entry.

TAF EDDS 281100Z 2812/2912 04008KT 9999 BKN035 BECMG 2818/2821 33005KT PROB30 TEMPO 2818/2824 4000 TSRA BKN025CB TEMPO 290

I think the error is in the following function:

function joinProbIfNeeded(ls: string[]): string[] { for (let i = 0; i < ls.length; i++) { if (/PROB\d{2}/.test(ls[i]) && /TEMPO/.test(ls[i + 1])) { ls.splice(i, 2, ${ls[i]} ${ls[i + 1]}); i--; } }

i-- should be deleted to avoid that the PROB tag is handled twice (i did not check for other side effects).

Cannot parse TAF with station beginning "FM"

FM is always assumed to be a from-group in the tokenization process. The end result of this is that TAFs (and probably METARs, though I haven't checked yet) beginning with "FM" cannot be parsed.

This can be fixed by updating the pattern in extractLinesTokens(tafCode):

From

.replace(/\s(?=PROB\d{2}\s(?=TEMPO|INTER)|TEMPO|INTER|BECMG|FM|PROB)/g, "\n")

To

.replace(/\s(?=PROB\d{2}\s(?=TEMPO|INTER)|TEMPO|INTER|BECMG|FM(?![A-Z])|PROB)/g, "\n")

I'm just digging into this now, I'm looking into a few other failures. Want a pull request?

support node

I'am use it in node for api server, but can't use es module.
can out UMD for support both node and browser ?

Error [ERR_REQUIRE_ESM]: require() of ES Module D:\**\node_modules\.pnpm\[email protected]\node_modules\metar-taf-parser\metar-taf-parser.js from D:\**\src\app.ts not supported.

Invalid end date in parsed TAF forecast

First of all, thanks a lot for this excellent library! It works really well.
I stumbled upon this issue (?) though and would like to know if it is intended and/or known.

When parsing the following TAF on the demo page:

TAF ESGG 260830Z 2609/2709 02009KT 3000 BR BKN003
BECMG 2609/2611 9999 NSW FEW015

I get the following result. Shouldn't the start/end dates of the root object match up with the ones in the first forecast?
Notice how in the first forecast entry, the start and end dates are identical.

metar-taf-parser 01-27

The issue goes away if I remove the second line of the provided TAF:

metar-taf-parser 01-27

It seems like I can safely fall back to the "root" start/end dates for the first entry, just wanted to know if this was intentional!

Suggestion: include raw text for each parameter

First, thanks for this great library... really appreciated :-)

I find it useful to be able to break down a raw METAR string into pieces, and re-construct a "custom" string using only those pieces considered to be of interest, e.g. station, time, temperature, dewpoint, and wind speed/direction. Plus... with the option to decode (or not) each piece.

Take this example:

CYVR 301300Z 09006KT 15SM FEW055 FEW120 M04/M06 A3049 RMK SC1AC1 SC TR SLP327

My non-decoded custom string would be:

CYVR 301300Z M04/M06 09006KT

While my decoded custom string would be:

Vancouver International Airport 13:00 -4°C -6°C 11.1 km/h 90°

So it would be handy if the raw text used as the basis for each decoded piece were also included in the response data, e.g.

"wind": { ...., "raw": "09006KT" }

I note that this raw field is already added to each array element in remarks, so could be added likewise for each array element of clouds and also for each other grouping like wind, visibility etc?

It would be more tricky (for backward compatibility) for temperature, dewpoint, altimeter because these are just straight values and aren't already JSON objects to which a new raw element could be added.

Station being incorrectly parsed

Hi there,

While playing around with this library I found a problem in parsing some metars, When the raw metar is preceeded by 'AUTO' or 'AMD' The station is being reported as 'AUTO' or 'AMD'. I believe this to be incorrect behaviour.

Example outputs:
{ "station": "AUTO", "message": "AUTO LSZL 061950Z 10002KT 9999NDV NCD 01/M00 Q1015 RMK=", "remarks": [], "clouds": [], "weatherConditions": [], "trends": [], "runwaysInfo": [], "wind": { "speed": 2, "direction": "E", "degrees": 100, "unit": "KT" }, "visibility": { "indicator": "P", "value": 9999, "unit": "m", "ndv": true }, "temperature": 1, "dewPoint": 0, "altimeter": 1015, "remark": "" },

{ "station": "AMD", "message": "AMD LSZL 070643Z 0706/0715 VRB01KT CAVOK PROB40 TEMPO 0706/0708 4000 BCFG FEW002 PROB30 TEMPO 0706/0708 1200 PRFG BKN002=", "remarks": [], "clouds": [], "weatherConditions": [], "trends": [ { "type": "TEMPO", "weatherConditions": [ { "descriptive": "BC", "phenomenons": [ "FG" ] } ], "clouds": [ { "quantity": "FEW", "height": 200 } ], "times": [], "remarks": [], "raw": "TEMPO 0706/0708 4000 BCFG FEW002 PROB30", "visibility": { "value": 4000, "unit": "m" } }, { "type": "TEMPO", "weatherConditions": [ { "descriptive": "PR", "phenomenons": [ "FG" ] } ], "clouds": [ { "quantity": "BKN", "height": 200 } ], "times": [], "remarks": [], "raw": "TEMPO 0706/0708 1200 PRFG BKN002", "visibility": { "value": 1200, "unit": "m" } } ], "runwaysInfo": [], "wind": { "speed": 1, "direction": "VRB", "unit": "KT" }, "cavok": true, "visibility": { "indicator": "P", "value": 9999, "unit": "m" } },

Visibility incorrectly parsed as wind speed, visibility not included in output

Raw METAR:

VIDP 270200Z 00000KT 0050 R27/0450 R29L/0650 R29R/0150 FG VV/// 12/11 Q1021 NOSIG

metar-taf-parser wind output (link to full output)

	"wind": {
	  "speed": 50,
	  "direction": "N",
	  "degrees": 0, 
	  "unit": "KT"
	}

It looks like the parser incorrectly includes the visibility part of the METAR in the wind. Additionally, it also looks like the visibility is left out entirely, as it is not mentioned in the parsed output.


python-metar-taf-parser wind output:

Wind: Speed : 0 KT Direction : North | 0°

Looks like it correctly identified that the visibility was not part of the wind. However, there is also no indication that it successfully parsed the visibility, as the output does not include any visibility readings.


This project seems to be able to parse it correctly: https://e6bx.com/metar-decoder/

I'll try to dig in and see if I can help find a solution to this!

Cannot parse "PART x OF y" in NOAA TAF cycle entries

The NOAA TAF cycles contain entries which begin with PART x OF y, for example, PART 1 OF 2 TAF KXYZ.... There is no provision for parsing or skipping this. If you'd like, I could take a stab at adding support for it. If it is supported, you'd probably want to note the partial in the parsed TAF output.

I've not written any code for this yet since you could go either way on it. It's not technically part of the TAF, so perhaps you don't want to parse it at all, but it does appear in the cycle files, so anybody using the library to parse cycle files would need to handle it in a preprocess step.

Cannot parse "R29/CLRD70" or "R12/210160"

message type is "metar", can not read "R29/CLRD70" or "R12/210160"

example
METAR UIAA 200300Z 31008G15MPS 9999 NSC 24/M02 Q1004 R29/CLRD70 NOSIG RMK QFE695/0926=
METAR UIBB 200300Z 11005MPS 360V160 9999 NSC 16/M00 Q1007 R12/210160 NOSIG RMK QFE712/0950=

can you help me, thanks!

TimestampOutOfBoundsError: Provided timestamp is outside the report validity period

Hello I get the problem when I try to run this library in expressjs

this is my code

import Express from "express";
import { eachHourOfInterval } from "date-fns";
import {
  parseTAFAsForecast,
  getCompositeForecastForDate,
} from "metar-taf-parser";

const app = new Express();

app.get("/", (req, res) => {
  const rawTAFString =
    "TAF WIII 040500Z 0406/0512 02010KT 7000 FEW021 BECMG 0414/0416 16005KT 5000 HZ BECMG 0419/0421 3000 BR BECMG 0501/0503 07010KT 7000 NSW=";
  const issued = new Date();
  const report = parseTAFAsForecast(rawTAFString, { issued });

  const forecastPerHour = eachHourOfInterval({
    start: report.start,
    end: report.end,
  }).map((hour) => ({
    hour,
    ...getCompositeForecastForDate(hour, report),
  }));

  res.send(report);
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

And I got this error

TimestampOutOfBoundsError: Provided timestamp is outside the report validity period
    at getCompositeForecastForDate (file:///D:/Script/express/taf-parser/node_modules/metar-taf-parser/metar-taf-parser.js:3074:15)
    at file:///D:/Script/express/taf-parser/app.js:21:8
    at Array.map (<anonymous>)
    at file:///D:/Script/express/taf-parser/app.js:19:6
    at Layer.handle [as handle_request] (D:\Script\express\taf-parser\node_modules\express\lib\router\layer.js:95:5)
    at next (D:\Script\express\taf-parser\node_modules\express\lib\router\route.js:144:13)
    at Route.dispatch (D:\Script\express\taf-parser\node_modules\express\lib\router\route.js:114:3)
    at Layer.handle [as handle_request] (D:\Script\express\taf-parser\node_modules\express\lib\router\layer.js:95:5)
    at D:\Script\express\taf-parser\node_modules\express\lib\router\index.js:284:15
    at Function.process_params (D:\Script\express\taf-parser\node_modules\express\lib\router\index.js:346:12)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.