veaf-mission-creation-tools
Version:
A set of tools that help set up a and run dynamic missions
441 lines (369 loc) • 12.9 kB
JavaScript
(function() {
// http://www.met.tamu.edu/class/metar/metar-pg10-sky.html
// https://ww8.fltplan.com/AreaForecast/abbreviations.htm
// http://en.wikipedia.org/wiki/METAR
// http://www.unc.edu/~haines/metar.html
var re = /(R\d{2})([L|R|C])?(\/)([P|M])?(\d+)(?:([V])([P|M])?(\d+))?([N|U|D])?(FT)?/g;
function RVR(rvrString) {
this.result = {};
this.rvrString = rvrString;
this.parse();
}
RVR.prototype.parse = function() {
var matches;
while ((matches = re.exec(this.rvrString)) != null) {
if (matches.index === re.lastIndex) {
re.lastIndex++;
}
this.result = {
runway: matches[1],
direction: matches[2],
seperator: matches[3],
minIndicator: matches[4],
minValue: matches[5],
variableIndicator: matches[6],
maxIndicator: matches[7],
maxValue: matches[8],
trend: matches[9],
unitsOfMeasure: matches[10],
};
}
};
function parseRVR(rvrString) {
var m = new RVR(rvrString);
m.parse();
return m.result;
}
var TYPES = ["METAR", "SPECI"];
var CLOUDS = {
NCD: "no clouds",
SKC: "sky clear",
CLR: "no clouds under 12,000 ft",
NSC: "no significant",
FEW: "few",
SCT: "scattered",
BKN: "broken",
OVC: "overcast",
VV: "vertical visibility",
};
var WEATHER = {
// Intensity
"-": "light intensity",
"+": "heavy intensity",
VC: "in the vicinity",
// Descriptor
MI: "shallow",
PR: "partial",
BC: "patches",
DR: "low drifting",
BL: "blowing",
SH: "showers",
TS: "thunderstorm",
FZ: "freezing",
// Precipitation
RA: "rain",
DZ: "drizzle",
SN: "snow",
SG: "snow grains",
IC: "ice crystals",
PL: "ice pellets",
GR: "hail",
GS: "small hail",
UP: "unknown precipitation",
// Obscuration
FG: "fog",
VA: "volcanic ash",
BR: "mist",
HZ: "haze",
DU: "widespread dust",
FU: "smoke",
SA: "sand",
PY: "spray",
// Other
SQ: "squall",
PO: "dust or sand whirls",
DS: "duststorm",
SS: "sandstorm",
FC: "funnel cloud",
};
var RECENT_WEATHER = {
REBLSN: "Moderate/heavy blowing snow (visibility significantly reduced)reduced",
REDS: "Dust Storm",
REFC: "Funnel Cloud",
REFZDZ: "Freezing Drizzle",
REFZRA: "Freezing Rain",
REGP: "Moderate/heavy snow pellets",
REGR: "Moderate/heavy hail",
REGS: "Moderate/heavy small hail",
REIC: "Moderate/heavy ice crystals",
REPL: "Moderate/heavy ice pellets",
RERA: "Moderate/heavy rain",
RESG: "Moderate/heavy snow grains",
RESHGR: "Moderate/heavy hail showers",
RESHGS: "Moderate/heavy small hail showers",
// RESHGS: "Moderate/heavy snow pellet showers", // dual meaning?
RESHPL: "Moderate/heavy ice pellet showers",
RESHRA: "Moderate/heavy rain showers",
RESHSN: "Moderate/heavy snow showers",
RESN: "Moderate/heavy snow",
RESS: "Sandstorm",
RETS: "Thunderstorm",
REUP: "Unidentified precipitation (AUTO obs. only)",
REVA: "Volcanic Ash",
};
function parseAbbreviation(s, map) {
var abbreviation, meaning, length = 3;
if (!s) return;
if (s.slice(0,1) == "+") {
} else if (s.slice(0,1) == "-") {
} else if (s.slice(0,2) == "VC") {
} else {
}
while (length && !meaning) {
abbreviation = s.slice(0, length);
meaning = map[abbreviation];
length--;
}
if (meaning) {
return {
abbreviation: abbreviation,
meaning: meaning,
};
}
}
function asInt(s) {
return parseInt(s, 10);
}
function METAR(metarString) {
this.fields = metarString
.split(" ")
.map(function(f) {
return f.trim();
})
.filter(function(f) {
return !!f;
});
this.i = -1;
this.current = null;
this.result = {};
}
METAR.prototype.next = function() {
this.i++;
return (this.current = this.fields[this.i]);
};
METAR.prototype.peek = function() {
return this.fields[this.i + 1];
};
METAR.prototype.parseType = function() {
var token = this.peek();
if (TYPES.indexOf(token) !== -1) {
this.next();
this.result.type = this.current;
} else {
this.result.type = "METAR";
}
};
METAR.prototype.parseStation = function() {
this.next();
this.result.station = this.current;
};
METAR.prototype.parseDate = function() {
this.next();
var d = new Date();
d.setUTCDate(asInt(this.current.slice(0, 2)));
d.setUTCHours(asInt(this.current.slice(2, 4)));
d.setUTCMinutes(asInt(this.current.slice(4, 6)));
this.result.time = d;
};
METAR.prototype.parseAuto = function() {
this.result.auto = this.peek() === "AUTO";
if (this.result.auto) this.next();
};
METAR.prototype.parseCorrection = function() {
if (this.result.correction) {
return;
}
var token = this.peek();
this.result.correction = false;
if (token.lastIndexOf("CC", 0) == 0) {
this.result.correction = token.substr(2, 1);
this.next();
}
if (token.lastIndexOf("COR", 0) == 0) {
this.result.correction = true;
this.next();
}
};
var variableWind = /^([0-9]{3})V([0-9]{3})$/;
METAR.prototype.parseWind = function() {
this.result.wind = {
speed: null,
gust: null,
direction: null,
variation: null,
};
if (this.peek().match(/^[0-9]{1,4}(SM?)/)) {
return;
}
this.next();
var direction = this.current.slice(0, 3);
if (direction === "VRB") {
this.result.wind.direction = "VRB";
this.result.wind.variation = true;
} else {
this.result.wind.direction = asInt(direction);
}
var gust = this.current.slice(5, 8);
if (gust[0] === "G") {
this.result.wind.gust = asInt(gust.slice(1));
}
this.result.wind.speed = asInt(this.current.slice(3, 5));
var unitMatch;
if ((unitMatch = this.current.match(/KT|MPS|KPH|SM$/))) {
this.result.wind.unit = unitMatch[0];
} else {
throw new Error("Bad wind unit: " + this.current);
}
var varMatch;
if ((varMatch = this.peek().match(variableWind))) {
this.next();
this.result.wind.variation = {
min: asInt(varMatch[1]),
max: asInt(varMatch[2]),
};
}
};
METAR.prototype.parseCavok = function() {
this.result.cavok = this.peek() === "CAVOK";
if (this.result.cavok) this.next();
};
METAR.prototype.parseVisibility = function() {
var re = /^([0-9]+)([A-Z]{1,2})/g;
this.result.visibility = null;
this.result.visibilityVariation = null;
this.result.visibilityVariationDirection = null;
this.result.visibility = null;
if (this.result.cavok) return;
if (this.peek() === "////") {
this.next();
return;
}
if (this.peek().match(/^[0-9]+(SM)?/)) {
this.next();
this.result.visibility = asInt(this.current.slice(0, 4));
}
// Look for a directional variation report
if (this.peek().match(/^[0-9]+[N|E|S|W|NW|NE|SW|SE]/)) {
this.next();
var matches;
while ((matches = re.exec(this.current)) != null) {
if (matches.index === re.lastIndex) {
re.lastIndex++;
}
this.result.visibilityVariation = matches[1];
this.result.visibilityVariationDirection = matches[2];
}
}
};
METAR.prototype.parseRunwayVisibility = function() {
if (this.result.cavok) return;
if (this.peek().match(/^R[0-9]+/)) {
this.next();
this.result.rvr = parseRVR(this.current);
// TODO: peek is more than one RVR in METAR and parse
}
};
function parseWeatherAbbrv(s, res) {
var weather = parseAbbreviation(s, WEATHER);
if (weather) {
res = res || [];
res.push(weather);
return parseWeatherAbbrv(s.slice(weather.abbreviation.length), res);
}
return res;
}
METAR.prototype.parseWeather = function() {
if (this.result.weather === undefined) this.result.weather = null;
if (this.result.cavok) return;
var weather = parseWeatherAbbrv(this.peek());
if (!weather) return;
if (!this.result.weather) this.result.weather = [];
this.result.weather = this.result.weather.concat(weather);
this.next();
this.parseWeather();
};
METAR.prototype.parseClouds = function() {
if (!this.result.clouds) this.result.clouds = null;
if (this.result.cavok) return;
var cloud = parseAbbreviation(this.peek(), CLOUDS);
if (!cloud) return;
this.next();
cloud.altitude = asInt(this.current.slice(cloud.abbreviation.length)) *
100 || null;
cloud.cumulonimbus = /CB$/.test(this.current);
this.result.clouds = this.result.clouds || [];
this.result.clouds.push(cloud);
this.parseClouds();
};
METAR.prototype.parseTempDewpoint = function() {
this.next();
var replaced = this.current.replace(/M/g, "-");
var a = replaced.split("/");
if (2 !== a.length) return; // expecting XX/XX
this.result.temperature = asInt(a[0]);
this.result.dewpoint = asInt(a[1]);
};
METAR.prototype.parseAltimeter = function() {
var temp;
this.next();
if (this.current === undefined || this.current === null) return;
// inches of mercury if AXXXX
if (this.current.length === 5 && "A" === this.current[0]) {
temp = this.current.substr(1, 2);
temp += ".";
temp += this.current.substr(3, 5);
this.result.altimeterInHg = parseFloat(temp, 10);
} else if (this.current.length && "Q" === this.current[0]) {
temp = this.current.substr(1);
this.result.altimeterInHpa = parseInt(temp, 10);
}
};
METAR.prototype.parseRecentSignificantWeather = function() {
this.next();
if (this.current === undefined || this.current === null) return;
if (RECENT_WEATHER[this.current]) {
this.result.recentSignificantWeather = this.current;
this.result.recentSignificantWeatherDescription = RECENT_WEATHER[
this.current
];
}
};
METAR.prototype.parse = function() {
this.parseType();
this.parseCorrection();
this.parseStation();
this.parseDate();
this.parseAuto();
this.parseCorrection(); // Second possible position for the correction
this.parseWind();
this.parseCavok();
this.parseVisibility();
this.parseRunwayVisibility();
this.parseWeather();
this.parseClouds();
this.parseTempDewpoint();
this.parseAltimeter();
this.parseRecentSignificantWeather();
};
function parseMETAR(metarString) {
var m = new METAR(metarString);
m.parse();
return m.result;
}
parseMETAR.parseRVR = parseRVR;
if (typeof module !== "undefined") {
module.exports = parseMETAR;
} else if (typeof window !== "undefined") {
window.parseMETAR = parseMETAR;
}
})();