@swr-data-lab/components 2.1.0 → 2.3.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.
@@ -1,9 +1,13 @@
1
1
  <script lang="ts">import maplibre, {} from 'maplibre-gl';
2
2
  import { onMount, onDestroy, getContext, hasContext } from 'svelte';
3
- import { createMapContext } from '../context.svelte.js';
3
+ import { createMapContext, MapContext } from '../context.svelte.js';
4
4
  import {} from '../types';
5
5
  import FallbackStyle from './FallbackStyle';
6
- let { children, options, style = FallbackStyle, minZoom = 0, maxZoom = 14.99, zoom = $bindable(), center = $bindable(), pitch = $bindable(0), bearing = $bindable(0), loading = $bindable(true), projection = { type: 'mercator' }, allowRotation = false, allowZoom = true, showDebug = false, initialLocation: receivedInitialLocation } = $props();
6
+ import { derived } from 'svelte/store';
7
+ let { children, options, style = FallbackStyle, minZoom = 0, maxZoom = 14.99, zoom = $bindable(), center = $bindable(), pitch = $bindable(0), bearing = $bindable(0), loading = $bindable(true), projection = { type: 'mercator' }, allowRotation = false, allowZoom = true, showDebug = false, initialLocation: receivedInitialLocation,
8
+ // Future: This should become bindable.readonly when that becomes
9
+ // available, see: https://github.com/sveltejs/svelte/issues/7712
10
+ mapContext = $bindable(), onmoveend, onmovestart } = $props();
7
11
  let container;
8
12
  // Merge initial location with default object so individual
9
13
  // properties (like pitch) can be omitted by the caller
@@ -14,7 +18,7 @@ let initialLocation = {
14
18
  pitch: 0,
15
19
  ...receivedInitialLocation
16
20
  };
17
- const mapContext = createMapContext();
21
+ mapContext = createMapContext();
18
22
  if (getContext('initialLocation') !== undefined && getContext('initialLocation') !== false) {
19
23
  initialLocation = getContext('initialLocation');
20
24
  }
@@ -43,6 +47,12 @@ onMount(() => {
43
47
  pitch = mapContext.map?.getPitch();
44
48
  bearing = mapContext.map?.getBearing();
45
49
  });
50
+ if (onmoveend) {
51
+ mapContext.map.on('moveend', onmoveend);
52
+ }
53
+ if (onmovestart) {
54
+ mapContext.map.on('movestart', onmovestart);
55
+ }
46
56
  });
47
57
  onDestroy(async () => {
48
58
  if (mapContext.map)
@@ -65,6 +75,20 @@ $effect(() => {
65
75
  mapContext.map?.scrollZoom.enable();
66
76
  }
67
77
  });
78
+ const debugValues = $derived(Object.entries({ ...center, zoom, pitch, allowZoom, allowRotation }));
79
+ const handleDebugValueClick = (e) => {
80
+ if (e.target) {
81
+ const t = e.target;
82
+ const s = t.innerText;
83
+ navigator.clipboard.writeText(s);
84
+ }
85
+ };
86
+ const handleDebugCopyLocationClick = (e) => {
87
+ if (e.target) {
88
+ const s = JSON.stringify({ lng: center?.lng, lat: center?.lat, zoom, pitch });
89
+ navigator.clipboard.writeText(s);
90
+ }
91
+ };
68
92
  </script>
69
93
 
70
94
  <div bind:this={container} class="container" data-testid="map-container">
@@ -74,10 +98,17 @@ $effect(() => {
74
98
  {/if}
75
99
  {/if}
76
100
  {#if showDebug}
77
- <pre class="debug">
78
- {Object.entries({ ...center, zoom, pitch, allowZoom, allowRotation })
79
- .map(([key, val]) => `${key}: ${val}`)
80
- .join('\n')}</pre>
101
+ <ul class="debug">
102
+ {#each debugValues as [key, value]}
103
+ <li>
104
+ {key}:
105
+ <button onclick={handleDebugValueClick}
106
+ >{value?.toLocaleString('en', { maximumSignificantDigits: 6 })}</button
107
+ >
108
+ </li>
109
+ {/each}
110
+ <li><button onclick={handleDebugCopyLocationClick}>[Copy Location]</button></li>
111
+ </ul>
81
112
  {/if}
82
113
  </div>
83
114
 
@@ -96,6 +127,17 @@ $effect(() => {
96
127
  padding: 2px;
97
128
  font-family: monospace;
98
129
  }
130
+ .debug button {
131
+ appearance: none;
132
+ background: transparent;
133
+ font-family: inherit;
134
+ color: inherit;
135
+ border: 0;
136
+ cursor: pointer;
137
+ }
138
+ .debug button:hover, .debug button:focus-visible {
139
+ text-decoration: underline;
140
+ }
99
141
 
100
142
  :global(.maplibregl-map) {
101
143
  overflow: hidden;
@@ -1,5 +1,6 @@
1
- import maplibre, { type ProjectionSpecification, type StyleSpecification } from 'maplibre-gl';
1
+ import maplibre, { type MapLibreEvent, type ProjectionSpecification, type StyleSpecification } from 'maplibre-gl';
2
2
  import { type Snippet } from 'svelte';
3
+ import { MapContext } from '../context.svelte.js';
3
4
  import { type Location } from '../types';
4
5
  interface MapProps {
5
6
  style?: StyleSpecification | string;
@@ -16,8 +17,11 @@ interface MapProps {
16
17
  projection?: ProjectionSpecification;
17
18
  showDebug?: boolean;
18
19
  options?: any;
20
+ mapContext?: MapContext;
21
+ onmovestart?: (e: MapLibreEvent) => null;
22
+ onmoveend?: (e: MapLibreEvent) => null;
19
23
  children?: Snippet;
20
24
  }
21
- declare const Map: import("svelte").Component<MapProps, {}, "center" | "zoom" | "pitch" | "bearing" | "loading">;
25
+ declare const Map: import("svelte").Component<MapProps, {}, "center" | "zoom" | "pitch" | "bearing" | "loading" | "mapContext">;
22
26
  type Map = ReturnType<typeof Map>;
23
27
  export default Map;
@@ -7,7 +7,7 @@ import makeWalking from './components/Walking';
7
7
  import makeRoads from './components/Roads';
8
8
  const { buildingFootprints, buildingExtrusions, structureExtrusions } = makeBuildings();
9
9
  const { landuse } = makeLanduse();
10
- const { placeLabels } = makePlaceLabels();
10
+ const { placeLabels, boundaryLabels } = makePlaceLabels();
11
11
  const { admin } = makeAdmin();
12
12
  const { airports, transitBridges, transitSurface, transitTunnels } = makeTransit();
13
13
  const { walkingLabels, walkingTunnels, walkingSurface, walkingBridges } = makeWalking();
@@ -77,7 +77,8 @@ const style = (opts) => {
77
77
  // 8. Building extrusions
78
78
  ...(options.enableBuildingExtrusions ? [buildingExtrusions] : []),
79
79
  // 8. Point labels
80
- ...placeLabels
80
+ ...placeLabels,
81
+ ...boundaryLabels
81
82
  ]
82
83
  };
83
84
  };
@@ -3,7 +3,7 @@ import {} from '../../types';
3
3
  export default function makeAdmin() {
4
4
  const admin = [
5
5
  {
6
- id: 'boundary-country:outline',
6
+ id: 'boundary-country:case',
7
7
  filter: [
8
8
  'all',
9
9
  ['==', 'admin_level', 2],
@@ -22,14 +22,10 @@ export default function makeAdmin() {
22
22
  ]
23
23
  },
24
24
  'line-opacity': 0.75
25
- },
26
- layout: {
27
- 'line-cap': 'round',
28
- 'line-join': 'round'
29
25
  }
30
26
  },
31
27
  {
32
- id: 'boundary-country-disputed:outline',
28
+ id: 'boundary-country-disputed:case',
33
29
  filter: [
34
30
  'all',
35
31
  ['==', 'admin_level', 2],
@@ -50,7 +46,7 @@ export default function makeAdmin() {
50
46
  }
51
47
  },
52
48
  {
53
- id: 'boundary-state:outline',
49
+ id: 'boundary-state:case',
54
50
  filter: [
55
51
  'all',
56
52
  ['==', 'admin_level', 4],
@@ -69,10 +65,6 @@ export default function makeAdmin() {
69
65
  ]
70
66
  },
71
67
  'line-opacity': 0.75
72
- },
73
- layout: {
74
- 'line-cap': 'round',
75
- 'line-join': 'round'
76
68
  }
77
69
  },
78
70
  {
@@ -88,7 +80,7 @@ export default function makeAdmin() {
88
80
  'line-color': {
89
81
  stops: [
90
82
  [7, '#cecdcd'],
91
- [10, 'black']
83
+ [10, '#161616']
92
84
  ]
93
85
  },
94
86
  'line-width': {
@@ -99,10 +91,6 @@ export default function makeAdmin() {
99
91
  [12, 3]
100
92
  ]
101
93
  }
102
- },
103
- layout: {
104
- 'line-cap': 'round',
105
- 'line-join': 'round'
106
94
  }
107
95
  },
108
96
  {
@@ -124,9 +112,6 @@ export default function makeAdmin() {
124
112
  },
125
113
  'line-color': 'hsl(246,0%,77%)',
126
114
  'line-dasharray': [2, 1]
127
- },
128
- layout: {
129
- 'line-cap': 'square'
130
115
  }
131
116
  },
132
117
  {
@@ -152,10 +137,6 @@ export default function makeAdmin() {
152
137
  [8, 1]
153
138
  ]
154
139
  }
155
- },
156
- layout: {
157
- 'line-cap': 'round',
158
- 'line-join': 'round'
159
140
  }
160
141
  }
161
142
  ].map((el) => {
@@ -163,7 +144,11 @@ export default function makeAdmin() {
163
144
  source: 'versatiles-osm',
164
145
  'source-layer': 'boundaries',
165
146
  type: 'line',
166
- ...el
147
+ ...el,
148
+ layout: {
149
+ 'line-cap': 'round',
150
+ 'line-join': 'round'
151
+ }
167
152
  };
168
153
  });
169
154
  return { admin };
@@ -14,7 +14,7 @@ export default function makeLanduse() {
14
14
  type: 'fill',
15
15
  'source-layer': 'ocean',
16
16
  paint: {
17
- 'fill-color': 'hsl(220, 30%, 96%)'
17
+ 'fill-color': tokens.water_ocean
18
18
  }
19
19
  },
20
20
  {
@@ -300,11 +300,11 @@ export default function makeLanduse() {
300
300
  'source-layer': 'water_polygons',
301
301
  filter: ['==', 'kind', 'water'],
302
302
  paint: {
303
- 'fill-color': tokens.water_light,
303
+ 'fill-color': tokens.water_ocean,
304
304
  'fill-opacity': {
305
305
  stops: [
306
- [4, 0],
307
- [6, 1]
306
+ [3, 0],
307
+ [4, 1]
308
308
  ]
309
309
  }
310
310
  }
@@ -1,4 +1,5 @@
1
1
  import type { SymbolLayerSpecification } from 'maplibre-gl';
2
2
  export default function makePlaceLabels(): {
3
3
  placeLabels: SymbolLayerSpecification[];
4
+ boundaryLabels: SymbolLayerSpecification[];
4
5
  };
@@ -1,194 +1,239 @@
1
1
  import tokens from '../tokens';
2
+ // Hand-authored list of place labes we want to show at low zoom levels
3
+ // Ideally majorCities would include Frankfurt and Leipzig, but they're not
4
+ // state capitals so they're not available in the versatiles data until z6
5
+ const majorCities = [
6
+ 'Stuttgart',
7
+ 'München',
8
+ 'Mainz',
9
+ 'Bremen',
10
+ 'Düsseldorf',
11
+ 'Hamburg',
12
+ 'Bremen',
13
+ 'Dresden',
14
+ 'Erfurt'
15
+ ];
16
+ const majorCountries = [
17
+ 'Deutschland',
18
+ 'Dänemark',
19
+ 'Frankreich',
20
+ 'Niederlande',
21
+ 'Belgien',
22
+ 'Schweiz',
23
+ 'Polen',
24
+ 'Österreich',
25
+ 'Tschechien',
26
+ 'Slowakei',
27
+ 'Italien',
28
+ 'Ungarn'
29
+ ];
30
+ // For smaller cities we use the population field to derive our hierarchy,
31
+ // though that's limited by the fact that versatiles hard-codes population
32
+ // values for "city" and anything below.
33
+ // See: https://github.com/versatiles-org/shortbread-tilemaker/blob/69e5d4c586a1d2726b746a24829bfb05d4dbeb91/process.lua#L198-L242
2
34
  export default function makePlaceLabels() {
3
35
  const placeLabels = [
4
36
  {
5
- id: 'label-place-neighbourhood',
6
- filter: ['==', 'kind', 'neighbourhood'],
7
- minzoom: 14,
37
+ id: 'label-place-quarter',
38
+ filter: ['all', ['in', 'kind', 'neighbourhood']],
39
+ minzoom: 12,
8
40
  layout: {
9
- 'text-field': '{name_de}',
10
- 'text-font': tokens.sans_regular,
11
41
  'text-size': {
12
- stops: [[14, 14]]
42
+ stops: [
43
+ [10, 13],
44
+ [15, 16]
45
+ ]
13
46
  }
14
47
  },
15
48
  paint: {
16
- 'text-color': tokens.label_secondary,
17
- 'text-halo-color': tokens.background,
18
- 'text-halo-width': 2
49
+ 'text-color': tokens.label_tertiary
19
50
  }
20
51
  },
21
52
  {
22
- id: 'label-place-quarter',
23
- filter: ['==', 'kind', 'quarter'],
24
- minzoom: 14,
53
+ id: 'label-place-suburb',
54
+ filter: [
55
+ 'all',
56
+ ['in', 'kind', 'suburb', 'village', 'hamlet', 'town'],
57
+ ['>', 'population', 1000],
58
+ ['<', 'population', 15_000]
59
+ ],
60
+ minzoom: 11.5,
25
61
  layout: {
26
- 'text-field': '{name_de}',
27
- 'text-font': tokens.sans_regular,
28
62
  'text-size': {
29
- stops: [[10, 14]]
63
+ stops: [
64
+ [11, 13],
65
+ [15, 15]
66
+ ]
30
67
  }
31
68
  },
32
69
  paint: {
33
- 'text-color': tokens.label_secondary,
34
- 'text-halo-color': tokens.background,
35
- 'text-halo-width': 2
70
+ 'text-color': tokens.label_tertiary
36
71
  }
37
72
  },
38
73
  {
39
- id: 'label-place-suburb',
40
- filter: ['==', 'kind', 'suburb'],
41
- minzoom: 11,
74
+ id: 'label-place-town',
75
+ filter: [
76
+ 'all',
77
+ ['in', 'kind', 'village', 'hamlet', 'town'],
78
+ ['<', 'population', 50_000],
79
+ ['>', 'population', 15_000]
80
+ ],
81
+ minzoom: 10,
42
82
  layout: {
43
- 'text-field': '{name_de}',
44
- 'text-font': tokens.sans_regular,
45
83
  'text-size': {
46
84
  stops: [
47
- [11, 13],
48
- [13, 15]
85
+ [10, 14],
86
+ [12, 15]
49
87
  ]
50
88
  }
51
- },
52
- paint: {
53
- 'text-color': tokens.label_secondary,
54
- 'text-halo-color': tokens.background,
55
- 'text-halo-width': 1.5
56
89
  }
57
90
  },
58
91
  {
59
- id: 'label-place-hamlet',
60
- filter: ['==', 'kind', 'hamlet'],
61
- minzoom: 13,
92
+ id: 'label-small-city',
93
+ filter: [
94
+ 'all',
95
+ ['in', 'kind', 'city', 'town'],
96
+ ['>', 'population', 50_000],
97
+ ['<', 'population', 100_000],
98
+ ['!in', 'name_de', ...majorCities]
99
+ ],
100
+ minzoom: 8.5,
62
101
  layout: {
63
- 'text-field': '{name_de}',
64
- 'text-font': tokens.sans_regular,
65
102
  'text-size': {
66
103
  stops: [
67
- [10, 11],
68
- [12, 14]
104
+ [8, 14],
105
+ [12, 16]
69
106
  ]
70
107
  }
71
- },
72
- paint: {
73
- 'text-color': tokens.label_secondary,
74
- 'text-halo-color': tokens.background,
75
- 'text-halo-width': 2
76
108
  }
77
109
  },
78
110
  {
79
- id: 'label-place-village',
80
- filter: ['==', 'kind', 'village'],
81
- minzoom: 11,
111
+ id: 'label-place-medium-city',
112
+ filter: [
113
+ 'all',
114
+ ['in', 'kind', 'city', 'town'],
115
+ ['>', 'population', 100_000],
116
+ ['<', 'population', 400_000],
117
+ ['!in', 'name_de', ...majorCities]
118
+ ],
119
+ minzoom: 7,
120
+ maxzoom: 13,
82
121
  layout: {
83
- 'text-field': '{name_de}',
84
- 'text-font': tokens.sans_regular,
85
122
  'text-size': {
86
123
  stops: [
87
- [9, 11],
88
- [12, 14]
124
+ [7, 12],
125
+ [13, 17]
89
126
  ]
90
127
  }
91
- },
92
- paint: {
93
- 'text-color': tokens.label_secondary,
94
- 'text-halo-color': tokens.background,
95
- 'text-halo-width': 2
96
128
  }
97
129
  },
98
130
  {
99
- id: 'label-place-town',
100
- filter: ['==', 'kind', 'town'],
101
- minzoom: 9,
131
+ id: 'label-place-big-city',
132
+ filter: [
133
+ 'all',
134
+ ['in', 'kind', 'city', 'town', 'state_capital'],
135
+ ['>', 'population', 400_000],
136
+ ['!in', 'name_de', ...majorCities]
137
+ ],
138
+ minzoom: 7,
139
+ maxzoom: 12,
102
140
  layout: {
103
- 'text-field': '{name_de}',
104
- 'text-font': tokens.sans_regular,
105
- 'text-letter-spacing': 0.015,
106
141
  'text-size': {
107
142
  stops: [
108
- [8, 13],
109
- [12, 16]
143
+ [7, 14],
144
+ [15, 20]
110
145
  ]
111
146
  }
112
147
  },
113
148
  paint: {
114
- 'text-color': tokens.label_primary,
115
- 'text-halo-color': tokens.background,
116
- 'text-halo-width': 2
149
+ 'text-color': tokens.label_primary
117
150
  }
118
151
  },
119
152
  {
120
- id: 'label-place-city',
121
- filter: ['==', 'kind', 'city'],
122
- minzoom: 6,
123
- maxzoom: 11,
153
+ id: 'label-place-major-city',
154
+ filter: ['all', ['in', 'name_de', ...majorCities]],
155
+ minzoom: 5,
156
+ maxzoom: 12,
124
157
  layout: {
125
- 'text-field': '{name_de}',
126
- 'text-font': tokens.sans_regular,
127
- 'text-letter-spacing': 0.015,
128
158
  'text-size': {
129
159
  stops: [
130
- [8, 14],
131
- [10, 14]
160
+ [7, 14],
161
+ [15, 20]
132
162
  ]
133
163
  }
134
164
  },
135
165
  paint: {
136
- 'text-color': tokens.label_secondary,
137
- 'text-halo-color': tokens.background,
138
- 'text-halo-width': 2
166
+ 'text-color': tokens.label_primary
139
167
  }
140
168
  },
141
169
  {
142
- id: 'label-place-statecapital',
143
- filter: ['==', 'kind', 'state_capital'],
170
+ id: 'label-place-capital',
171
+ filter: ['all', ['==', 'kind', 'capital'], ['>', 'population', 1000000]],
144
172
  minzoom: 5,
145
173
  maxzoom: 12,
146
174
  layout: {
147
- 'text-field': '{name_de}',
148
- 'text-font': tokens.sans_regular,
149
- 'text-letter-spacing': 0.025,
150
175
  'text-size': {
151
176
  stops: [
152
- [5, 13],
153
- [14, 20]
177
+ [7, 15],
178
+ [14, 23]
154
179
  ]
155
180
  }
156
181
  },
182
+ paint: {
183
+ 'text-color': tokens.label_primary
184
+ }
185
+ }
186
+ ].map((el) => {
187
+ return {
188
+ ...el,
189
+ type: 'symbol',
190
+ source: 'versatiles-osm',
191
+ 'source-layer': 'place_labels',
192
+ layout: {
193
+ 'text-font': tokens.sans_regular,
194
+ 'text-letter-spacing': 0.025,
195
+ 'text-field': '{name_de}',
196
+ ...el.layout
197
+ },
157
198
  paint: {
158
199
  'text-color': tokens.label_secondary,
159
200
  'text-halo-color': tokens.background,
160
- 'text-halo-width': 1
201
+ 'text-halo-width': 1,
202
+ ...el.paint
161
203
  }
162
- },
204
+ };
205
+ });
206
+ const boundaryLabels = [
163
207
  {
164
- id: 'label-place-capital',
165
- filter: ['all', ['==', 'kind', 'capital'], ['==', 'name_de', 'Berlin']],
166
- minzoom: 5,
167
- maxzoom: 12,
208
+ id: 'label-boundary-country',
209
+ filter: ['all', ['==', 'admin_level', 2], ['in', 'name_de', ...majorCountries]],
210
+ minzoom: 4,
211
+ maxzoom: 8,
168
212
  layout: {
169
213
  'text-field': '{name_de}',
170
- 'text-letter-spacing': 0.015,
171
- 'text-font': tokens.sans_medium,
214
+ 'text-letter-spacing': 0.0825,
215
+ 'text-font': tokens.sans_regular,
216
+ 'text-transform': 'uppercase',
172
217
  'text-size': {
173
218
  stops: [
174
- [5, 15],
175
- [14, 20]
219
+ [4, 10],
220
+ [7, 17]
176
221
  ]
177
222
  }
178
223
  },
179
224
  paint: {
180
- 'text-color': tokens.label_primary,
225
+ 'text-color': tokens.label_tertiary,
181
226
  'text-halo-color': tokens.background,
182
- 'text-halo-width': 2
227
+ 'text-halo-width': 1
183
228
  }
184
229
  }
185
230
  ].map((el) => {
186
231
  return {
187
232
  type: 'symbol',
188
233
  source: 'versatiles-osm',
189
- 'source-layer': 'place_labels',
234
+ 'source-layer': 'boundary_labels',
190
235
  ...el
191
236
  };
192
237
  });
193
- return { placeLabels };
238
+ return { placeLabels, boundaryLabels };
194
239
  }