UNPKG

metar-plot

Version:
292 lines (286 loc) 9.11 kB
import { Weather, WEATHER } from "./parts/Weather" import { Cloud , CLOUDS} from "./parts/Cloud" import { Wind } from "./parts/Wind" import { RVR } from "./parts/RVR" import { Console } from "console"; //Meassage types const TYPES = ["METAR", "SPECI"]; //Metar Object export class METAR { //Type of message public type?: string //Designates that this metar is a correction public correction?: boolean; //Station ID public station: string; /*Time of report *METARs are only accurate to the day, hour, minute, and sec *This will assume the metar was produced today*/ public time: Date; //Designated if this metar was from an automated station public auto?: boolean //Wind speed, direction and unit public wind: Wind = new Wind(); //Designated if ceiling & visablility are OK public cavok?: boolean; //Visability in meters public visibility?: number; public visibilityVariation?: number; public visibilityVariationDirection?: number; //List of weather conditions reported public weather: Array<Weather> = new Array<Weather>(); //List of Cloud observations public clouds: Array<Cloud> = new Array<Weather>(); //Tempuature (ºC) public temperature?: number; //Dew Point (ºC) public dewpoint?: number; //Altimeter reading (pressure) in inHg public altimeter?: number; public recentSignificantWeather?: string; public recentSignificantWeatherDescription?: string; public rvr?: RVR; /** * Extracted Metar data in a human readable format. * @param metarString raw metar string if provided station and time will be ignored and replaced with the content in the raw METAR * @param station staion name for instance creation * @param time time for instance creation */ constructor(metarString?: string, station?: string, time?: Date) { this.station = station ?? "----" this.time = time ?? new Date() if (metarString != null) { parseMetar(metarString, this) } } } /** * Parses a raw metar and binds or creates a METAR object * @param metarString Raw METAR string * @param ref Reference to a METAR object. This objects contents will be shallow replaced with the Raw metars values. * Meaning values will be updated or added but not removed. * @returns */ export function parseMetar(metarString: string, ref?: METAR): METAR { let station = parseStation(metarString) let time = parseDate(metarString); if (ref != null) { ref.station = station ref.time = time } else { ref = new METAR(undefined, station, time) } //Parse Auto ref.auto = parseAuto(metarString) //Parse Wind ref.wind = parseWind(metarString); //Parse CAVOK ref.cavok = parseCavok(metarString) //Parse Visablility ref.visibility = parseVisibility(metarString) //Parse Runway VIS //TODO //Parse Weather ref.weather = parseWeather(metarString); //Parse Clouds ref.clouds = parseClouds(metarString); //Parse Temp Point Internations let temps_int = parseTempInternation(metarString) if (temps_int != null) { ref.temperature = temps_int[0]; ref.dewpoint = temps_int[1]; } //Parse Temp North american Will overwirte international since it is more precise let temps_ne = parseTempNA(metarString) if (temps_ne != null) { ref.temperature = temps_ne[0]; ref.dewpoint = temps_ne[1]; } //Parse Altimeter ref.altimeter = parseAltimeter(metarString) return ref; } /** * Parses the station name form the metar * @param metar raw metar * @returns */ export function parseStation(metar: string): string { let re = /^(METAR\s)?([A-Z]{1,4})\s/g let matches = re.exec(metar) if (matches != null) { return matches[2] } else { throw new Error("Station could not be found invalid metar") } } /** * Parse Date object from metar. * NOTE: Raw metar data does not contain month or year data. So this function assumes this metar was created in the current month and current year * @param metar raw metar * @returns */ export function parseDate(metar: string): Date { let re = /([\d]{2})([\d]{2})([\d]{2})Z/g let matches = re.exec(metar) if (matches != null) { var d = new Date(); d.setUTCDate(parseInt(matches[1])); d.setUTCHours(parseInt(matches[2])); d.setUTCMinutes(parseInt(matches[3])); d.setUTCSeconds(0) d.setUTCMilliseconds(0) return d } else { throw new Error("Failed to parse Date") } } /** * Parses for CAVOK (Ceiling and visabiliy OK) * @param metar raw metar * @returns */ export function parseCavok(metar: string): boolean { let re = /\sCAVOK\s/g return metar.match(re) != null ? true : false } /** * Parses for Automation * @param metar raw metar * @returns */ export function parseAuto(metar: string): boolean { let re = /\s(AUTO)?(AO1)?(AO2)?\s/g return metar.match(re) != null ? true : false } /** * Parse international temp dewp point format. * @param metar raw metar * @returns */ export function parseTempInternation(metar: string): [number, number] | undefined { let re = /\s(M)?(\d{2})\/(M)?(\d{2})\s/g let matches = re.exec(metar) if (matches != null) { let temp = parseInt(matches[2]) * (matches[1] == null ? 1 : -1) let dew_point = parseInt(matches[4]) * (matches[3] == null ? 1 : -1) return [temp, dew_point] } } /** * Parse North American temp dew point format * @param metar raw metar * @returns */ export function parseTempNA(metar: string): [number, number] | undefined { let re = /(T)(\d{1})(\d{2})(\d{1})(\d{1})(\d{2})(\d{1})/g let matches = re.exec(metar) if (matches != null) { let temp = parseFloat(matches[3] + "." + matches[4]) * (matches[2] === "0" ? 1 : -1) let dew_point = parseFloat(matches[6] + "." + matches[7]) * (matches[5] === "0" ? 1 : -1) return [temp, dew_point] } } /** * Parse Weather items * @param metar raw metar * @returns */ export function parseWeather(metar: string): Array<Weather> { let obs_keys = Object.keys(WEATHER).join('|').replace(/\+/g, "\\+") let re = new RegExp(`\\s?(${obs_keys})\\s`, 'g') let matches = metar.match(re) if (matches != null) { return matches.map(match => { console.log(match) let key = match.trim() return { abbreviation: key, meaning: WEATHER[key].text } }) } else { return new Array<Weather>() } } /** * Parse visability * @param metar raw metar * @returns */ export function parseVisibility(metar: string): number | undefined { var re = /\s([0-9]{1,2})?\s?([0-9]{1}\/[0-9]{1})?(SM)\s|\s([0-9]{1,4})\s/g; if (metar.match(re)) { let vis_parts = re.exec(metar) if (vis_parts != null) { let meters = vis_parts[4] let miles = vis_parts[1]; let frac_miles = vis_parts[2] //Metric case ex: 1000, 9999 if(meters != null){ return parseInt(meters) } //whole miles case ex: 1SM 10SM else if(frac_miles != null){ let total = 0.0; if(miles != null){ total += parseFloat(miles) } total += parseFloat(eval(frac_miles)) return total * 1609.34 } //factional miles case "1 1/2SM" "1/4SM" else{ return parseInt(miles) * 1609.34 } } } return undefined } /** * Parse cloud coverages * @param metarString raw metar * @returns */ export function parseClouds(metarString: string): Cloud[] { let re = /(NCD|SKC|CLR|NSC|FEW|SCT|BKN|OVC|VV)(\d{3})/g let clouds = new Array<Cloud>() let matches while((matches = re.exec(metarString)) != null){ let cloud: Cloud = { abbreviation: matches[1], meaning: CLOUDS[matches[1]]?.text, altitude: parseInt(matches[2]) * 100 } clouds.push(cloud) } return clouds } /** * Parse wind data * @param metar raw metar * @returns */ export function parseWind(metar: string): Wind { let wind: Wind = new Wind() let re = /\s(\d{3})(\d{2})(G)?(\d{2})?(KT|MPS)\s/g let matches = re.exec(metar) if (matches != null) { wind.direction = parseInt(matches[1]); wind.speed = parseInt(matches[2]); wind.unit = matches[5] } return wind } export function parseAltimeter(metar: string): number | undefined{ let re = /(A|Q)(\d{2})(\d{2})/g let matches = re.exec(metar) if(matches != null){ if(matches[1] === "Q"){ let pressure = parseFloat(matches[2]+matches[3]) return parseFloat((pressure * 0.029529).toFixed(2)) }else{ return parseFloat(matches[2]+"."+matches[3]) } } }