From afef988a201e17898911406545228272021d14d1 Mon Sep 17 00:00:00 2001 From: Max Larsson Kuhla Date: Fri, 25 Oct 2024 10:56:39 +0200 Subject: [PATCH] ATIS added conditions and blueflash #61 - Added support for extracting wx conditions (precipitation, obscuration etc) - Added support for blueflash (toggleable in settings) - Toggleable local ATIS input (in settings) --- frontend/package-lock.json | 14 ++- frontend/package.json | 1 + frontend/src/components/ATIS.vue | 148 +++++++++++++++++++++++-------- frontend/src/stores/settings.ts | 3 + frontend/src/views/Settings.vue | 3 +- 5 files changed, 127 insertions(+), 42 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6f4941c..918af35 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "diff": "^5.2.0", "moment": "^2.30.1", "ol": "^9.2.2", + "papaparse": "^5.4.1", "pinia": "^2.1.7", "uuid": "^9.0.1", "vue": "^3.4.21", @@ -5832,6 +5833,12 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7236,10 +7243,11 @@ } }, "node_modules/vite": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", - "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "devOptional": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/frontend/package.json b/frontend/package.json index 8b9eb76..07d37a2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "diff": "^5.2.0", "moment": "^2.30.1", "ol": "^9.2.2", + "papaparse": "^5.4.1", "pinia": "^2.1.7", "uuid": "^9.0.1", "vue": "^3.4.21", diff --git a/frontend/src/components/ATIS.vue b/frontend/src/components/ATIS.vue index dbf72df..c056d06 100644 --- a/frontend/src/components/ATIS.vue +++ b/frontend/src/components/ATIS.vue @@ -3,7 +3,7 @@
@@ -11,6 +11,7 @@ {{ time }}
{ return `VIS ${visData}` } -const extractTemperature = (text: string) => { - const match = text.match(/T(?:M)?(-?\d+)/) - if (match) { - const temp = match[1].padStart(2, '0') - return text.includes('TM') ? `TMS${temp}` : `T${temp}` +const extractWeatherConditions = (text: string) => { + const visToCloudRegex = /VIS.*?(?=CLD)/s; + const match = text.match(visToCloudRegex); + + if (!match) return ""; + + const conditionText = match[0]; + const conditionRegex = /(RE)?(\+|-|VC)?(MI|BC|PR|DR|BL|SH|TS|FZ)?(DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS){1,3}/g; + const conditions = []; + + let conditionMatch; + while ((conditionMatch = conditionRegex.exec(conditionText)) !== null) { + let condition = ''; + + // Recent + if (conditionMatch[1]) { + condition += conditionMatch[1]; + } + + // Intensity or proximity + if (conditionMatch[2]) { + condition += conditionMatch[2]; + } + + // Descriptor + if (conditionMatch[3]) { + condition += conditionMatch[3]; + } + + // Precipitation, Obscuration, or Other (up to 3) + if (conditionMatch[4]) { + condition += conditionMatch[4]; + } + + conditions.push(condition); } - return '' -} -const extractDewpoint = (text: string) => { - const match = text.match(/DP(?:M)?(-?\d+)/) - if (match) { - const dewpoint = match[1].padStart(2, '0') - return text.includes('DPM') ? `DPMS${dewpoint}` : `DP${dewpoint}` + if (conditionText.includes('NSW')) { + conditions.push('NSW'); } - return '' -} + return conditions.join(' '); +} const extractClouds = (text: string) => { const cloudRegex = /(FEW|SCT|BKN|OVC)(\d{3})(CB|TCU)?/g; const vvRegex = /VV(\d{3}|\/{3})/; @@ -206,7 +232,7 @@ const extractClouds = (text: string) => { } if (vertVisibility) { - return vertVisibility; + return `CLD ${vertVisibility}`; } if (specialCloud) { @@ -221,6 +247,22 @@ const extractClouds = (text: string) => { return ''; } +const extractTemperature = (text: string) => { + const match = text.match(/T(\d{2})\//) + if (match) { + return `T${match[1]}` + } + return '' +} + +const extractDewpoint = (text: string) => { + const match = text.match(/\/DP(\d{2})/) + if (match) { + return `DP${match[1]}` + } + return '' +} + const formatAtisText = (text: string) => { if (!text) return "" @@ -245,8 +287,7 @@ const formatAtisText = (text: string) => { const date = extractInfo(/(\d{6}Z)/) const wind = extractWind(text) const vis = extractVisibility(text) - //TODO Precipitation/Weather - const precipitation = extractInfo(/(FBL|MOD)\s+(RA|DZ)/) + const conditions = extractWeatherConditions(text) const clouds = extractClouds(text) const temperature = extractTemperature(text) const dewpoint = extractDewpoint(text) @@ -265,7 +306,7 @@ WIND ${wind} ${vis} -${precipitation} +${conditions} ${clouds} ${temperature.padEnd(9)}${dewpoint} @@ -276,36 +317,64 @@ ${other} } const changed = ref(false) -let changeTimeout: any = null +const changedLong = ref(false) +let changeTimeouts: any[] = [] function click() { - if (changeTimeout) clearTimeout(changeTimeout) - changed.value = false + for (const timeout of changeTimeouts) clearTimeout(timeout) + changeTimeouts.splice(0) + changed.value = changedLong.value = false } -function updateLocalAtis() { - if (localAtisInput.value) { - localAtis.value = { - text: localAtisInput.value.split("\n"), - last_updated: moment().unix() +const firstUpdate = ref(true) + +watch([ + time, + () => atis.value?.text, // This covers the entire ATIS text, including atisLetter, atisCode, runway, etc. + () => localAtis.value?.text +], (newValues, oldValues) => { + if (firstUpdate.value) { + firstUpdate.value = false + return + } + changed.value = false + if (!settings.metreportFlash) return + for (let i = 0; i < newValues.length; i++) { + if (oldValues[i] && oldValues[i].length > 0 && JSON.stringify(newValues[i]) !== JSON.stringify(oldValues[i])) { + changed.value = true + break } - } else { - localAtis.value = null } -} + if (changed.value) { + changeTimeouts.splice(0) + changeTimeouts.push(setTimeout(() => (changed.value = false), 1000)) + changeTimeouts.push(setTimeout(() => (changed.value = true), 2000)) + changeTimeouts.push( + setTimeout(() => { + changed.value = false + changedLong.value = true + }, 3000) + ) + changeTimeouts.push( + setTimeout(() => { + changed.value = false + changedLong.value = false + }, 63000) + ) + } +}) -watch(atis, (newValue, oldValue) => { - if (oldValue && newValue && !settings.atisFlash) return - changed.value = true - if (changeTimeout) clearTimeout(changeTimeout) - changeTimeout = setTimeout(() => { - changed.value = false - }, 3000) +watch(() => settings.enableLocalAtis, (newValue) => { + if (!newValue) { + localAtisInput.value = "" + localAtis.value = null + } }) onMounted(() => { if (!vatsim.data.general) vatsim.fetchData() }) + - diff --git a/frontend/src/stores/settings.ts b/frontend/src/stores/settings.ts index 7475bd1..838f3cd 100644 --- a/frontend/src/stores/settings.ts +++ b/frontend/src/stores/settings.ts @@ -5,6 +5,7 @@ export const useSettingsStore = defineStore("settings", () => { const windowSnapping = ref(true) const metreportFlash = ref(true) const metsensorFlash = ref(false) + const enableLocalAtis = ref(false) if ("settings" in localStorage) { try { @@ -12,6 +13,7 @@ export const useSettingsStore = defineStore("settings", () => { if ("windowSnapping" in settings) windowSnapping.value = settings.windowSnapping if ("metreportFlash" in settings) metreportFlash.value = settings.metreportFlash if ("metsensorFlash" in settings) metsensorFlash.value = settings.metsensorFlash + if ("enableLocalAtis" in settings) enableLocalAtis.value = settings.enableLocalAtis } catch (e: any) { console.error("Failed to parse settings", e) delete localStorage.settings @@ -30,5 +32,6 @@ export const useSettingsStore = defineStore("settings", () => { windowSnapping, metreportFlash, metsensorFlash, + enableLocalAtis, } }) diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index ac675ab..31adb07 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -8,8 +8,9 @@

Options

- + +