Add basic support for active weather alerts

yikes nobody should stake their life on how well this works, but it
does seem to basically work..
This commit is contained in:
Lance Edgar 2024-06-08 23:16:29 -05:00
parent 7ed314b5bf
commit 0710f2238c
4 changed files with 134 additions and 2 deletions

View file

@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import WeatherView from '../views/WeatherView.vue'
import AlertsView from '../views/AlertsView.vue'
import HourlyView from '../views/HourlyView.vue'
import EditListView from '../views/EditListView.vue'
@ -17,6 +18,11 @@ const router = createRouter({
name: 'weather',
component: WeatherView,
},
{
path: '/alerts',
name: 'alerts',
component: AlertsView,
},
{
path: '/hourly',
name: 'hourly',

View file

@ -10,6 +10,7 @@ const getDefaults = () => {
coordinates,
cityState,
weather: null,
alerts: null,
forecast: null,
radarLatestURL: null,
radarLoopURL: null,
@ -32,14 +33,17 @@ export const useWeatherStore = defineStore('weather', {
this.setForecast(null)
this.radarLatestURL = null
this.radarLoopURL = null
this.alerts = null
},
async getWeather() {
if (!this.weather) {
let url
let response
const url = `https://api.weather.gov/points/${this.coordinates}`
const response = await fetch(url)
url = `https://api.weather.gov/points/${this.coordinates}`
response = await fetch(url)
const weather = await response.json()
if (weather.status == 404) {
throw new Error(`Data not found for ${this.coordinates}`)
@ -57,6 +61,65 @@ export const useWeatherStore = defineStore('weather', {
this.radarLoopURL = `https://radar.weather.gov/ridge/standard/${station}_loop.gif`
this.setWeather(weather)
// fetch zone to get its official id
url = weather.properties.forecastZone
response = await fetch(url)
const zone = await response.json()
// fetch alerts for zone
url = `https://api.weather.gov/alerts/active/zone/${zone.properties.id}`
response = await fetch(url)
const zoneAlerts = await response.json()
// fetch county to get its official id
url = weather.properties.county
response = await fetch(url)
const county = await response.json()
// fetch alerts for county
url = `https://api.weather.gov/alerts/active/zone/${county.properties.id}`
response = await fetch(url)
const countyAlerts = await response.json()
const newAlerts = {}
// use latest timestamp from either zone or county
newAlerts.updated = zoneAlerts.updated
if (countyAlerts.updated > zoneAlerts.updated) {
newAlerts.updated = countyAlerts.updated
}
// collect all alert "features" but de-duplicate them
newAlerts.features = {}
for (let feature of zoneAlerts.features) {
newAlerts.features[feature.properties.id] = feature
}
for (let feature of countyAlerts.features) {
newAlerts.features[feature.properties.id] = feature
}
newAlerts.features = Object.values(newAlerts.features)
// put "likely" before "possible" alerts
newAlerts.features.sort((a, b) => {
if (a.properties.certainty == 'Likely' && b.properties.certainty == 'Possible') {
return -1
}
if (a.properties.certainty == 'Possible' && b.properties.certainty == 'Likely') {
return 1
}
// if (a.properties.certainty == b.properties.certainty) {
// return 0
// }
// TODO: what else should this do?
return 0
})
// we have our final alerts
this.alerts = newAlerts
}
return this.weather

51
src/views/AlertsView.vue Normal file
View file

@ -0,0 +1,51 @@
<script setup>
import { mapStores } from 'pinia'
import { useWeatherStore } from '../stores/weather'
</script>
<script>
export default {
computed: {
...mapStores(useWeatherStore),
},
activated() {
this.weatherStore.getWeather()
},
}
</script>
<template>
<main>
<o-button variant="primary"
size="small"
icon-left="arrow-left"
@click="$router.push('/weather')">
Back
</o-button>
<h5 class="is-size-5">{{ weatherStore.cityState }}</h5>
<h5 class="is-size-5">Alerts</h5>
<br />
<o-collapse v-for="feature in weatherStore.alerts?.features || []"
variant="warning"
:open="false">
<template #trigger>
<o-notification :variant="feature.properties.certainty == 'Likely' ? 'danger' : 'warning'"
icon="plus"
icon-size="small"
style="cursor: pointer;">
{{ feature.properties.event }} ({{ feature.properties.severity }}, {{ feature.properties.certainty }})
</o-notification>
</template>
<div class="notification">
<h5 class="is-size-5 block">{{ feature.properties.headline }}</h5>
<p class="block" style="white-space: pre-wrap;">{{ feature.properties.description }}</p>
</div>
</o-collapse>
</main>
</template>

View file

@ -10,6 +10,7 @@ export default {
data() {
return {
coordinates: null,
alerts: null,
}
},
@ -48,6 +49,7 @@ export default {
const forecast = await this.weatherStore.getForecast()
this.coordinates = this.weatherStore.coordinates
this.alerts = this.weatherStore.alerts
},
getShortForecast(period) {
@ -77,6 +79,16 @@ export default {
</o-select>
</o-field>
<div v-if="alerts?.features?.length">
<o-notification variant="warning"
icon="warning"
@click="$router.push('/alerts')"
style="cursor: pointer;">
<p class="has-text-weight-bold">Watches In Effect</p>
<p>Updated {{ new Date(alerts.updated).toLocaleTimeString('en-US', {timeStyle: 'short'}) }}</p>
</o-notification>
</div>
<nav class="panel weather-panel">
<p class="panel-heading">