Add wx forecast info to plan (experiment).

Use https://open-meteo.com/ as a weather forecast provider.

This code is intended for demonstration only, not for
production purposes.

(issue #157)
This commit is contained in:
D. Berge
2023-09-01 00:33:13 +02:00
parent ecbb1e04ee
commit 67f3f83c61

View File

@@ -119,7 +119,11 @@
>
<template v-slot:item.srss="{item}">
<span style="white-space: nowrap;">
<v-icon small :title="srssInfo(item)">{{srssIcon(item)}}</v-icon>
/
<v-icon small :title="wxInfo(item)" v-if="item.meta.wx">{{wxIcon(item)}}</v-icon>
</span>
</template>
<template v-slot:item.sequence="{item, value}">
@@ -422,6 +426,123 @@ export default {
plannerConfig: null,
shiftAll: false, // Shift all sequences checkbox
// Weather API
wxData: null,
weathercode: {
0: {
description: "Clear sky",
icon: "mdi-weather-sunny"
},
1: {
description: "Mainly clear",
icon: "mdi-weather-sunny"
},
2: {
description: "Partly cloudy",
icon: "mdi-weather-partly-cloudy"
},
3: {
description: "Overcast",
icon: "mdi-weather-cloudy"
},
45: {
description: "Fog",
icon: "mde-weather-fog"
},
48: {
description: "Depositing rime fog",
icon: "mdi-weather-fog"
},
51: {
description: "Light drizzle",
icon: "mdi-weather-partly-rainy"
},
53: {
description: "Moderate drizzle",
icon: "mdi-weather-partly-rainy"
},
55: {
description: "Dense drizzle",
icon: "mdi-weather-rainy"
},
56: {
description: "Light freezing drizzle",
icon: "mdi-weather-partly-snowy-rainy"
},
57: {
description: "Freezing drizzle",
icon: "mdi-weather-partly-snowy-rainy"
},
61: {
description: "Light rain",
icon: "mdi-weather-rainy"
},
63: {
description: "Moderate rain",
icon: "mdi-weather-rainy"
},
65: {
description: "Heavy rain",
icon: "mdi-weather-pouring"
},
66: {
description: "Light freezing rain",
icon: "mdi-loading"
},
67: {
description: "Freezing rain",
icon: "mdi-loading"
},
71: {
description: "Light snow",
icon: "mdi-loading"
},
73: {
description: "Moderate snow",
icon: "mdi-loading"
},
75: {
description: "Heavy snow",
icon: "mdi-loading"
},
77: {
description: "Snow grains",
icon: "mdi-loading"
},
80: {
description: "Light rain showers",
icon: "mdi-loading"
},
81: {
description: "Moderate rain showers",
icon: "mdi-loading"
},
82: {
description: "Violent rain showers",
icon: "mdi-loading"
},
85: {
description: "Light snow showers",
icon: "mdi-loading"
},
86: {
description: "Snow showers",
icon: "mdi-loading"
},
95: {
description: "Thunderstorm",
icon: "mdi-loading"
},
96: {
description: "Hailstorm",
icon: "mdi-loading"
},
99: {
description: "Heavy hailstorm",
icon: "mdi-loading"
},
},
// Context menu stuff
contextMenuShow: false,
contextMenuX: 0,
@@ -630,6 +751,113 @@ export default {
return text.join("\n");
},
wxInfo (line) {
function atm(key) {
return line.meta?.wx?.atmospheric?.hourly[key];
}
function mar(key) {
return line.meta?.wx?.marine?.hourly[key];
}
const code = atm("weathercode");
const description = this.weathercode[code]?.description ?? `WMO code ${code}`;
const wind_speed = Math.round(atm("windspeed_10m"));
const wind_direction = String(Math.round(atm("winddirection_10m"))).padStart(3, "0");
const pressure = Math.round(atm("surface_pressure"));
const temperature = Math.round(atm("temperature_2m"));
const humidity = atm("relativehumidity_2m");
const precipitation = atm("precipitation");
const precipitation_probability = atm("precipitation_probability");
const precipitation_str = precipitation_probability
? `\nPrecipitation ${precipitation} mm (prob. ${precipitation_probability}%)`
: ""
const wave_height = mar("wave_height").toFixed(1);
const wave_direction = mar("wave_direction");
const wave_period = mar("wave_period");
return `${description}\n${temperature}° C\n${pressure} hPa\nWind ${wind_speed} kt ${wind_direction}°\nRelative humidity ${humidity}%${precipitation_str}\nWaves ${wave_height} m ${wave_direction}° @ ${wave_period} s`;
},
wxIcon (line) {
const code = line.meta?.wx?.atmospheric?.hourly?.weathercode;
return this.weathercode[code]?.icon ?? "mdi-help";
},
async wxQuery (line) {
function midpoint(line) {
// WARNING Fails if across the antimeridian
const longitude = (line.geometry.coordinates[0][0] + line.geometry.coordinates[1][0])/2;
const latitude = (line.geometry.coordinates[0][1] + line.geometry.coordinates[1][1])/2;
return [ longitude, latitude ];
}
function extract (fcst) {
const τ = (line.ts0.valueOf() + line.ts1.valueOf()) / 2000;
const [idx, ε] = fcst?.hourly?.time?.reduce( (acc, cur, idx) => {
const δ = Math.abs(cur - τ);
const retval = acc
? acc[1] < δ
? acc
: [ idx, δ ]
: [ idx, δ ];
return retval;
});
if (idx) {
const hourly = {};
for (let key in fcst?.hourly) {
fcst.hourly[key] = fcst.hourly[key][idx];
}
}
return fcst;
}
async function fetch_atmospheric (opts) {
const { longitude, latitude, dt0, dt1 } = opts;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&hourly=temperature_2m,relativehumidity_2m,precipitation_probability,precipitation,weathercode,pressure_msl,surface_pressure,windspeed_10m,winddirection_10m&daily=uv_index_max&windspeed_unit=kn&timeformat=unixtime&timezone=GMT&start_date=${dt0}&end_date=${dt1}&format=json`;
const init = {};
const res = await fetch (url, init);
if (res?.ok) {
const data = await res.json();
return extract(data);
}
}
async function fetch_marine (opts) {
const { longitude, latitude, dt0, dt1 } = opts;
const url = `https://marine-api.open-meteo.com/v1/marine?latitude=${latitude}&longitude=${longitude}&hourly=wave_height,wave_direction,wave_period&timeformat=unixtime&timezone=GMT&start_date=${dt0}&end_date=${dt1}&format=json`;
const init = {};
const res = await fetch (url, init);
if (res?.ok) {
const data = await res.json();
return extract(data);
}
}
if (line) {
const [ longitude, latitude ] = midpoint(line);
const dt0 = line.ts0.toISOString().substr(0, 10);
const dt1 = line.ts1.toISOString().substr(0, 10);
return {
atmospheric: await fetch_atmospheric({longitude, latitude, dt0, dt1}),
marine: await fetch_marine({longitude, latitude, dt0, dt1})
};
}
},
lagAfter (item) {
const pos = this.items.indexOf(item)+1;
if (pos != 0) {
@@ -723,6 +951,9 @@ export default {
for (const item of this.items) {
item.ts0 = new Date(item.ts0);
item.ts1 = new Date(item.ts1);
this.wxQuery(item).then( (wx) => {
item.meta = {...item.meta, wx};
});
}
},