mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 13:17:08 +00:00
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:
@@ -119,7 +119,11 @@
|
|||||||
>
|
>
|
||||||
|
|
||||||
<template v-slot:item.srss="{item}">
|
<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="srssInfo(item)">{{srssIcon(item)}}</v-icon>
|
||||||
|
/
|
||||||
|
<v-icon small :title="wxInfo(item)" v-if="item.meta.wx">{{wxIcon(item)}}</v-icon>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.sequence="{item, value}">
|
<template v-slot:item.sequence="{item, value}">
|
||||||
@@ -422,6 +426,123 @@ export default {
|
|||||||
plannerConfig: null,
|
plannerConfig: null,
|
||||||
shiftAll: false, // Shift all sequences checkbox
|
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
|
// Context menu stuff
|
||||||
contextMenuShow: false,
|
contextMenuShow: false,
|
||||||
contextMenuX: 0,
|
contextMenuX: 0,
|
||||||
@@ -630,6 +751,113 @@ export default {
|
|||||||
return text.join("\n");
|
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) {
|
lagAfter (item) {
|
||||||
const pos = this.items.indexOf(item)+1;
|
const pos = this.items.indexOf(item)+1;
|
||||||
if (pos != 0) {
|
if (pos != 0) {
|
||||||
@@ -723,6 +951,9 @@ export default {
|
|||||||
for (const item of this.items) {
|
for (const item of this.items) {
|
||||||
item.ts0 = new Date(item.ts0);
|
item.ts0 = new Date(item.ts0);
|
||||||
item.ts1 = new Date(item.ts1);
|
item.ts1 = new Date(item.ts1);
|
||||||
|
this.wxQuery(item).then( (wx) => {
|
||||||
|
item.meta = {...item.meta, wx};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user