Forecast fixes
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import Loading from '@/components/Loading.vue';
|
||||
import {formatDate} from '@ztimson/utils';
|
||||
import {BASE, type DataRow} from '../services/api';
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {api, BASE} from '../services/api';
|
||||
|
||||
const props = withDefaults(defineProps<{ days: DataRow[] }>(), {});
|
||||
const days = ref<any[]>()
|
||||
const filler = [1, 2, 3, 4, 5];
|
||||
|
||||
function dir(deg: number) {
|
||||
@@ -16,6 +17,13 @@ function dir(deg: number) {
|
||||
if(deg > 292.5 && deg <= 337.5) return 'NW';
|
||||
return 'N';
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const start = new Date(), end = new Date();
|
||||
start.setDate(start.getDate() + 1);
|
||||
end.setDate(end.getDate() + 5);
|
||||
days.value = await api.daily(start, end);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -13,9 +13,9 @@ import Point from 'ol/geom/Point'
|
||||
import CircleGeom from 'ol/geom/Circle'
|
||||
import { fromLonLat, toLonLat } from 'ol/proj'
|
||||
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
|
||||
import { current } from '../services/weather'
|
||||
import 'ol/ol.css'
|
||||
|
||||
const current = ref([0, 0]);
|
||||
const props = defineProps<{ dark: boolean }>()
|
||||
const mapEl = ref<HTMLDivElement>()
|
||||
const showOverlays = ref(false)
|
||||
|
||||
@@ -16,5 +16,9 @@ export const api = {
|
||||
position: (fields?: string) => get<{latitude: number, longitude: number, altitude: number}>('/api/position', fields ? { fields } : {}),
|
||||
current: (fields?: string) => get<DataRow>('/api/current', fields ? { fields } : {}),
|
||||
hourly: (start?: string, end?: string, fields?: string) => get<DataRow[]>('/api/hourly', { ...(start ? { start } : {}), ...(end ? { end } : {}), ...(fields ? { fields } : {}) }),
|
||||
daily: (start?: string, end?: string, fields?: string) => get<DataRow[]>('/api/daily', { ...(start ? { start } : {}), ...(end ? { end } : {}), ...(fields ? { fields } : {}) }),
|
||||
daily: (start?: Date, end?: Date, fields?: string) => get<DataRow[]>('/api/daily', {
|
||||
...(start ? { start: start.toISOString().slice(0, 10) } : {}),
|
||||
...(end ? { end: end.toISOString().slice(0, 10) } : {}),
|
||||
...(fields ? { fields } : {})
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { api, type DataRow } from './api'
|
||||
|
||||
export const current = ref<DataRow>({})
|
||||
export const hourly = ref<DataRow[]>([])
|
||||
export const daily = ref<DataRow[]>([])
|
||||
export const loading = ref(false)
|
||||
export const lastFetch = ref<Date | null>(null)
|
||||
|
||||
export async function fetchAll() {
|
||||
const date = (d: Date = new Date()) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`
|
||||
|
||||
loading.value = true
|
||||
const start = new Date(), end = new Date();
|
||||
start.setDate(start.getDate() + 1);
|
||||
end.setDate(end.getDate() + 5);
|
||||
const [c, h, d] = await Promise.allSettled([api.current(), api.hourly(), api.daily(date(start), date(end))])
|
||||
if (c.status === 'fulfilled') current.value = c.value
|
||||
if (h.status === 'fulfilled') hourly.value = h.value
|
||||
if (d.status === 'fulfilled') daily.value = d.value
|
||||
lastFetch.value = new Date()
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
export async function fetchHistoricHourly(start: string, end: string): Promise<DataRow[]> {
|
||||
return api.hourly(start, end)
|
||||
}
|
||||
|
||||
export async function fetchHistoricDaily(start: string, end: string): Promise<DataRow[]> {
|
||||
return api.daily(start, end)
|
||||
}
|
||||
|
||||
export const isDaytime = computed(() => current.value.sun_is_day === 1)
|
||||
@@ -1,20 +1,14 @@
|
||||
`<script setup lang="ts">
|
||||
import {formatDate} from '@ztimson/utils';
|
||||
import {onMounted, onUnmounted, ref, computed} from 'vue';
|
||||
import {current, daily, fetchAll, loading} from '../services/weather';
|
||||
import {METRICS, GROUPS} from '../services/units';
|
||||
import {onMounted, onUnmounted, ref} from 'vue';
|
||||
import MapView from '../components/MapView.vue';
|
||||
import CurrentWeather from '../components/CurrentWeather.vue';
|
||||
import ForecastStrip from '../components/ForecastStrip.vue';
|
||||
import MetricCard from '../components/MetricCard.vue';
|
||||
import GraphModal from '../components/GraphModal.vue';
|
||||
|
||||
const day = ref(formatDate('dddd'));
|
||||
const date = ref(formatDate('MMMM D'));
|
||||
const time = ref(formatDate('h:mm A'));
|
||||
const props = defineProps<{dark: boolean}>();
|
||||
const selectedMetric = ref<string | null>(null);
|
||||
let interval: ReturnType<typeof setInterval>;
|
||||
let clock: ReturnType<typeof setInterval>;
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -23,22 +17,10 @@ onMounted(async () => {
|
||||
date.value = formatDate('MMMM D');
|
||||
time.value = formatDate('h:mm A');
|
||||
}, 1000);
|
||||
await fetchAll();
|
||||
interval = setInterval(fetchAll, 60 * 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(clock);
|
||||
clearInterval(interval);
|
||||
});
|
||||
|
||||
const groupedMetrics = computed(() => {
|
||||
return GROUPS.map(group => ({
|
||||
group,
|
||||
keys: Object.entries(METRICS)
|
||||
.filter(([key, meta]) => meta.group === group && current.value[key] != null)
|
||||
.map(([key]) => key)
|
||||
})).filter(g => g.keys.length > 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -161,8 +143,6 @@ const groupedMetrics = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<div v-if="loading" class="loading-bar"/>
|
||||
|
||||
<div class="map-col">
|
||||
<MapView :dark="props.dark"/>
|
||||
</div>
|
||||
@@ -178,27 +158,14 @@ const groupedMetrics = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<CurrentWeather />
|
||||
<ForecastStrip :days="daily"/>
|
||||
|
||||
<template v-for="g in groupedMetrics" :key="g.group">
|
||||
<div class="group-label">{{ g.group }}</div>
|
||||
<div class="metric-grid">
|
||||
<MetricCard
|
||||
v-for="key in g.keys"
|
||||
:key="key"
|
||||
:metric-key="key"
|
||||
:data="current"
|
||||
@click="selectedMetric = key"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<ForecastStrip />
|
||||
</div>
|
||||
|
||||
<GraphModal
|
||||
:metric-key="selectedMetric"
|
||||
:current-data="current"
|
||||
@close="selectedMetric = null"
|
||||
/>
|
||||
<!-- <GraphModal-->
|
||||
<!-- :metric-key="selectedMetric"-->
|
||||
<!-- :current-data="current"-->
|
||||
<!-- @close="selectedMetric = null"-->
|
||||
<!-- />-->
|
||||
</div>
|
||||
</template>
|
||||
`
|
||||
|
||||
@@ -186,16 +186,6 @@ function moonriseMoonset(lat, lon, date = new Date()) {
|
||||
return {moonrise: moonrise, moonset: moonset};
|
||||
}
|
||||
|
||||
function nextSolsticeEquinox(date = new Date()) {
|
||||
const y = date.getFullYear();
|
||||
return {
|
||||
summer_solstice: new Date(Date.UTC(y, 5, 21)),
|
||||
winter_solstice: new Date(Date.UTC(y, 11, 21)),
|
||||
vernal_equinox: new Date(Date.UTC(y, 2, 20)),
|
||||
autumnal_equinox: new Date(Date.UTC(y + 1, 2, 20)),
|
||||
};
|
||||
}
|
||||
|
||||
export function getCelestialCurrent(lat, lon, date = new Date()) {
|
||||
const data = {
|
||||
...sunPosition(lat, lon, date),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// forecast.mjs
|
||||
import {queryHourly, getCoords, queryCurrent} from './influx.mjs';
|
||||
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
|
||||
import {getCelestialCurrent} from './celestial.mjs';
|
||||
import {getWeatherCondition} from './openweather.mjs';
|
||||
|
||||
export let lastForecast = { ts: 0, slots: [], summary: null }
|
||||
|
||||
@@ -102,40 +102,25 @@ app.get('/api/hourly', async (req, res) => {
|
||||
|
||||
app.get('/api/daily', async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const now = new Date()
|
||||
const now = new Date()
|
||||
now.setHours(0, 0, 0, 0)
|
||||
const next = new Date(now); next.setDate(now.getDate() + 1)
|
||||
|
||||
const next = new Date(now); next.setDate(now.getDate() + 1)
|
||||
const start = req.query.start ? new Date(req.query.start) : now
|
||||
const end = req.query.end ? new Date(req.query.end) : next
|
||||
const coords = await getCoords()
|
||||
|
||||
// Does the requested window overlap with today (the 24h forecast window)?
|
||||
const todayStr = now
|
||||
const usePhysics = lastForecast.summary && start <= next && end >= now
|
||||
|
||||
// Past influx history (days before today)
|
||||
const [sensorResult] = await Promise.allSettled([
|
||||
start < now ? queryDaily(start, now) : Promise.resolve([])
|
||||
])
|
||||
const todayStr = now.toISOString().slice(0, 10) // ✅ string for comparison
|
||||
const usePhysics = lastForecast.summary && start <= now && end > now // ✅ only when today is in window
|
||||
const [sensorResult] = await Promise.allSettled([start < now ? queryDaily(start, now) : Promise.resolve([])])
|
||||
const history = sensorResult.status === 'fulfilled' ? sensorResult.value : []
|
||||
|
||||
// Future meteo (days after today)
|
||||
const meteoStart = end > next ? next : null
|
||||
const [meteoResult] = await Promise.allSettled([
|
||||
meteoStart && end > next ? dailyWeather(coords.latitude, coords.longitude, next, end) : Promise.resolve([])
|
||||
])
|
||||
// ✅ meteo starts from whichever is later: next or start
|
||||
const meteoFrom = new Date(Math.max(next.getTime(), start.getTime()))
|
||||
const [meteoResult] = await Promise.allSettled([end > next ? dailyWeather(coords.latitude, coords.longitude, meteoFrom, end) : Promise.resolve([])])
|
||||
const meteo = meteoResult.status === 'fulfilled' ? meteoResult.value : []
|
||||
|
||||
// Splice in physics summary for today, skip any meteo row that duplicates it
|
||||
const todaySlot = usePhysics ? [lastForecast.summary] : []
|
||||
const meteoClean = meteo.filter(r => r.time !== todayStr)
|
||||
|
||||
const daily = getCelestialForecast(
|
||||
coords.latitude, coords.longitude,
|
||||
[...history, ...todaySlot, ...meteoClean]
|
||||
)
|
||||
const meteoClean = meteo.filter(r => String(r.time).slice(0, 10) !== todayStr) // ✅ safe string compare
|
||||
|
||||
const daily = getCelestialForecast(coords.latitude, coords.longitude, [...history, ...todaySlot, ...meteoClean])
|
||||
res.json(filterArr(daily, fields))
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user