@swr-data-lab/components 1.11.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/blocks/Mermaid.jsx +9 -0
- package/.storybook/main.ts +23 -17
- package/.storybook/preview.ts +42 -13
- package/package.json +72 -68
- package/src/DesignTokens/Tokens.ts +15 -12
- package/src/Select/Select.stories.svelte +3 -3
- package/src/Switcher/Switcher.svelte +1 -0
- package/src/index.js +12 -0
- package/src/maplibre/AttributionControl/AttributionControl.stories.svelte +29 -0
- package/src/maplibre/AttributionControl/AttributionControl.svelte +45 -0
- package/src/maplibre/AttributionControl/index.js +2 -0
- package/src/maplibre/GeocoderControl/GeocoderAPIs.ts +49 -0
- package/src/maplibre/GeocoderControl/GeocoderControl.stories.svelte +78 -0
- package/src/maplibre/GeocoderControl/GeocoderControl.svelte +207 -0
- package/src/maplibre/GeocoderControl/index.js +2 -0
- package/src/maplibre/Map/FallbackStyle.ts +18 -0
- package/src/maplibre/Map/Map.stories.svelte +118 -0
- package/src/maplibre/Map/Map.svelte +283 -0
- package/src/maplibre/Map/index.js +2 -0
- package/src/maplibre/MapControl/MapControl.mdx +12 -0
- package/src/maplibre/MapControl/MapControl.stories.svelte +56 -0
- package/src/maplibre/MapControl/MapControl.svelte +41 -0
- package/src/maplibre/MapControl/index.js +2 -0
- package/src/maplibre/MapStyle/SWRDataLabLight.mdx +86 -0
- package/src/maplibre/MapStyle/SWRDataLabLight.stories.svelte +41 -0
- package/src/maplibre/MapStyle/SWRDataLabLight.ts +72 -0
- package/src/maplibre/MapStyle/components/Admin.ts +173 -0
- package/src/maplibre/MapStyle/components/Buildings.ts +23 -0
- package/src/maplibre/MapStyle/components/Landuse.ts +499 -0
- package/src/maplibre/MapStyle/components/Natural.ts +1 -0
- package/src/maplibre/MapStyle/components/PlaceLabels.ts +199 -0
- package/src/maplibre/MapStyle/components/Roads.ts +2345 -0
- package/src/maplibre/MapStyle/components/Transit.ts +507 -0
- package/src/maplibre/MapStyle/components/Walking.ts +1538 -0
- package/src/maplibre/MapStyle/index.js +2 -0
- package/src/maplibre/MapStyle/tokens.ts +21 -0
- package/src/maplibre/Maplibre.mdx +91 -0
- package/src/maplibre/NavigationControl/NavigationControl.stories.svelte +39 -0
- package/src/maplibre/NavigationControl/NavigationControl.svelte +36 -0
- package/src/maplibre/NavigationControl/index.js +2 -0
- package/src/maplibre/ScaleControl/ScaleControl.stories.svelte +71 -0
- package/src/maplibre/ScaleControl/ScaleControl.svelte +25 -0
- package/src/maplibre/ScaleControl/index.js +2 -0
- package/src/maplibre/Source/MapSource.stories.svelte +9 -0
- package/src/maplibre/Source/MapSource.svelte +61 -0
- package/src/maplibre/Source/index.js +2 -0
- package/src/maplibre/Source/source.ts +89 -0
- package/src/maplibre/Tooltip/Tooltip.stories.svelte +192 -0
- package/src/maplibre/Tooltip/Tooltip.svelte +175 -0
- package/src/maplibre/Tooltip/index.js +2 -0
- package/src/maplibre/VectorLayer/VectorLayer.stories.svelte +65 -0
- package/src/maplibre/VectorLayer/VectorLayer.svelte +142 -0
- package/src/maplibre/VectorLayer/index.js +2 -0
- package/src/maplibre/VectorTileSource/VectorTileSource.mdx +19 -0
- package/src/maplibre/VectorTileSource/VectorTileSource.stories.svelte +46 -0
- package/src/maplibre/VectorTileSource/VectorTileSource.svelte +24 -0
- package/src/maplibre/VectorTileSource/index.js +2 -0
- package/src/maplibre/WithLinkLocation/WithLinkLocation.mdx +11 -0
- package/src/maplibre/WithLinkLocation/WithLinkLocation.stories.svelte +29 -0
- package/src/maplibre/WithLinkLocation/WithLinkLocation.svelte +83 -0
- package/src/maplibre/WithLinkLocation/index.js +2 -0
- package/src/maplibre/context.svelte.ts +89 -0
- package/src/maplibre/types.ts +12 -0
- package/src/maplibre/utils.ts +52 -0
- package/tsconfig.json +1 -0
- package/src/events/clickOutside.js +0 -23
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy, type Snippet } from 'svelte';
|
|
3
|
+
import { type Listener, type LngLatLike, Popup } from 'maplibre-gl';
|
|
4
|
+
import { getMapContext } from '../context.svelte.ts';
|
|
5
|
+
import { resetPopupEventListener } from '../utils';
|
|
6
|
+
|
|
7
|
+
interface TooltipProps {
|
|
8
|
+
position: LngLatLike | undefined;
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
maxWidth?: number;
|
|
11
|
+
showCloseButton?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Close the tooltip if the user clicks anywhere on the map
|
|
14
|
+
*/
|
|
15
|
+
closeOnClick?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* y axis offset (px)
|
|
18
|
+
*/
|
|
19
|
+
offset?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Toggle mouse events on the tooltip element. Should be false if the tooltip appears on hover to avoid flickering.
|
|
22
|
+
*/
|
|
23
|
+
mouseEvents?: boolean;
|
|
24
|
+
onClose?: Listener | undefined;
|
|
25
|
+
onOpen?: Listener | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
position,
|
|
30
|
+
children,
|
|
31
|
+
offset = 20,
|
|
32
|
+
maxWidth = 360,
|
|
33
|
+
showCloseButton = false,
|
|
34
|
+
closeOnClick = true,
|
|
35
|
+
mouseEvents = false,
|
|
36
|
+
onClose,
|
|
37
|
+
onOpen
|
|
38
|
+
}: TooltipProps = $props();
|
|
39
|
+
|
|
40
|
+
const { map } = $derived(getMapContext());
|
|
41
|
+
|
|
42
|
+
let tooltip = new Popup({
|
|
43
|
+
closeButton: showCloseButton,
|
|
44
|
+
closeOnClick: closeOnClick,
|
|
45
|
+
maxWidth: `${maxWidth}px`,
|
|
46
|
+
offset: offset
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let el: Node | undefined = $state();
|
|
50
|
+
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (position && el && map) {
|
|
53
|
+
tooltip.setLngLat(position).setDOMContent(el).addTo(map);
|
|
54
|
+
} else {
|
|
55
|
+
tooltip.remove();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Bind events
|
|
60
|
+
$effect(() => resetPopupEventListener(tooltip, 'open', onOpen));
|
|
61
|
+
$effect(() => resetPopupEventListener(tooltip, 'close', onClose));
|
|
62
|
+
|
|
63
|
+
onDestroy(() => tooltip.remove());
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div bind:this={el} class="container" class:mouseEvents>
|
|
67
|
+
{@render children?.()}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
.container {
|
|
72
|
+
background: white;
|
|
73
|
+
padding: 0.65em;
|
|
74
|
+
border-radius: 2px;
|
|
75
|
+
border: 1px solid rgba(0, 0, 0, 0.75);
|
|
76
|
+
filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1));
|
|
77
|
+
pointer-events: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.mouseEvents {
|
|
81
|
+
pointer-events: all;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:global {
|
|
85
|
+
.maplibregl-popup {
|
|
86
|
+
top: 0;
|
|
87
|
+
left: 0;
|
|
88
|
+
display: flex;
|
|
89
|
+
position: absolute;
|
|
90
|
+
will-change: transform;
|
|
91
|
+
}
|
|
92
|
+
.maplibregl-popup-anchor-top,
|
|
93
|
+
.maplibregl-popup-anchor-top-left,
|
|
94
|
+
.maplibregl-popup-anchor-top-right {
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
}
|
|
97
|
+
.maplibregl-popup-anchor-bottom,
|
|
98
|
+
.maplibregl-popup-anchor-bottom-left,
|
|
99
|
+
.maplibregl-popup-anchor-bottom-right {
|
|
100
|
+
flex-direction: column-reverse;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.maplibregl-popup-anchor-left {
|
|
104
|
+
flex-direction: row;
|
|
105
|
+
}
|
|
106
|
+
.maplibregl-popup-anchor-right {
|
|
107
|
+
flex-direction: row-reverse;
|
|
108
|
+
}
|
|
109
|
+
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip {
|
|
110
|
+
transform: translateY(50%) rotate(45deg);
|
|
111
|
+
align-self: center;
|
|
112
|
+
}
|
|
113
|
+
.maplibregl-popup-anchor-top .maplibregl-popup-tip {
|
|
114
|
+
transform: translateY(-50%) rotate(-135deg);
|
|
115
|
+
align-self: center;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.maplibregl-popup-anchor-left .maplibregl-popup-tip {
|
|
119
|
+
transform: translateX(-50%) rotate(135deg);
|
|
120
|
+
align-self: center;
|
|
121
|
+
}
|
|
122
|
+
.maplibregl-popup-anchor-top-left .maplibregl-popup-tip {
|
|
123
|
+
transform: translateY(1em) translateX(-50%) rotate(135deg);
|
|
124
|
+
}
|
|
125
|
+
.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip {
|
|
126
|
+
transform: translateY(-1em) translateX(-50%) rotate(135deg);
|
|
127
|
+
}
|
|
128
|
+
.maplibregl-popup-anchor-right .maplibregl-popup-tip {
|
|
129
|
+
transform: translateX(50%) rotate(-45deg);
|
|
130
|
+
align-self: center;
|
|
131
|
+
}
|
|
132
|
+
.maplibregl-popup-anchor-top-right .maplibregl-popup-tip {
|
|
133
|
+
transform: translateX(-1em) translateY(-50%) rotate(-135deg);
|
|
134
|
+
align-self: flex-end;
|
|
135
|
+
}
|
|
136
|
+
.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip {
|
|
137
|
+
transform: translateX(-1em) translateY(50%) rotate(45deg);
|
|
138
|
+
align-self: flex-end;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.maplibregl-popup-close-button {
|
|
142
|
+
background-color: transparent;
|
|
143
|
+
border: 0;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
position: absolute;
|
|
146
|
+
top: 0.45em;
|
|
147
|
+
right: 0.45em;
|
|
148
|
+
display: flex;
|
|
149
|
+
border-radius: var(--br-small);
|
|
150
|
+
justify-content: center;
|
|
151
|
+
align-items: center;
|
|
152
|
+
padding-bottom: 0.1em;
|
|
153
|
+
border: 1px solid var(--violet-dark-5);
|
|
154
|
+
font-size: 1.2rem;
|
|
155
|
+
width: 1em;
|
|
156
|
+
height: 1em;
|
|
157
|
+
z-index: 100;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.maplibregl-popup-close-button:hover,
|
|
161
|
+
.maplibregl-popup-close-button:focus {
|
|
162
|
+
background: rgb(245, 245, 245);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.maplibregl-popup-tip {
|
|
166
|
+
width: 0.6rem;
|
|
167
|
+
height: 0.6rem;
|
|
168
|
+
background: white;
|
|
169
|
+
position: absolute;
|
|
170
|
+
border-right: 1px solid rgba(0, 0, 0, 0.75);
|
|
171
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.75);
|
|
172
|
+
z-index: 10;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import Map from '../Map/Map.svelte';
|
|
4
|
+
import VectorLayer from './VectorLayer.svelte';
|
|
5
|
+
import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
|
|
6
|
+
import AttributionControl from '../AttributionControl/AttributionControl.svelte';
|
|
7
|
+
import VectorTileSource from '../VectorTileSource/VectorTileSource.svelte';
|
|
8
|
+
|
|
9
|
+
import { SWRDataLabLight } from '../MapStyle';
|
|
10
|
+
|
|
11
|
+
const { Story } = defineMeta({
|
|
12
|
+
title: 'Maplibre/Layer/VectorLayer',
|
|
13
|
+
component: VectorLayer
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<Story asChild name="Default">
|
|
18
|
+
<DesignTokens>
|
|
19
|
+
<div class="container">
|
|
20
|
+
<Map showDebug={true} style={SWRDataLabLight}>
|
|
21
|
+
<VectorTileSource
|
|
22
|
+
id="ev-infra-source"
|
|
23
|
+
url={`https://static.datenhub.net/data/p108_e_auto_check/ev_infra_merged.versatiles?tiles/{z}/{x}/{y}`}
|
|
24
|
+
/>
|
|
25
|
+
<VectorLayer
|
|
26
|
+
sourceId="ev-infra-source"
|
|
27
|
+
type="fill"
|
|
28
|
+
id="coverage-fill"
|
|
29
|
+
sourceLayer="coverage"
|
|
30
|
+
placeBelow="street-residential"
|
|
31
|
+
paint={{
|
|
32
|
+
'fill-color': [
|
|
33
|
+
'step',
|
|
34
|
+
['get', 'coverage_2025'],
|
|
35
|
+
'white',
|
|
36
|
+
1,
|
|
37
|
+
'lightgray',
|
|
38
|
+
1.3,
|
|
39
|
+
'lightgreen'
|
|
40
|
+
]
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
<VectorLayer
|
|
44
|
+
sourceId="ev-infra-source"
|
|
45
|
+
sourceLayer="coverage"
|
|
46
|
+
id="ev-infra-outline"
|
|
47
|
+
type="line"
|
|
48
|
+
paint={{
|
|
49
|
+
'line-width': 0.5,
|
|
50
|
+
'line-color': 'purple',
|
|
51
|
+
'line-opacity': 1
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
<AttributionControl />
|
|
55
|
+
</Map>
|
|
56
|
+
</div>
|
|
57
|
+
</DesignTokens>
|
|
58
|
+
</Story>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.container {
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 600px;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
AddLayerObject,
|
|
4
|
+
FillLayoutProps,
|
|
5
|
+
FillPaintProps,
|
|
6
|
+
LineLayoutProps,
|
|
7
|
+
LinePaintProps,
|
|
8
|
+
MapGeoJSONFeature,
|
|
9
|
+
MapLayerMouseEvent
|
|
10
|
+
} from 'maplibre-gl';
|
|
11
|
+
|
|
12
|
+
import { getMapContext } from '../context.svelte.ts';
|
|
13
|
+
import { onDestroy } from 'svelte';
|
|
14
|
+
import { resetLayerEventListener } from '../utils.ts';
|
|
15
|
+
|
|
16
|
+
interface VectorLayerProps {
|
|
17
|
+
id: string;
|
|
18
|
+
sourceId: string;
|
|
19
|
+
sourceLayer: string;
|
|
20
|
+
type: 'line' | 'fill';
|
|
21
|
+
placeBelow: string;
|
|
22
|
+
visible?: boolean;
|
|
23
|
+
minZoom?: number;
|
|
24
|
+
maxZoom?: number;
|
|
25
|
+
paint?: LinePaintProps | FillPaintProps;
|
|
26
|
+
layout?: LineLayoutProps | FillLayoutProps;
|
|
27
|
+
hovered?: MapGeoJSONFeature | undefined;
|
|
28
|
+
selected?: MapGeoJSONFeature | undefined;
|
|
29
|
+
|
|
30
|
+
onclick: (e: MapLayerMouseEvent) => any;
|
|
31
|
+
onmousemove: (e: MapLayerMouseEvent) => any;
|
|
32
|
+
onmouseleave: (e: MapLayerMouseEvent) => any;
|
|
33
|
+
}
|
|
34
|
+
const {
|
|
35
|
+
id,
|
|
36
|
+
sourceId,
|
|
37
|
+
sourceLayer,
|
|
38
|
+
visible = true,
|
|
39
|
+
placeBelow = 'label-place-capital',
|
|
40
|
+
type,
|
|
41
|
+
paint,
|
|
42
|
+
layout,
|
|
43
|
+
hovered = $bindable(),
|
|
44
|
+
selected = $bindable(),
|
|
45
|
+
minZoom = 0,
|
|
46
|
+
maxZoom = 24,
|
|
47
|
+
onclick,
|
|
48
|
+
onmousemove,
|
|
49
|
+
onmouseleave
|
|
50
|
+
}: VectorLayerProps = $props();
|
|
51
|
+
|
|
52
|
+
const { map, styleLoaded } = $derived(getMapContext());
|
|
53
|
+
let beforeId: string | undefined = $state();
|
|
54
|
+
let prevSelected: string | number | undefined = $state();
|
|
55
|
+
let prevHovered: string | number | undefined = $state();
|
|
56
|
+
|
|
57
|
+
const layerSpec = {
|
|
58
|
+
id,
|
|
59
|
+
type,
|
|
60
|
+
source: sourceId,
|
|
61
|
+
'source-layer': sourceLayer,
|
|
62
|
+
layout: $state.snapshot(layout) ?? {},
|
|
63
|
+
paint: $state.snapshot(paint) ?? {},
|
|
64
|
+
minzoom: minZoom,
|
|
65
|
+
maxzoom: maxZoom
|
|
66
|
+
} as AddLayerObject;
|
|
67
|
+
|
|
68
|
+
$effect(() => {
|
|
69
|
+
if (map && styleLoaded) {
|
|
70
|
+
const style = map.getStyle();
|
|
71
|
+
beforeId = style.layers.find((l) => {
|
|
72
|
+
return l.id === placeBelow;
|
|
73
|
+
})?.id;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
$effect(() => {
|
|
78
|
+
if (map && styleLoaded && beforeId) {
|
|
79
|
+
map.addLayer(layerSpec, beforeId);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
$effect(() => resetLayerEventListener(map, 'click', id, onclick));
|
|
84
|
+
$effect(() => resetLayerEventListener(map, 'mousemove', id, onmousemove));
|
|
85
|
+
$effect(() => resetLayerEventListener(map, 'mouseleave', id, onmouseleave));
|
|
86
|
+
|
|
87
|
+
// Set hovered feature state
|
|
88
|
+
$effect(() => {
|
|
89
|
+
if (styleLoaded) {
|
|
90
|
+
if (hovered) {
|
|
91
|
+
if (prevHovered) {
|
|
92
|
+
map?.setFeatureState(
|
|
93
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: prevHovered },
|
|
94
|
+
{ hovered: false }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
map?.setFeatureState(
|
|
98
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: hovered.id },
|
|
99
|
+
{ hovered: true }
|
|
100
|
+
);
|
|
101
|
+
prevHovered = hovered.id;
|
|
102
|
+
} else {
|
|
103
|
+
if (prevHovered) {
|
|
104
|
+
map?.setFeatureState(
|
|
105
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: prevHovered },
|
|
106
|
+
{ hovered: false }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Set selected feature state
|
|
114
|
+
$effect(() => {
|
|
115
|
+
if (styleLoaded) {
|
|
116
|
+
if (selected) {
|
|
117
|
+
if (prevSelected) {
|
|
118
|
+
map?.setFeatureState(
|
|
119
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: prevSelected },
|
|
120
|
+
{ selected: false }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
map?.setFeatureState(
|
|
124
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: selected.id },
|
|
125
|
+
{ selected: true }
|
|
126
|
+
);
|
|
127
|
+
prevSelected = selected.id;
|
|
128
|
+
} else {
|
|
129
|
+
if (prevSelected) {
|
|
130
|
+
map?.setFeatureState(
|
|
131
|
+
{ source: sourceId, sourceLayer: sourceLayer, id: prevSelected },
|
|
132
|
+
{ selected: false }
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
onDestroy(() => {
|
|
140
|
+
if (map && map.getLayer(id)) map.removeLayer(id);
|
|
141
|
+
});
|
|
142
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Story, Meta, Primary, Controls, Stories } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
import * as VectorTileSourceStories from './VectorTileSource.stories.svelte';
|
|
4
|
+
|
|
5
|
+
<Meta of={VectorTileSourceStories} />
|
|
6
|
+
|
|
7
|
+
# VectorTileSource
|
|
8
|
+
|
|
9
|
+
Loads tiled vector data from a tileserver. Any mablibre-supported tileserver will work, but we'll typically use tiles served by `versatiles-rs` or `versatiles-node-static-proxy`.
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
<Map>
|
|
13
|
+
<VectorTileSource
|
|
14
|
+
url="https://static.datenhub.net/rent_merged.versatiles?tiles/{z}/{x}/{y}"
|
|
15
|
+
id="chargers"
|
|
16
|
+
/>
|
|
17
|
+
<VectorLayer sourceId="chargers" />
|
|
18
|
+
</Map>
|
|
19
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
|
|
4
|
+
import VectorTileSource from './VectorTileSource.svelte';
|
|
5
|
+
import VectorLayer from '../VectorLayer/VectorLayer.svelte';
|
|
6
|
+
import Map from '../Map/Map.svelte';
|
|
7
|
+
import { SWRDataLabLight } from '../MapStyle/';
|
|
8
|
+
import AttributionControl from '../AttributionControl';
|
|
9
|
+
|
|
10
|
+
const { Story } = defineMeta({
|
|
11
|
+
title: 'Maplibre/Source/VectorTileSource',
|
|
12
|
+
component: VectorTileSource
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<Story asChild name="Default">
|
|
17
|
+
<DesignTokens>
|
|
18
|
+
<div class="container">
|
|
19
|
+
<Map showDebug={true} style={SWRDataLabLight}>
|
|
20
|
+
<VectorTileSource
|
|
21
|
+
id="ev-infra-source"
|
|
22
|
+
url={`https://static.datenhub.net/data/p108_e_auto_check/ev_infra_merged.versatiles?tiles/{z}/{x}/{y}`}
|
|
23
|
+
/>
|
|
24
|
+
<VectorLayer
|
|
25
|
+
sourceId="ev-infra-source"
|
|
26
|
+
sourceLayer="coverage"
|
|
27
|
+
id="ev-infra-outline"
|
|
28
|
+
type="line"
|
|
29
|
+
paint={{
|
|
30
|
+
'line-width': 0.5,
|
|
31
|
+
'line-color': 'purple',
|
|
32
|
+
'line-opacity': 1
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<AttributionControl />
|
|
36
|
+
</Map>
|
|
37
|
+
</div>
|
|
38
|
+
</DesignTokens>
|
|
39
|
+
</Story>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.container {
|
|
43
|
+
width: 100%;
|
|
44
|
+
height: 600px;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { type Snippet } from 'svelte';
|
|
3
|
+
import { type SourceSpecification } from 'maplibre-gl';
|
|
4
|
+
import MapSource from '../Source/MapSource.svelte';
|
|
5
|
+
|
|
6
|
+
interface VectorTileSourceProps {
|
|
7
|
+
id: string;
|
|
8
|
+
url: string;
|
|
9
|
+
minZoom?: number;
|
|
10
|
+
maxZoom?: number;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { minZoom = 0, maxZoom = 24, id, url }: VectorTileSourceProps = $props();
|
|
15
|
+
|
|
16
|
+
const sourceSpec: SourceSpecification = {
|
|
17
|
+
type: 'vector',
|
|
18
|
+
tiles: [url],
|
|
19
|
+
maxzoom: maxZoom,
|
|
20
|
+
minzoom: minZoom
|
|
21
|
+
};
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<MapSource {id} {sourceSpec} />
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Story, Meta, Primary, Controls, Stories } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
import * as WithLinkLocationStories from './WithLinkLocation.stories.svelte';
|
|
4
|
+
|
|
5
|
+
<Meta of={WithLinkLocationStories} />
|
|
6
|
+
|
|
7
|
+
# WithLinkLocation
|
|
8
|
+
|
|
9
|
+
<Controls />
|
|
10
|
+
|
|
11
|
+
<Stories />
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import WithLinkLocation from './WithLinkLocation.svelte';
|
|
4
|
+
import DesignTokens from '../../DesignTokens/DesignTokens.svelte';
|
|
5
|
+
import Map from '../Map/Map.svelte';
|
|
6
|
+
import { SWRDataLabLight } from '../MapStyle';
|
|
7
|
+
|
|
8
|
+
const { Story } = defineMeta({
|
|
9
|
+
title: 'Maplibre/Extras/WithLinkLocation',
|
|
10
|
+
component: WithLinkLocation
|
|
11
|
+
});
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<Story asChild name="Default">
|
|
15
|
+
<DesignTokens>
|
|
16
|
+
<div class="container">
|
|
17
|
+
<WithLinkLocation countries="de" languages="de" service="maptiler" key="V32kPHZjMa0Mkn6YvSzA">
|
|
18
|
+
<Map style={SWRDataLabLight} initialLocation={{ lat: 51, lng: 10, zoom: 20 }}></Map>
|
|
19
|
+
</WithLinkLocation>
|
|
20
|
+
</div>
|
|
21
|
+
</DesignTokens>
|
|
22
|
+
</Story>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.container {
|
|
26
|
+
width: 500px;
|
|
27
|
+
height: 300px;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, setContext, type Snippet } from 'svelte';
|
|
3
|
+
import { MaptilerGeocoderAPI } from '../GeocoderControl/GeocoderAPIs';
|
|
4
|
+
import {
|
|
5
|
+
type GeocodingCountry,
|
|
6
|
+
type GeocodingLanguage,
|
|
7
|
+
type GeocodingService,
|
|
8
|
+
type Location
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
MaplibreGeocoderApi,
|
|
13
|
+
MaplibreGeocoderApiConfig
|
|
14
|
+
} from '@maplibre/maplibre-gl-geocoder';
|
|
15
|
+
|
|
16
|
+
interface WithLinkLocationProps {
|
|
17
|
+
service?: GeocodingService;
|
|
18
|
+
/**
|
|
19
|
+
* API key for selected geocoding `service`
|
|
20
|
+
*/
|
|
21
|
+
key: string;
|
|
22
|
+
/**
|
|
23
|
+
* Limit search to one or more countries
|
|
24
|
+
*/
|
|
25
|
+
countries?: GeocodingLanguage | GeocodingCountry[];
|
|
26
|
+
languages?: GeocodingLanguage | GeocodingLanguage[];
|
|
27
|
+
urlParameter?: string;
|
|
28
|
+
children: Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
key,
|
|
33
|
+
service = 'maptiler',
|
|
34
|
+
countries = 'de',
|
|
35
|
+
languages = 'de',
|
|
36
|
+
urlParameter = 'location',
|
|
37
|
+
children
|
|
38
|
+
}: WithLinkLocationProps = $props();
|
|
39
|
+
|
|
40
|
+
const countriesArr = Array.isArray(countries) ? countries : [countries];
|
|
41
|
+
const languagesArr = Array.isArray(languages) ? languages : [languages];
|
|
42
|
+
|
|
43
|
+
let geocoder: MaplibreGeocoderApi;
|
|
44
|
+
if (service === 'maptiler') {
|
|
45
|
+
geocoder = new MaptilerGeocoderAPI(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let location: Location | boolean | undefined = $state();
|
|
49
|
+
|
|
50
|
+
function bboxToArea(bbox: [number, number, number, number]) {
|
|
51
|
+
return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onMount(async () => {
|
|
55
|
+
const params = new URLSearchParams(window.location.search);
|
|
56
|
+
if (params.has(urlParameter)) {
|
|
57
|
+
const config: MaplibreGeocoderApiConfig = {
|
|
58
|
+
countries: countriesArr.join(','),
|
|
59
|
+
language: languagesArr.join(','),
|
|
60
|
+
query: params.get(urlParameter)?.toString(),
|
|
61
|
+
limit: 1
|
|
62
|
+
};
|
|
63
|
+
const res = await geocoder.forwardGeocode(config);
|
|
64
|
+
if (res.features[0].bbox && res.features[0].geometry.type === 'Point') {
|
|
65
|
+
location = {
|
|
66
|
+
lat: res.features[0].geometry.coordinates[1],
|
|
67
|
+
lng: res.features[0].geometry.coordinates[0],
|
|
68
|
+
zoom: 11 - bboxToArea(res.features[0].bbox) * 5.5
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
location = false;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
$effect.pre(() => {
|
|
77
|
+
setContext('initialLocation', location);
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
{#if location !== undefined}
|
|
82
|
+
{@render children?.()}
|
|
83
|
+
{/if}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Map as MapLibre, Marker, LayerSpecification } from 'maplibre-gl';
|
|
2
|
+
import { getContext, setContext } from 'svelte';
|
|
3
|
+
|
|
4
|
+
const MAP_CONTEXT_KEY = Symbol.for('map-context');
|
|
5
|
+
const SOURCE_CONTEXT_KEY = Symbol.for('source-context');
|
|
6
|
+
const LAYER_CONTEXT_KEY = Symbol.for('layer-context');
|
|
7
|
+
const POPUP_TARGET_KEY = Symbol.for('popup-target');
|
|
8
|
+
|
|
9
|
+
export class Box<T> {
|
|
10
|
+
value = $state() as T;
|
|
11
|
+
|
|
12
|
+
constructor(initialValue: T) {
|
|
13
|
+
this.value = initialValue;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class MapContext {
|
|
18
|
+
_map = $state(null) as MapLibre | null;
|
|
19
|
+
minzoom = $state(0);
|
|
20
|
+
maxzoom = $state(24);
|
|
21
|
+
styleLoaded = $state(false);
|
|
22
|
+
private _listener?: maplibregl.Listener = undefined;
|
|
23
|
+
|
|
24
|
+
get map() {
|
|
25
|
+
return this._map;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set map(value: maplibregl.Map | null) {
|
|
29
|
+
// Unbind any old event listeners
|
|
30
|
+
if (this._listener) {
|
|
31
|
+
this._map?.off('styledata', this._listener);
|
|
32
|
+
this._listener = undefined;
|
|
33
|
+
}
|
|
34
|
+
// Set new map instance and bind new event listeners
|
|
35
|
+
this._map = value;
|
|
36
|
+
if (this._map) {
|
|
37
|
+
this._listener = this._onstyledata.bind(this);
|
|
38
|
+
this._map.on('styledata', this._listener);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private _onstyledata(e: maplibregl.MapStyleDataEvent) {
|
|
43
|
+
this.styleLoaded = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class SourceContext {
|
|
48
|
+
_source = $state();
|
|
49
|
+
loaded = $state(false);
|
|
50
|
+
minzoom = $state(0);
|
|
51
|
+
maxzoom = $state(24);
|
|
52
|
+
|
|
53
|
+
get source() {
|
|
54
|
+
return this._source;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class LayerContext {
|
|
58
|
+
layer = $state() as LayerSpecification;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function setPopupTarget(value: Box<Marker | string | undefined>) {
|
|
62
|
+
setContext(POPUP_TARGET_KEY, value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getPopupTarget(): Box<Marker | string> | undefined {
|
|
66
|
+
return getContext(POPUP_TARGET_KEY);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createMapContext(): MapContext {
|
|
70
|
+
return setContext(MAP_CONTEXT_KEY, new MapContext());
|
|
71
|
+
}
|
|
72
|
+
export function getMapContext(): MapContext {
|
|
73
|
+
return getContext(MAP_CONTEXT_KEY);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createSourceContext(): SourceContext {
|
|
77
|
+
return setContext(SOURCE_CONTEXT_KEY, new SourceContext());
|
|
78
|
+
}
|
|
79
|
+
export function getSourceContext(): SourceContext {
|
|
80
|
+
return getContext(SOURCE_CONTEXT_KEY);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getLayerContext(): LayerContext {
|
|
84
|
+
return getContext(LAYER_CONTEXT_KEY);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function setLayerContext(value: string) {
|
|
88
|
+
setContext(LAYER_CONTEXT_KEY, value);
|
|
89
|
+
}
|