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:
parent
7ed314b5bf
commit
0710f2238c
4 changed files with 134 additions and 2 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
import WeatherView from '../views/WeatherView.vue'
|
import WeatherView from '../views/WeatherView.vue'
|
||||||
|
import AlertsView from '../views/AlertsView.vue'
|
||||||
import HourlyView from '../views/HourlyView.vue'
|
import HourlyView from '../views/HourlyView.vue'
|
||||||
import EditListView from '../views/EditListView.vue'
|
import EditListView from '../views/EditListView.vue'
|
||||||
|
|
||||||
|
@ -17,6 +18,11 @@ const router = createRouter({
|
||||||
name: 'weather',
|
name: 'weather',
|
||||||
component: WeatherView,
|
component: WeatherView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/alerts',
|
||||||
|
name: 'alerts',
|
||||||
|
component: AlertsView,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/hourly',
|
path: '/hourly',
|
||||||
name: 'hourly',
|
name: 'hourly',
|
||||||
|
|
|
@ -10,6 +10,7 @@ const getDefaults = () => {
|
||||||
coordinates,
|
coordinates,
|
||||||
cityState,
|
cityState,
|
||||||
weather: null,
|
weather: null,
|
||||||
|
alerts: null,
|
||||||
forecast: null,
|
forecast: null,
|
||||||
radarLatestURL: null,
|
radarLatestURL: null,
|
||||||
radarLoopURL: null,
|
radarLoopURL: null,
|
||||||
|
@ -32,14 +33,17 @@ export const useWeatherStore = defineStore('weather', {
|
||||||
this.setForecast(null)
|
this.setForecast(null)
|
||||||
this.radarLatestURL = null
|
this.radarLatestURL = null
|
||||||
this.radarLoopURL = null
|
this.radarLoopURL = null
|
||||||
|
this.alerts = null
|
||||||
},
|
},
|
||||||
|
|
||||||
async getWeather() {
|
async getWeather() {
|
||||||
|
|
||||||
if (!this.weather) {
|
if (!this.weather) {
|
||||||
|
let url
|
||||||
|
let response
|
||||||
|
|
||||||
const url = `https://api.weather.gov/points/${this.coordinates}`
|
url = `https://api.weather.gov/points/${this.coordinates}`
|
||||||
const response = await fetch(url)
|
response = await fetch(url)
|
||||||
const weather = await response.json()
|
const weather = await response.json()
|
||||||
if (weather.status == 404) {
|
if (weather.status == 404) {
|
||||||
throw new Error(`Data not found for ${this.coordinates}`)
|
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.radarLoopURL = `https://radar.weather.gov/ridge/standard/${station}_loop.gif`
|
||||||
|
|
||||||
this.setWeather(weather)
|
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
|
return this.weather
|
||||||
|
|
51
src/views/AlertsView.vue
Normal file
51
src/views/AlertsView.vue
Normal 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>
|
|
@ -10,6 +10,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
coordinates: null,
|
coordinates: null,
|
||||||
|
alerts: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ export default {
|
||||||
|
|
||||||
const forecast = await this.weatherStore.getForecast()
|
const forecast = await this.weatherStore.getForecast()
|
||||||
this.coordinates = this.weatherStore.coordinates
|
this.coordinates = this.weatherStore.coordinates
|
||||||
|
this.alerts = this.weatherStore.alerts
|
||||||
},
|
},
|
||||||
|
|
||||||
getShortForecast(period) {
|
getShortForecast(period) {
|
||||||
|
@ -77,6 +79,16 @@ export default {
|
||||||
</o-select>
|
</o-select>
|
||||||
</o-field>
|
</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">
|
<nav class="panel weather-panel">
|
||||||
|
|
||||||
<p class="panel-heading">
|
<p class="panel-heading">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue