diff --git a/components/src/Switcher/Switcher.stories.svelte b/components/src/Switcher/Switcher.stories.svelte index 64c85962..c7a6a7ae 100644 --- a/components/src/Switcher/Switcher.stories.svelte +++ b/components/src/Switcher/Switcher.stories.svelte @@ -62,7 +62,6 @@ asChild play={async ({ canvasElement, step }) => { const canvas = within(canvasElement); - console.log(canvas); await step('Options are displayed in a row', async () => { const list = canvas.getByRole('list'); expect(list.classList).toContain('layout-row'); @@ -84,7 +83,6 @@ asChild play={async ({ canvasElement, step }) => { const canvas = within(canvasElement); - console.log(canvas); await step('Options are displayed in a row', async () => { const list = canvas.getByRole('list'); expect(list.classList).toContain('layout-column'); diff --git a/components/src/maplibre/Map/Map.svelte b/components/src/maplibre/Map/Map.svelte index 4b2fbef8..4c1c534b 100644 --- a/components/src/maplibre/Map/Map.svelte +++ b/components/src/maplibre/Map/Map.svelte @@ -5,9 +5,12 @@ type ProjectionSpecification, type StyleSpecification } from 'maplibre-gl'; + + import { type Location } from '../types'; + import { onMount, onDestroy, type Snippet, getContext, hasContext } from 'svelte'; + import { createMapContext, MapContext } from '../context.svelte.js'; - import { type Location } from '../types'; import FallbackStyle from './FallbackStyle'; import { de } from './locale'; @@ -33,7 +36,7 @@ showDebug?: boolean; options?: any; /** - * Set the mouse cursor. `""` (empty string) restores Maplibre's default behaviour. See VectorLayer/Default for a common usage example + * Set the mouse cursor. `""` (empty string) restores Maplibre's default behaviour. See VectorLayer/Default for a usage example */ cursor?: string; mapContext?: MapContext; @@ -75,21 +78,23 @@ }: MapProps = $props(); let container: HTMLElement; + mapContext = createMapContext(); + + // Initial location is determined by (in order of precedence) : + // 1. An arbitrary default location + // 2. initialLocation prop + // 3. initialLocation context (notably set by ) - // Merge initial location with default object so individual - // properties (like pitch) can be omitted by the caller - let initialLocation = { + let contextLocation = getContext('initialLocation'); + + let initialLocation = $derived({ lat: 51.3, lng: 10.2, zoom: 5, pitch: 0, - ...receivedInitialLocation - }; - - mapContext = createMapContext(); - if (getContext('initialLocation') !== undefined && getContext('initialLocation') !== false) { - initialLocation = getContext('initialLocation'); - } + ...receivedInitialLocation, + ...contextLocation + }); onMount(() => { mapContext.map = new maplibre.Map({ @@ -151,6 +156,12 @@ } }); + $effect(() => { + mapContext.map?.jumpTo({ + center: [initialLocation.lng, initialLocation.lat], + zoom: initialLocation.zoom + }); + }); $effect(() => { if (allowZoom === false) { mapContext.map?.scrollZoom.disable(); diff --git a/components/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte b/components/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte index 3c7bfa00..1b497f04 100644 --- a/components/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte +++ b/components/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte @@ -97,7 +97,7 @@ > @@ -150,7 +150,7 @@ diff --git a/components/src/maplibre/Tooltip/Tooltip.stories.svelte b/components/src/maplibre/Tooltip/Tooltip.stories.svelte index 106588a6..d53750f8 100644 --- a/components/src/maplibre/Tooltip/Tooltip.stories.svelte +++ b/components/src/maplibre/Tooltip/Tooltip.stories.svelte @@ -33,7 +33,9 @@ > + + + + +``` + +- This component includes a workaround to support for [TileJSON data with relative URLs](https://docs.versatiles.org/compendium/specification_extended_tilejson.html#relaxed-rule-for-tiles) as produced by versatiles-rs. +- TileJSON data can by overridden by passing the relevant props to the component directly. + +## Using tile URLs directly + +Alternatively, you can point to a tile URL directly using the `tiles` prop: ```jsx - + - + ``` diff --git a/components/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte b/components/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte index 91a27177..c1a4922f 100644 --- a/components/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte +++ b/components/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte @@ -1,6 +1,8 @@ + +
+ + + + + +
+
+
+ + + +
+ + + + + +
+
+
+ +
diff --git a/components/src/maplibre/VectorTileSource/VectorTileSource.svelte b/components/src/maplibre/VectorTileSource/VectorTileSource.svelte index 62761ac7..0a09eb02 100644 --- a/components/src/maplibre/VectorTileSource/VectorTileSource.svelte +++ b/components/src/maplibre/VectorTileSource/VectorTileSource.svelte @@ -1,27 +1,43 @@ diff --git a/components/src/maplibre/VectorTileSource/fetchTileJson.ts b/components/src/maplibre/VectorTileSource/fetchTileJson.ts new file mode 100644 index 00000000..3d6fe0f2 --- /dev/null +++ b/components/src/maplibre/VectorTileSource/fetchTileJson.ts @@ -0,0 +1,20 @@ +import { type TileJsonData } from './types'; + +// Workaround for https://github.com/versatiles-org/versatiles-rs/issues/184 +// Drop when/if this lands: https://github.com/maplibre/maplibre-gl-js/issues/182 + +export default async function fetchTileJSON(url: string): Promise { + const u = new URL(url); + const res = await fetch(u); + const data = await res.json(); + + // Simple heuristic for absolute URLs + const re = /(((http)s?)):\/\/.*/gi; + + return { + tiles: data?.tiles.map((path: string) => (re.test(path) ? path : `${u.origin}${path}`)), + attribution: data?.attribution || data?.author, + minZoom: data?.minzoom, + maxZoom: data?.maxzoom + }; +} diff --git a/components/src/maplibre/VectorTileSource/types.ts b/components/src/maplibre/VectorTileSource/types.ts new file mode 100644 index 00000000..827ce3bb --- /dev/null +++ b/components/src/maplibre/VectorTileSource/types.ts @@ -0,0 +1,8 @@ +interface TileJsonData { + tiles?: string[]; + attribution?: string; + minZoom?: number; + maxZoom?: number; +} + +export { type TileJsonData }; diff --git a/components/src/maplibre/WithLinkLocation/WithLinkLocation.mdx b/components/src/maplibre/WithLinkLocation/WithLinkLocation.mdx index 564e1cda..05679890 100644 --- a/components/src/maplibre/WithLinkLocation/WithLinkLocation.mdx +++ b/components/src/maplibre/WithLinkLocation/WithLinkLocation.mdx @@ -6,6 +6,8 @@ import * as WithLinkLocationStories from './WithLinkLocation.stories.svelte'; # WithLinkLocation +This component allows you to set a map's initial location using a forward-geocoded URL parameter, as in: [http://localhost:6006/iframe.html?id=maplibre-extras-withlinklocation--default&viewMode=story&location=berlin](http://localhost:6006/iframe.html?id=maplibre-extras-withlinklocation--default&viewMode=story&location=berlin) (note `location` parameter at the end of the URL).This can be useful for multi-page visualisation scenarios. + diff --git a/components/src/maplibre/WithLinkLocation/WithLinkLocation.svelte b/components/src/maplibre/WithLinkLocation/WithLinkLocation.svelte index 71c7c64e..30df757f 100644 --- a/components/src/maplibre/WithLinkLocation/WithLinkLocation.svelte +++ b/components/src/maplibre/WithLinkLocation/WithLinkLocation.svelte @@ -23,7 +23,13 @@ * Limit search to one or more countries */ countries?: GeocodingLanguage | GeocodingCountry[]; + /** + * Limit search to one or more languages + */ languages?: GeocodingLanguage | GeocodingLanguage[]; + /** + * Customise the URL parameter used to set the initial location + */ urlParameter?: string; children: Snippet; } @@ -41,18 +47,20 @@ const languagesArr = Array.isArray(languages) ? languages : [languages]; let geocoder: MaplibreGeocoderApi; + if (service === 'maptiler') { geocoder = new MaptilerGeocoderAPI(key); } - let location: Location | boolean | undefined = $state(); + let location: any = $state({}); function bboxToArea(bbox: [number, number, number, number]) { return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]); } - onMount(async () => { + $effect(async () => { const params = new URLSearchParams(window.location.search); + if (params.has(urlParameter)) { const config: MaplibreGeocoderApiConfig = { countries: countriesArr.join(','), @@ -60,24 +68,21 @@ query: params.get(urlParameter)?.toString(), limit: 1 }; + const res = await geocoder.forwardGeocode(config); + if (res.features[0].bbox && res.features[0].geometry.type === 'Point') { - location = { - lat: res.features[0].geometry.coordinates[1], - lng: res.features[0].geometry.coordinates[0], - zoom: 11 - bboxToArea(res.features[0].bbox) * 5.5 - }; + location.lat = res.features[0].geometry.coordinates[1]; + location.lng = res.features[0].geometry.coordinates[0]; + location.zoom = 11 - bboxToArea(res.features[0].bbox) * 5.5; + location.active = true; } } else { - location = false; + location = {}; } }); - $effect.pre(() => { - setContext('initialLocation', location); - }); + setContext('initialLocation', location); -{#if location !== undefined} - {@render children?.()} -{/if} +{@render children?.()} diff --git a/components/svelte.config.js b/components/svelte.config.js index a356bb48..41649442 100644 --- a/components/svelte.config.js +++ b/components/svelte.config.js @@ -8,7 +8,11 @@ const config = { // We can't use vitePreprocess() here, see: https://github.com/sveltejs/kit/issues/13122 preprocess: sveltePreprocess({ scss: true }), - + compilerOptions: { + experimental: { + async: true + } + }, kit: { adapter: adapter() }