From 2e25972831ed477cb640bbf7b0a06c598b751341 Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Wed, 28 Jan 2026 19:40:04 +0100 Subject: [PATCH 1/7] try shademap --- package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +++- src/lib/constants.ts | 45 +++++++++++++++++++++-------------------- src/routes/+page.svelte | 22 ++++++++++++++++++++ 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c4e215e..adc16ee1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,12 @@ "@vitest/coverage-v8": "^4.0.17", "bits-ui": "^2.15.2", "clsx": "^2.1.1", + "crepuscule": "file:../../forks/crepuscule", "eslint": "^9.39.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.13.0", "globals": "^17.0.0", + "mapbox-gl-shadow-simulator": "^0.67.0", "maplibre-gl": "^5.15.0", "mdsvex": "^0.12.3", "mode-watcher": "^1.1.0", @@ -53,6 +55,26 @@ "vitest-browser-svelte": "^2.0.1" } }, + "../../forks/crepuscule": { + "version": "1.0.0", + "dev": true, + "dependencies": { + "maplibre-gl": "^5.17.0" + }, + "devDependencies": { + "@types/node": "^20.4.5", + "@typescript-eslint/eslint-plugin": "^5.61.0", + "@typescript-eslint/parser": "^5.61.0", + "eslint": "^8.44.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.28.0", + "prettier": "^2.8.8", + "typescript": "^5.0.4", + "vite": "^4.4.8", + "vite-plugin-dts": "^3.4.0", + "vite-plugin-plain-text": "^1.4.2" + } + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -3321,6 +3343,10 @@ "node": ">= 0.6" } }, + "node_modules/crepuscule": { + "resolved": "../../forks/crepuscule", + "link": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4772,6 +4798,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mapbox-gl-shadow-simulator": { + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/mapbox-gl-shadow-simulator/-/mapbox-gl-shadow-simulator-0.67.0.tgz", + "integrity": "sha512-XycKUZB8FDbYZE068CmOX4UAZeKhh8N7OQpnVCbdoyn9YEzM6BE9nHZ2iiz8DbRq5zX6taHICg94l44H6RRMNg==", + "dev": true, + "license": "UNLICENSED", + "dependencies": { + "suncalc": "^1.9.0" + } + }, "node_modules/maplibre-gl": { "version": "5.16.0", "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.16.0.tgz", @@ -5870,6 +5906,12 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/suncalc": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", + "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==", + "dev": true + }, "node_modules/supercluster": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", diff --git a/package.json b/package.json index 1f6b31b4..c3b546af 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,12 @@ "@vitest/coverage-v8": "^4.0.17", "bits-ui": "^2.15.2", "clsx": "^2.1.1", + "crepuscule": "file:../../forks/crepuscule", "eslint": "^9.39.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.13.0", "globals": "^17.0.0", + "mapbox-gl-shadow-simulator": "^0.67.0", "maplibre-gl": "^5.15.0", "mdsvex": "^0.12.3", "mode-watcher": "^1.1.0", @@ -71,4 +73,4 @@ "vitest": "^4.0.4", "vitest-browser-svelte": "^2.0.1" } -} \ No newline at end of file +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 624b9f6e..71e1a392 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -4,21 +4,22 @@ export const DEFAULT_VARIABLE = 'temperature_2m'; // Vector options defaults export const DEFAULT_VECTOR_OPTIONS = { - grid: false, - arrows: true, - contours: false, - breakpoints: true, - contourInterval: 2 + grid: false, + arrows: true, + contours: false, + breakpoints: true, + contourInterval: 2 }; // Preferences defaults export const DEFAULT_PREFERENCES = { - globe: false, - terrain: false, - hillshade: false, - clipWater: false, - showScale: true, - timeSelector: true + globe: false, + terrain: false, + hillshade: false, + clipWater: false, + showScale: true, + crepuscule: false, + timeSelector: true }; // Color hash default @@ -36,10 +37,10 @@ export const DEFAULT_OPACITY = 75; // Complete default values for URL parameter checking export const COMPLETE_DEFAULT_VALUES: { [key: string]: boolean | string | number } = { - domain: DEFAULT_DOMAIN, - variable: DEFAULT_VARIABLE, - ...DEFAULT_PREFERENCES, - ...DEFAULT_VECTOR_OPTIONS + domain: DEFAULT_DOMAIN, + variable: DEFAULT_VARIABLE, + ...DEFAULT_PREFERENCES, + ...DEFAULT_VECTOR_OPTIONS }; // Time constants @@ -54,11 +55,11 @@ export const METADATA_REFRESH_INTERVAL = 5 * MILLISECONDS_PER_MINUTE; // 5 minut // Calendar display constants export const DAY_NAMES = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday' + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday' ]; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4c05da97..5352cf12 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,6 +9,7 @@ omProtocol, updateCurrentBounds } from '@openmeteo/mapbox-layer'; + import ShadeMap from 'mapbox-gl-shadow-simulator'; import { type RequestParameters } from 'maplibre-gl'; import * as maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; @@ -142,9 +143,30 @@ if (getInitialMetaDataPromise) await getInitialMetaDataPromise; addOmFileLayers(); + addHillshadeSources(); $map.addControl(new HillshadeButton()); + const shadeMap = new ShadeMap({ + date: new Date(), // display shadows for current date + color: '#01112f', // shade color + opacity: 0.7, // opacity of shade color + apiKey: '', + terrainSource: { + tileSize: 256, + maxZoom: 15, + getSourceUrl: ({ x, y, z }) => { + return `https://s3.amazonaws.com/elevation-tiles-prod/terrarium/${z}/${x}/${y}.png`; + }, + getElevation: ({ r, g, b, a }) => { + return r * 256 + g + b / 256 - 32768; + } + }, + debug: (msg) => { + console.log(new Date().toISOString(), msg); + } + }).addTo($map); + addPopup(); changeOMfileURL(); }); From 09014e757dd0467b89af6ba483b1674067e3c67e Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Thu, 29 Jan 2026 09:49:14 +0100 Subject: [PATCH 2/7] change onDateChange --- src/lib/components/time/time-selector.svelte | 2 ++ src/lib/stores/preferences.ts | 5 +++++ src/routes/+page.svelte | 10 ++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index 2a5c86db..f980f071 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -14,6 +14,7 @@ modelRun, modelRunLocked, preferences, + shadeMap, time } from '$lib/stores/preferences'; import { inProgress, latest, metaJson } from '$lib/stores/preferences'; @@ -289,6 +290,7 @@ } $time = new SvelteDate(date); + $shadeMap?.setDate($time); currentDate = date; updateUrl('time', formatISOWithoutTimezone($time)); await checkClosestModelRun(); diff --git a/src/lib/stores/preferences.ts b/src/lib/stores/preferences.ts index a3dcc83b..3a85b978 100644 --- a/src/lib/stores/preferences.ts +++ b/src/lib/stores/preferences.ts @@ -1,6 +1,9 @@ import { MediaQuery } from 'svelte/reactivity'; import { type Writable, writable } from 'svelte/store'; +import type ShadeMap from 'mapbox-gl-shadow-simulator'; + + import { setMode } from 'mode-watcher'; import { type Persisted, persisted } from 'svelte-persisted-store'; @@ -61,6 +64,8 @@ export const helpOpen = writable(false); export const metaJson: Writable = writable(undefined); export const modelRunLocked = writable(false); +export const shadeMap = writable(null); + export const resetStates = async () => { modelRunLocked.set(false); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5352cf12..610278f0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -29,6 +29,7 @@ resetStates, resolution, resolutionSet, + shadeMap, time, url } from '$lib/stores/preferences'; @@ -147,16 +148,17 @@ addHillshadeSources(); $map.addControl(new HillshadeButton()); - const shadeMap = new ShadeMap({ - date: new Date(), // display shadows for current date + $shadeMap = new ShadeMap({ + date: $time, // display shadows for current date color: '#01112f', // shade color opacity: 0.7, // opacity of shade color - apiKey: '', + apiKey: '', // obtain from https://shademap.app/about/, terrainSource: { tileSize: 256, maxZoom: 15, + getSourceUrl: ({ x, y, z }) => { - return `https://s3.amazonaws.com/elevation-tiles-prod/terrarium/${z}/${x}/${y}.png`; + return `https://tiles.mapterhorn.com/${z}/${x}/${y}.webp`; }, getElevation: ({ r, g, b, a }) => { return r * 256 + g + b / 256 - 32768; From e4c280ca97b1a62a4f587baf35fbb92eb88f712f Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Thu, 29 Jan 2026 10:21:31 +0100 Subject: [PATCH 3/7] smooth scrolling --- src/lib/components/time/time-selector.svelte | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index f980f071..b59c0777 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -644,12 +644,21 @@ if (left === 0) { currentDate.setHours(0); } - let timeStep = - timeStepsComplete[ - Math.round( - (timeStepsComplete.length * target.scrollLeft) / (dayContainerScrollWidth - viewWidth) - ) - ]; + // let timeStep = + // timeStepsComplete[ + // Math.round( + // (timeStepsComplete.length * target.scrollLeft) / (dayContainerScrollWidth - viewWidth) + // ) + // ]; + // + let timeStep = new Date( + timeStepsComplete[0].getTime() + + (timeStepsComplete[timeStepsComplete.length - 1].getTime() - + timeStepsComplete[0].getTime()) * + (target.scrollLeft / (dayContainerScrollWidth - viewWidth)) + ); + $shadeMap?.setDate(timeStep); + if (timeStep) currentDate = new SvelteDate(timeStep); }; @@ -662,9 +671,17 @@ centerDateButton($time); currentDate = new SvelteDate($time); } else { - let timeStep = findTimeStep(currentDate, timeSteps); + let timeStep = + timeStepsComplete[ + Math.round( + (timeStepsComplete.length * dayContainerScrollLeft) / + (dayContainerScrollWidth - viewWidth) + ) + ]; + timeStep = findTimeStep(timeStep, timeSteps); if (timeStep) currentDate = timeStep; onDateChange(currentDate); + isScrolling = true; centerDateButton(currentDate); } } From 5622f7756d2813ce49c03e1b108db6478956dad1 Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Thu, 29 Jan 2026 10:27:23 +0100 Subject: [PATCH 4/7] smooth scrolling --- src/lib/components/time/time-selector.svelte | 24 +++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index b59c0777..7e66f471 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -526,11 +526,18 @@ let hoveredHour = $derived( timeStepsComplete - ? timeStepsComplete[ - Math.round( - (timeStepsComplete.length * (hoverX + dayContainerScrollLeft)) / dayContainerScrollWidth - ) - ] + ? // ? timeStepsComplete[ + // Math.round( + // (timeStepsComplete.length * + // ) + // ] + new Date( + timeStepsComplete[0].getTime() + + ((timeStepsComplete[timeStepsComplete.length - 1].getTime() - + timeStepsComplete[0].getTime()) * + (hoverX + dayContainerScrollLeft)) / + dayContainerScrollWidth + ) : metaFirstTime ); @@ -568,10 +575,15 @@ if (hoursHoverContainer) { hoursHoverContainer.addEventListener('mousemove', (e) => { - if (hoursHoverContainerWidth) hoverX = e.layerX; + if (hoursHoverContainerWidth) { + hoverX = e.layerX; + + $shadeMap?.setDate(hoveredHour); + } }); hoursHoverContainer.addEventListener('mouseout', () => { hoverX = 0; + $shadeMap?.setDate($time); }); hoursHoverContainer.addEventListener('click', () => { if (desktop.current) { From 22efeb599b6274428cf7ed10ad7a07269f754dcd Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Thu, 29 Jan 2026 14:25:32 +0100 Subject: [PATCH 5/7] smoother scrolling --- src/lib/components/time/time-selector.svelte | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index 7e66f471..72a1254a 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -457,19 +457,23 @@ // generates all possible time steps for the current day const timeStepsComplete = $derived.by(() => { - const timeStepsComplete = []; - for (let day of daySteps) { - for (let i = 0; i <= 23; i++) { - if (metaFirstResolutionHours === 0.25) { - for (let j = 0; j < 60; j += 15) { - timeStepsComplete.push(withLocalTime(day, i, j)); + if (metaFirstResolutionHours) { + const timeStepsComplete = []; + for (let day of daySteps) { + for (let i = 0; i <= 23; i++) { + if (metaFirstResolutionHours === 0.25) { + for (let j = 0; j < 60; j += 15) { + timeStepsComplete.push(withLocalTime(day, i, j)); + } + } else { + timeStepsComplete.push(withLocalTime(day, i)); } - } else { - timeStepsComplete.push(withLocalTime(day, i)); } } + return timeStepsComplete; + } else { + return undefined; } - return timeStepsComplete; }); // state variables for mouse interaction and scrolling behavior @@ -706,7 +710,7 @@ const throttledScrollEvent = throttle((e: Event) => { onScrollEvent(e); - }, 25); + }, 0); const throttledScrollEndEvent = throttle(() => { onScrollEndEvent(); From f267525cd0ed82b550cb822181a8a0d5770aefe5 Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Thu, 29 Jan 2026 17:23:13 +0100 Subject: [PATCH 6/7] setDate when map loaded --- src/lib/components/time/time-selector.svelte | 1 - src/lib/index.ts | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index e3cfa743..5fb7071f 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -290,7 +290,6 @@ } $time = new SvelteDate(date); - $shadeMap?.setDate($time); currentDate = date; updateUrl('time', formatISOWithoutTimezone($time)); await checkClosestModelRun(); diff --git a/src/lib/index.ts b/src/lib/index.ts index 8348cf40..73a66d4b 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -32,6 +32,7 @@ import { opacity, preferences as p, resolution as r, + shadeMap as sM, tileSize as tS, time, url as u @@ -603,6 +604,8 @@ const checkRasterLoaded = () => { } checked = 0; loading.set(false); + const shadeMap = get(sM); + if (shadeMap) shadeMap.setDate(get(time)); clearInterval(checkRasterSourceLoadedInterval); } }, 50); From f3fbc585fe690f0289795da35ed8f4bf7f269cd3 Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Sun, 1 Feb 2026 16:38:39 +0100 Subject: [PATCH 7/7] nullchecks --- src/lib/components/time/time-selector.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/components/time/time-selector.svelte b/src/lib/components/time/time-selector.svelte index 5fb7071f..93c90b29 100644 --- a/src/lib/components/time/time-selector.svelte +++ b/src/lib/components/time/time-selector.svelte @@ -484,7 +484,7 @@ let isScrolling = $state(false); const centerDateButton = (date: Date, smooth = true) => { - if (dayContainer) { + if (dayContainer && timeStepsComplete) { const index = timeStepsComplete.findIndex((tSC) => tSC.getTime() === date.getTime()); if (index !== -1) { if (desktop.current) { @@ -589,7 +589,7 @@ $shadeMap?.setDate($time); }); hoursHoverContainer.addEventListener('click', () => { - if (desktop.current) { + if (desktop.current && timeStepsComplete) { let validTime = false; let timeStep = timeStepsComplete[ @@ -652,7 +652,7 @@ }); const onScrollEvent = (e: Event) => { - if (isScrolling) return; + if (isScrolling || !timeStepsComplete) return; const target = e.target as Element; const left = target.scrollLeft; @@ -682,7 +682,7 @@ // Clear isScrolling flag when scrolling ends isScrolling = false; - if (!desktop.current && !isDown) { + if (!desktop.current && !isDown && timeStepsComplete) { if ($loading) { centerDateButton($time); currentDate = new SvelteDate($time); @@ -865,7 +865,7 @@