Compare commits

...

2 Commits

Author SHA1 Message Date
D. Berge
fbc2550a75 Update Material Design iconset 2023-09-02 16:12:44 +02:00
D. Berge
67f3f83c61 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)
2023-09-01 00:37:15 +02:00
3 changed files with 240 additions and 9 deletions

View File

@@ -9,7 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@mdi/font": "^5.6.55", "@mdi/font": "^7.2.96",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"d3": "^7.0.1", "d3": "^7.0.1",
"jwt-decode": "^3.0.0", "jwt-decode": "^3.0.0",
@@ -1763,9 +1763,9 @@
} }
}, },
"node_modules/@mdi/font": { "node_modules/@mdi/font": {
"version": "5.9.55", "version": "7.2.96",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.2.96.tgz",
"integrity": "sha512-jswRF6q3eq8NWpWiqct6q+6Fg/I7nUhrxYJfiEM8JJpap0wVJLQdbKtyS65GdlK7S7Ytnx3TTi/bmw+tBhkGmg==" "integrity": "sha512-e//lmkmpFUMZKhmCY9zdjRe4zNXfbOIJnn6xveHbaV2kSw5aJ5dLXUxcRt1Gxfi7ZYpFLUWlkG2MGSFAiqAu7w=="
}, },
"node_modules/@mrmlnc/readdir-enhanced": { "node_modules/@mrmlnc/readdir-enhanced": {
"version": "2.2.1", "version": "2.2.1",
@@ -16432,9 +16432,9 @@
} }
}, },
"@mdi/font": { "@mdi/font": {
"version": "5.9.55", "version": "7.2.96",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.2.96.tgz",
"integrity": "sha512-jswRF6q3eq8NWpWiqct6q+6Fg/I7nUhrxYJfiEM8JJpap0wVJLQdbKtyS65GdlK7S7Ytnx3TTi/bmw+tBhkGmg==" "integrity": "sha512-e//lmkmpFUMZKhmCY9zdjRe4zNXfbOIJnn6xveHbaV2kSw5aJ5dLXUxcRt1Gxfi7ZYpFLUWlkG2MGSFAiqAu7w=="
}, },
"@mrmlnc/readdir-enhanced": { "@mrmlnc/readdir-enhanced": {
"version": "2.2.1", "version": "2.2.1",

View File

@@ -7,7 +7,7 @@
"build": "vue-cli-service build" "build": "vue-cli-service build"
}, },
"dependencies": { "dependencies": {
"@mdi/font": "^5.6.55", "@mdi/font": "^7.2.96",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"d3": "^7.0.1", "d3": "^7.0.1",
"jwt-decode": "^3.0.0", "jwt-decode": "^3.0.0",

View File

@@ -119,7 +119,11 @@
> >
<template v-slot:item.srss="{item}"> <template v-slot:item.srss="{item}">
<v-icon small :title="srssInfo(item)">{{srssIcon(item)}}</v-icon> <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>
<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};
});
} }
}, },