This repository has been archived on 2026-08-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
drizzli/src/routes/weather/maps/+page.svelte
T

100 lines
3.8 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import { storedLocation, storedModel, storedTheme } from '$lib/stores/settings';
import { mapsDomainForModel } from '$lib/utils/maps-domain';
// Hash piping, both directions:
// - inbound: a #zoom/lat/lng(/bearing/pitch) hash on OUR url seeds the
// map, so positions can be bookmarked/shared via drizzli links
// - outbound: the (cross-origin) map posts its hash here on every moveend;
// we mirror it into our url with replaceState. hashOverride is only set
// once on mount, so these mirror updates never reload the iframe.
let hashOverride = $state<string | null>(null);
let iframeEl = $state<HTMLIFrameElement | null>(null);
let mapReady = $state(false);
const postToMap = (message: Record<string, unknown>) => {
iframeEl?.contentWindow?.postMessage(message, MAPS_ORIGIN);
};
// Local maps dev server (open-meteo/maps); production: https://maps.open-meteo.com
// Run drizzli on a different port so the map keeps 5173 to itself.
// const MAPS_ORIGIN = 'http://localhost:5173';
const MAPS_ORIGIN = 'https://maps.open-meteo.com';
const MAP_HASH_RE = /^#\d+(\.\d+)?\/-?\d+(\.\d+)?\/-?\d+(\.\d+)?/;
onMount(() => {
const initialHash = window.location.hash;
hashOverride = MAP_HASH_RE.test(initialHash) ? initialHash : null;
const onMessage = (event: MessageEvent) => {
if (event.origin !== MAPS_ORIGIN) return;
const { type, hash, domains } = (event.data ?? {}) as {
type?: string;
hash?: string;
domains?: string[];
};
if (type === 'om-maps:hash') {
if (!hash || !MAP_HASH_RE.test(hash)) return;
history.replaceState(history.state, '', hash);
} else if (type === 'om-maps:ready' && Array.isArray(domains)) {
// The map is loaded and advertises which domains it can render;
// switch it to the selected model and our theme. The domain is
// omitted for best_match and models the map does not serve,
// keeping the map's own default. Re-fires on iframe reloads.
mapReady = true;
const domain = mapsDomainForModel($storedModel, new Set(domains));
postToMap({ type: 'om-maps:set', ...(domain && { domain }), theme: $storedTheme });
}
};
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
});
// the embedded map understands maplibre's #zoom/lat/lng hash, so the iframe
// opens focused on the selected location (zoomed out to regional scale);
// picking a new location while on this page recenters the map
const iframeSrc = $derived(
`${MAPS_ORIGIN}/${
hashOverride ??
`#6/${$storedLocation.latitude.toFixed(3)}/${$storedLocation.longitude.toFixed(3)}`
}`
);
// forward theme switches live; the initial theme travels with the
// ready response above (the map ignores no-op updates)
$effect(() => {
const theme = $storedTheme;
if (!mapReady) return;
postToMap({ type: 'om-maps:set', theme });
});
</script>
<svelte:head>
<title>Weather Map | Open-Meteo.com</title>
<link rel="canonical" href="https://open-meteo.com/weather/maps" />
<meta name="description" content="Interactive weather map powered by Open-Meteo" />
</svelte:head>
<!-- Full-bleed map: the layout drops its padding for this route. The map
follows our theme through the color-scheme declared on :root/.dark -->
<div class="h-full w-full bg-background">
<!-- allow="cross-origin-isolated" delegates SharedArrayBuffer use to the
map; it only takes effect when this site itself is served with
COOP/COEP headers (see README, Deployment) -->
<iframe
bind:this={iframeEl}
src={iframeSrc}
title="Open-Meteo Interactive Map"
loading="lazy"
allowfullscreen
allow="cross-origin-isolated"
referrerpolicy="no-referrer"
class="block h-full w-full border-0"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
></iframe>
</div>