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 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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
							
							
						
						
									
										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() {
 | 
			
		||||
        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">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue