Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions components/src/maplibre/Map/Map.stories.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script context="module" lang="ts">
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import { within, expect } from 'storybook/test';
import { within, expect, userEvent, fn } from 'storybook/test';

import Map from './Map.svelte';
import ScaleControl from '../ScaleControl/ScaleControl.svelte';
Expand All @@ -12,6 +12,8 @@
import { SWRDataLabLight } from '../MapStyle';

import { eclipse } from '@versatiles/style';
import { MapContext } from '../context.svelte';

const alternateStyle = eclipse({
language: 'de',
baseUrl: 'https://tiles.versatiles.org',
Expand All @@ -22,6 +24,9 @@
title: 'Maplibre/Map',
component: Map
});

let mapContext: MapContext;
const onMoveStart = fn();
</script>

<Story
Expand Down Expand Up @@ -75,8 +80,59 @@
</div>
</Story>

<Story
asChild
name="External Controls"
play={async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const containerEl = canvas.getByTestId('map-container');
const flyToBerlinButton = canvas.getByTestId('flyto-berlin');

await step('map renders', async () => {
const mapEl = containerEl.querySelector('.maplibregl-canvas');
expect(mapEl).toBeTruthy();
});
await step('mapContext prop receives valid data', async () => {
expect(mapContext).toBeInstanceOf(MapContext);
});
await step('external trigger map events', async () => {
await userEvent.click(flyToBerlinButton);
expect(onMoveStart).toHaveBeenCalled();
// We'd like to expext.poll() for onMoveEnd.toHaveBeenCalled()
// here but that API doesn't exist in Storybook, see:
// https://github.com/storybookjs/storybook/issues/29060
});
}}
>
<div class="container">
<DesignTokens>
<button
data-testid="flyto-berlin"
onclick={() => {
mapContext.map?.flyTo({ center: [13.3849, 52.5026], zoom: 9.8 });
}}>Fly to Berlin</button
>
<button
data-testid="flyto-origin"
onclick={() => {
mapContext.map?.flyTo({ center: [10.43, 50.88], zoom: 5 });
}}>Reset</button
>
<Map
style={SWRDataLabLight()}
initialLocation={{ lat: 50.88, lng: 10.43, zoom: 5 }}
showDebug
onmovestart={onMoveStart}
bind:mapContext
>
<AttributionControl />
</Map>
</DesignTokens>
</div>
</Story>

<Story asChild name="Globe Projection">
<div class="container dark">
<div class="container">
<DesignTokens>
<Map
style={SWRDataLabLight()}
Expand Down
28 changes: 24 additions & 4 deletions components/src/maplibre/Map/Map.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<script lang="ts">
import maplibre, { type ProjectionSpecification, type StyleSpecification } from 'maplibre-gl';
import maplibre, {
type MapLibreEvent,
type ProjectionSpecification,
type StyleSpecification
} from 'maplibre-gl';
import { onMount, onDestroy, type Snippet, getContext, hasContext } from 'svelte';
import { createMapContext } from '../context.svelte.js';
import { createMapContext, MapContext } from '../context.svelte.js';
import { type Location } from '../types';
import FallbackStyle from './FallbackStyle';

Expand All @@ -20,6 +24,9 @@
projection?: ProjectionSpecification;
showDebug?: boolean;
options?: any;
mapContext?: MapContext;
onmovestart?: (e: MapLibreEvent) => null;
onmoveend?: (e: MapLibreEvent) => null;
children?: Snippet;
}

Expand All @@ -38,7 +45,12 @@
allowRotation = false,
allowZoom = true,
showDebug = false,
initialLocation: receivedInitialLocation
initialLocation: receivedInitialLocation,
// Future: This should become bindable.readonly when that becomes
// available, see: https://github.com/sveltejs/svelte/issues/7712
mapContext = $bindable(),
onmoveend,
onmovestart
}: MapProps = $props();

let container: HTMLElement;
Expand All @@ -53,7 +65,7 @@
...receivedInitialLocation
};

const mapContext = createMapContext();
mapContext = createMapContext();
if (getContext('initialLocation') !== undefined && getContext('initialLocation') !== false) {
initialLocation = getContext('initialLocation');
}
Expand Down Expand Up @@ -85,6 +97,13 @@
pitch = mapContext.map?.getPitch();
bearing = mapContext.map?.getBearing();
});

if (onmoveend) {
mapContext.map.on('moveend', onmoveend);
}
if (onmovestart) {
mapContext.map.on('movestart', onmovestart);
}
});

onDestroy(async () => {
Expand All @@ -94,6 +113,7 @@
$effect(() => {
if (mapContext.map) mapContext.map.setStyle(style);
});

$effect(() => {
if (mapContext.styleLoaded) {
mapContext.map?.setProjection(projection);
Expand Down
4 changes: 2 additions & 2 deletions components/src/maplibre/MapStyle/SWRDataLabLight.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Light-themed general-purpose basemap using SWR colours and typefaces based on
[versatiles-greybeard](https://github.com/versatiles-org/versatiles-style).

- Data sources
- Vector data: [versatiles-osm](https://download.versatiles.org/) in the [Shortbread schema](https://shortbread-tiles.org/) served from `tiles.versatiles.org`
- Building footprints with heights: [basemap-de](https://basemap.de/produkte-und-dienste/web-vektor/) vector tiles served from `sgx.geodatenzentrum.de`
- Vector data: [versatiles-osm](https://download.versatiles.org/) in the [Shortbread schema](https://shortbread-tiles.org/) served from `tiles.versatiles.org` ([License](https://docs.versatiles.org/basics/tilesets.html#license--attribution))
- Building footprints with heights: [basemap-de](https://basemap.de/produkte-und-dienste/web-vektor/) vector tiles served from `sgx.geodatenzentrum.de` ([License](https://sgx.geodatenzentrum.de/web_public/gdz/lizenz/deu/Nutzungsbedingungen_basemapde.pdf))
- Typefaces: SWR Sans served from `https://static.datenhub.net/maps/fonts/`.
- Layer count: {SWRDataLabLight({enableBuildingExtrusions: true}).layers.length} (including building extrusions)

Expand Down
2 changes: 2 additions & 0 deletions components/src/maplibre/Maplibre.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ This example initialises a map using the SWRDataLabLight style, adds an addition
</style>
```

- `mapContext` is [bindable](https://svelte.dev/docs/svelte/$bindable), so you can access MapLibre APIs from outside the `<Map>` component. This is useful if you want to control the map programatically, ie. in a scrollytelling application.

## Available components

### Sources
Expand Down