diff --git a/cspell.json b/cspell.json index 10a385b..7aeaa85 100644 --- a/cspell.json +++ b/cspell.json @@ -1,5 +1,3 @@ { - "userWords": [ - "ConfigInterface" - ] -} \ No newline at end of file + "userWords": ["ConfigInterface"] +} diff --git a/src/lib/components/location/location-search.svelte b/src/lib/components/location/location-search.svelte index 839fdb0..34fcc2c 100644 --- a/src/lib/components/location/location-search.svelte +++ b/src/lib/components/location/location-search.svelte @@ -240,8 +240,8 @@ {location.country || ''}

- {location.latitude.toFixed(2)}°N {location.longitude.toFixed(2)}°E - {#if location.elevation}• {location.elevation.toFixed(0)}m{/if} + {location.latitude?.toFixed(2)}°N {location.longitude?.toFixed(2)}°E + {#if location.elevation}• {location.elevation?.toFixed(0)}m{/if}

diff --git a/src/lib/components/ui/card/card-action.svelte b/src/lib/components/ui/card/card-action.svelte index cc36c56..352420c 100644 --- a/src/lib/components/ui/card/card-action.svelte +++ b/src/lib/components/ui/card/card-action.svelte @@ -1,6 +1,6 @@ -
+
{@render children?.()}
diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte index 9b20ac7..c87efde 100644 --- a/src/lib/components/ui/card/card-description.svelte +++ b/src/lib/components/ui/card/card-description.svelte @@ -1,6 +1,6 @@ -
+
${$params.hourly.join(', ')} in models: ` + - $params.models.join(', ') + - '' - : '', - align: 'left' - }, - - yAxis: { - title: { - text: unit - } - }, - - xAxis: { - type: 'datetime', - plotLines: [ - { - value: Date.now() + data.utc_offset_seconds * 1000, - color: 'red', - width: 2 - } - ], - plotBands: plotBands - }, - - plotOptions: { - spline: { - lineWidth: 2, - states: { - hover: { - lineWidth: 3 + if (model.startsWith(variable)) { + for (let [index, val] of (values as any[]).entries()) { + if (val) { + let avVal = average[index]; + average[index] = avVal + val; + averageCount[index]++; } - }, - marker: { - enabled: false + } + + unit = data.hourly_units[model]; + + if (!averageOnly) { + series.push({ + name: model, + data: values, + type: + unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²' + ? 'column' + : 'spline', + tooltip: { + valueSuffix: ' ' + unit + }, + pointStart: hourly_starttime, + pointInterval: pointInterval + }); + } + } + } + + for (let [index, val] of average.entries()) { + average[index] = Math.round((val / averageCount[index]) * 10) / 10; + } + + series.push({ + name: variable + '_average', + data: average, + dashStyle: 'ShortDashDot', + color: '#5e5e5e', + type: + unit == 'mm' || unit == 'cm' || unit == 'inch' || unit == 'MJ/m²' + ? 'column' + : 'spline', + tooltip: { + valueSuffix: ' ' + unit + }, + lineWidth: 4, + states: { + hover: { + lineWidth: 6 } }, - column: { - pointWidth: 5 - } - }, + pointStart: hourly_starttime, + pointInterval: pointInterval, + className: 'highcharts-average-series' + }); - legend: { - enabled: showLegend, - layout: 'horizontal', - align: 'center', - verticalAlign: 'bottom' - }, + const chart = new Highcharts.Chart(chartDiv, { + credits: { + text: count === ($params.hourly?.length || 0) - 1 ? 'Open-Meteo.com' : '', + href: 'http://open-meteo.com' + }, - series: series, + chart: { + height: showLegend ? '400px' : '300px', + styledMode: true, + marginLeft: '50', + marginRight: 0 + }, - responsive: { - rules: [ - { - condition: { - maxWidth: 800 - } + lang: { + locale: 'en-GB' + }, + + title: { + text: count === 0 ? 'Model Compare' : '', + align: 'left' + }, + + subtitle: { + text: + count === 0 + ? `Compare ${$params.hourly?.join(', ')} in models: ` + + $params.models?.join(', ') + + '' + : '', + align: 'left' + }, + + yAxis: { + title: { + text: unit } - ] - }, + }, - tooltip: { - shared: true, - animation: false - } - }); + xAxis: { + type: 'datetime', + plotLines: [ + { + value: Date.now() + data.utc_offset_seconds * 1000, + color: 'red', + width: 2 + } + ], + plotBands: plotBands + }, - count++; - node.appendChild(chartDiv); - } + plotOptions: { + spline: { + lineWidth: 2, + states: { + hover: { + lineWidth: 3 + } + }, + marker: { + enabled: false + } + }, + column: { + pointWidth: 5 + } + }, + + legend: { + enabled: showLegend, + layout: 'horizontal', + align: 'center', + verticalAlign: 'bottom' + }, + + series: series, + + responsive: { + rules: [ + { + condition: { + maxWidth: 800 + } + } + ] + }, + + tooltip: { + shared: true, + animation: false + } + }); + + count++; + charts.push(chart); + // eslint-disable-next-line svelte/no-dom-manipulating + node.appendChild(chartDiv); + } + })(); } }); onDestroy(() => { - if (chart) { - chart.destroy(); - } + charts.forEach((c) => c.destroy()); }); -
+

Models

- {#if $params.models.length > 0} + {#if $params.models && $params.models.length > 0}
- {$params.models.length} / {models.flat().length} + {$params.models?.length} / {models.flat().length}
{/if} @@ -343,7 +358,7 @@ aria-labelledby="{value}_label" onCheckedChange={() => { if ($params.models?.includes(value)) { - $params.models = $params.models.filter((item) => { + $params.models = $params.models.filter((item: string) => { return item !== value; }); } else if ($params.models) { @@ -371,12 +386,12 @@ Hourly Weather Variables - {#if $params.hourly.length > 0} + {#if $params.hourly && $params.hourly.length > 0}
- {$params.hourly.length} / {hourly.flat().length} + {$params.hourly?.length} / {hourly.flat().length}
{/if} @@ -397,7 +412,7 @@ aria-labelledby="{value}_label" onCheckedChange={() => { if ($params.hourly?.includes(value)) { - $params.hourly = $params.hourly.filter((item) => { + $params.hourly = $params.hourly.filter((item: string) => { return item !== value; }); } else if ($params.hourly) { diff --git a/src/routes/weather/options.ts b/src/routes/weather/options.ts index a58f330..7c2c742 100644 --- a/src/routes/weather/options.ts +++ b/src/routes/weather/options.ts @@ -6,18 +6,20 @@ export const defaultParameters = { }; export const models = [ - { value: 'best_match', label: 'Best match' }, - { value: 'gfs_seamless', label: 'NCEP GFS Seamless' }, - { value: 'jma_seamless', label: 'JMA Seamless' }, - { value: 'kma_seamless', label: 'KMA Seamless' }, - { value: 'icon_seamless', label: 'DWD ICON Seamless' }, - { value: 'gem_seamless', label: 'GEM Seamless' }, - { value: 'meteofrance_seamless', label: 'Météo-France Seamless' }, - { value: 'arpae_cosmo_seamless', label: 'ARPAE Seamless' }, - { value: 'metno_seamless', label: 'MET Norway Seamless (with ECMWF)' }, - { value: 'knmi_seamless', label: 'KNMI Seamless (with ECMWF)' }, - { value: 'dmi_seamless', label: 'DMI Seamless (with ECMWF)' }, - { value: 'ukmo_seamless', label: 'UK Met Office Seamless' } + [{ value: 'best_match', label: 'Best match' }], + [ + { value: 'gfs_seamless', label: 'NCEP GFS Seamless' }, + { value: 'jma_seamless', label: 'JMA Seamless' }, + { value: 'kma_seamless', label: 'KMA Seamless' }, + { value: 'icon_seamless', label: 'DWD ICON Seamless' }, + { value: 'gem_seamless', label: 'GEM Seamless' }, + { value: 'meteofrance_seamless', label: 'Météo-France Seamless' }, + { value: 'arpae_cosmo_seamless', label: 'ARPAE Seamless' }, + { value: 'metno_seamless', label: 'MET Norway Seamless (with ECMWF)' }, + { value: 'knmi_seamless', label: 'KNMI Seamless (with ECMWF)' }, + { value: 'dmi_seamless', label: 'DMI Seamless (with ECMWF)' }, + { value: 'ukmo_seamless', label: 'UK Met Office Seamless' } + ] ]; export const hourly = [ diff --git a/src/routes/weather/week/+page.ts b/src/routes/weather/week/+page.ts index 9fd7ceb..0a3ab52 100644 --- a/src/routes/weather/week/+page.ts +++ b/src/routes/weather/week/+page.ts @@ -2,13 +2,13 @@ import { get } from 'svelte/store'; import { redirect } from '@sveltejs/kit'; import { storedLocation } from '$lib/stores/settings'; import { geoLocationNameToRoute } from '$lib/utils/meteo'; -import type { PageLoad } from '$types'; +import type { PageLoad } from './$types'; export const prerender = true; -export const load = (async () => { +export const load: PageLoad = async () => { const location = get(storedLocation); - const locationRoute = geoLocationNameToRoute(location.name); + const locationRoute = geoLocationNameToRoute(location.name || ''); throw redirect( 303, '/weather/week/' + @@ -18,4 +18,4 @@ export const load = (async () => { : locationRoute + '_' + location.id : locationRoute + '_' + location.id) ); -}) satisfies PageLoad; +}; diff --git a/src/routes/weather/week/[location]/+page.svelte b/src/routes/weather/week/[location]/+page.svelte index a64c892..4416b0b 100644 --- a/src/routes/weather/week/[location]/+page.svelte +++ b/src/routes/weather/week/[location]/+page.svelte @@ -21,7 +21,7 @@ import { getColor, textWhite } from '../../utils/colors'; import weatherCodes from '../../utils/weather-codes'; import { SvelteDate } from 'svelte/reactivity'; - import { pad } from '$lib/utils'; + import { pad } from '$lib/utils/index.js'; // --- Interfaces --- @@ -63,9 +63,9 @@ // --- State Setup --- const params = urlHashStore({ - latitude: [$storedLocation.latitude], - longitude: [$storedLocation.longitude], - models: 'best_match', + latitude: [$storedLocation.latitude || 0], + longitude: [$storedLocation.longitude || 0], + models: ['best_match'], ...defaultParameters }); @@ -89,7 +89,9 @@ const entries = 6; const winddir = true; - let modelSelected = $derived(models.find((mo) => String(mo.value) === $params.models)); + let modelSelected = $derived( + models.flat().find((mo) => $params.models?.includes(String(mo.value))) + ); // --- Logic --- @@ -654,13 +656,20 @@
- + { + $params.models = [v]; + }} + > {modelSelected?.label} - {#each models as mo (mo.value)} + {#each models.flat() as mo (mo.value)} {mo.label} {/each} diff --git a/src/routes/weather/week/[location]/+page.ts b/src/routes/weather/week/[location]/+page.ts index 1f26625..863f111 100644 --- a/src/routes/weather/week/[location]/+page.ts +++ b/src/routes/weather/week/[location]/+page.ts @@ -4,11 +4,11 @@ import { type GeoLocation, storedLocation } from '$lib/stores/settings'; import { geoLocationNameToRoute } from '$lib/utils/meteo'; -import type { PageLoad } from '$types'; +import type { PageLoad } from './$types'; export const prerender = true; -export const load = (async (event) => { +export const load: PageLoad = async (event) => { const urlLocation = event.params.location; let urlLocationSplit, urlLocationName, urlLocationId; @@ -31,8 +31,8 @@ export const load = (async (event) => { // lat, long coordinates if (urlLocation.includes('N') && urlLocation.includes('E')) { urlLocationSplit = urlLocation.split(/N|E/); - const latitude = urlLocationSplit[0]; - const longitude = urlLocationSplit[1]; + const latitude = Number(urlLocationSplit[0]); + const longitude = Number(urlLocationSplit[1]); location = { //id: undefined, @@ -58,7 +58,7 @@ export const load = (async (event) => { } } - const locationRoute = geoLocationNameToRoute(location.name); + const locationRoute = geoLocationNameToRoute(location.name || ''); if (location.population && location.population > 543000) { // 1000 biggest cities @@ -74,4 +74,4 @@ export const load = (async (event) => { storedLocation.set(location); return { location: location }; -}) satisfies PageLoad; +}; diff --git a/svelte.config.js b/svelte.config.js index 0487b76..8e47797 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -7,7 +7,11 @@ const config = { // for more information about preprocessors preprocess: vitePreprocess(), - kit: { adapter: adapter() } + kit: { + adapter: adapter({ + fallback: '404.html' + }) + } }; export default config;