@maplibre-yaml/core 0.1.3-beta.0 → 0.1.3
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/README.md +185 -952
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +40 -36
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +171 -4
- package/dist/index.js +66 -1
- package/dist/index.js.map +1 -1
- package/dist/{map-renderer-IvxniEQy.d.ts → map-renderer-Br4guic2.d.ts} +1 -1
- package/dist/{page.schema-VBytF9l5.d.ts → page.schema-EBT_0Ojm.d.ts} +7 -1
- package/dist/register.browser.js +42 -38
- package/dist/register.browser.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/register.js +40 -36
- package/dist/register.js.map +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +4 -0
- package/dist/schemas/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,10 @@
|
|
|
1
1
|
# @maplibre-yaml/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Declarative web maps with YAML configuration. Build interactive MapLibre maps using simple, readable YAML syntax.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@maplibre-yaml/core)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
## Features
|
|
9
|
-
|
|
10
|
-
### =� **Declarative Map Configuration**
|
|
11
|
-
Define your entire maplayers, sources, controls, and interactivityin clean, readable YAML syntax.
|
|
12
|
-
|
|
13
|
-
### =� **Comprehensive Data Management**
|
|
14
|
-
- **HTTP Fetching** with automatic retry and caching
|
|
15
|
-
- **Real-time Updates** via Server-Sent Events (SSE) and WebSocket
|
|
16
|
-
- **Polling** with configurable intervals and merge strategies
|
|
17
|
-
- **Smart Merging** - Replace, merge by key, or window-based appending
|
|
18
|
-
|
|
19
|
-
### <� **Rich Visualization**
|
|
20
|
-
- Support for all MapLibre layer types (circle, line, fill, symbol, heatmap, etc.)
|
|
21
|
-
- Dynamic styling with expressions
|
|
22
|
-
- Multiple data sources (GeoJSON, Vector Tiles, Raster, etc.)
|
|
23
|
-
|
|
24
|
-
### = **Dynamic Interactions**
|
|
25
|
-
- Click handlers and popups
|
|
26
|
-
- Layer visibility toggling
|
|
27
|
-
- Data-driven legends
|
|
28
|
-
- Map controls (navigation, scale, geolocation, fullscreen)
|
|
29
|
-
|
|
30
|
-
### � **Performance Optimized**
|
|
31
|
-
- LRU caching with TTL
|
|
32
|
-
- Request deduplication
|
|
33
|
-
- Non-overlapping polling execution
|
|
34
|
-
- Automatic reconnection for streaming
|
|
35
|
-
|
|
36
|
-
### =� **Framework Integration**
|
|
37
|
-
- Vanilla JavaScript/TypeScript
|
|
38
|
-
- Web Components (`<ml-map>`)
|
|
39
|
-
- Astro components (via `@maplibre-yaml/astro`)
|
|
40
|
-
- Framework agnostic core
|
|
41
|
-
|
|
42
8
|
## Installation
|
|
43
9
|
|
|
44
10
|
```bash
|
|
@@ -47,35 +13,39 @@ npm install @maplibre-yaml/core maplibre-gl
|
|
|
47
13
|
|
|
48
14
|
## Quick Start
|
|
49
15
|
|
|
50
|
-
###
|
|
16
|
+
### Web Component
|
|
17
|
+
|
|
18
|
+
The simplest way to use maplibre-yaml is with the `<ml-map>` web component:
|
|
51
19
|
|
|
52
20
|
```html
|
|
53
21
|
<!DOCTYPE html>
|
|
54
22
|
<html>
|
|
55
23
|
<head>
|
|
56
24
|
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css">
|
|
25
|
+
<style>
|
|
26
|
+
ml-map { display: block; height: 400px; }
|
|
27
|
+
</style>
|
|
57
28
|
</head>
|
|
58
29
|
<body>
|
|
59
|
-
<ml-map
|
|
60
|
-
|
|
30
|
+
<ml-map src="/map.yaml"></ml-map>
|
|
61
31
|
<script type="module">
|
|
62
|
-
import '@maplibre-yaml/core/
|
|
32
|
+
import '@maplibre-yaml/core/register';
|
|
63
33
|
</script>
|
|
64
34
|
</body>
|
|
65
35
|
</html>
|
|
66
36
|
```
|
|
67
37
|
|
|
68
|
-
###
|
|
38
|
+
### JavaScript API
|
|
69
39
|
|
|
70
40
|
```typescript
|
|
71
|
-
import {
|
|
41
|
+
import { parseYAMLConfig, MapRenderer } from '@maplibre-yaml/core';
|
|
72
42
|
|
|
73
43
|
const yaml = `
|
|
74
44
|
type: map
|
|
75
45
|
config:
|
|
76
46
|
center: [-122.4, 37.8]
|
|
77
47
|
zoom: 12
|
|
78
|
-
|
|
48
|
+
mapStyle: https://demotiles.maplibre.org/style.json
|
|
79
49
|
layers:
|
|
80
50
|
- id: points
|
|
81
51
|
type: circle
|
|
@@ -87,13 +57,22 @@ layers:
|
|
|
87
57
|
circle-color: "#3b82f6"
|
|
88
58
|
`;
|
|
89
59
|
|
|
90
|
-
const
|
|
91
|
-
const config = await parser.parse(yaml);
|
|
92
|
-
|
|
60
|
+
const config = parseYAMLConfig(yaml);
|
|
93
61
|
const container = document.getElementById('map');
|
|
94
62
|
const renderer = new MapRenderer(container, config);
|
|
95
63
|
```
|
|
96
64
|
|
|
65
|
+
## Entry Points
|
|
66
|
+
|
|
67
|
+
The package provides multiple entry points for different use cases:
|
|
68
|
+
|
|
69
|
+
| Import | Purpose |
|
|
70
|
+
|--------|---------|
|
|
71
|
+
| `@maplibre-yaml/core` | Main entry: parser, renderer, data utilities |
|
|
72
|
+
| `@maplibre-yaml/core/register` | Auto-registers `<ml-map>` web component |
|
|
73
|
+
| `@maplibre-yaml/core/schemas` | Zod schemas for validation and types |
|
|
74
|
+
| `@maplibre-yaml/core/components` | Manual component registration |
|
|
75
|
+
|
|
97
76
|
## YAML Configuration
|
|
98
77
|
|
|
99
78
|
### Basic Map
|
|
@@ -104,7 +83,7 @@ id: my-map
|
|
|
104
83
|
config:
|
|
105
84
|
center: [-74.006, 40.7128]
|
|
106
85
|
zoom: 10
|
|
107
|
-
|
|
86
|
+
mapStyle: https://demotiles.maplibre.org/style.json
|
|
108
87
|
|
|
109
88
|
layers:
|
|
110
89
|
- id: cities
|
|
@@ -135,14 +114,9 @@ layers:
|
|
|
135
114
|
type: geojson
|
|
136
115
|
url: https://api.transit.example.com/vehicles.geojson
|
|
137
116
|
refresh:
|
|
138
|
-
refreshInterval: 5000
|
|
139
|
-
updateStrategy: merge
|
|
140
|
-
updateKey: vehicleId
|
|
141
|
-
cache:
|
|
142
|
-
enabled: false # Disable cache for real-time data
|
|
143
|
-
loading:
|
|
144
|
-
enabled: true
|
|
145
|
-
message: Loading vehicles...
|
|
117
|
+
refreshInterval: 5000
|
|
118
|
+
updateStrategy: merge
|
|
119
|
+
updateKey: vehicleId
|
|
146
120
|
paint:
|
|
147
121
|
circle-radius: 6
|
|
148
122
|
circle-color:
|
|
@@ -155,7 +129,7 @@ layers:
|
|
|
155
129
|
- "#6b7280"
|
|
156
130
|
```
|
|
157
131
|
|
|
158
|
-
###
|
|
132
|
+
### Streaming with WebSocket
|
|
159
133
|
|
|
160
134
|
```yaml
|
|
161
135
|
layers:
|
|
@@ -163,53 +137,21 @@ layers:
|
|
|
163
137
|
type: circle
|
|
164
138
|
source:
|
|
165
139
|
type: geojson
|
|
166
|
-
url: https://api.example.com/initial
|
|
140
|
+
url: https://api.example.com/initial.geojson
|
|
167
141
|
stream:
|
|
168
142
|
type: websocket
|
|
169
143
|
url: wss://api.example.com/events
|
|
170
144
|
reconnect: true
|
|
171
|
-
reconnectMaxAttempts: 10
|
|
172
145
|
refresh:
|
|
173
146
|
updateStrategy: append-window
|
|
174
|
-
windowSize: 100
|
|
175
|
-
windowDuration: 300000
|
|
176
|
-
timestampField: timestamp
|
|
147
|
+
windowSize: 100
|
|
148
|
+
windowDuration: 300000
|
|
177
149
|
paint:
|
|
178
150
|
circle-radius: 8
|
|
179
151
|
circle-color: "#8b5cf6"
|
|
180
152
|
```
|
|
181
153
|
|
|
182
|
-
###
|
|
183
|
-
|
|
184
|
-
```yaml
|
|
185
|
-
layers:
|
|
186
|
-
- id: sensors
|
|
187
|
-
type: heatmap
|
|
188
|
-
source:
|
|
189
|
-
type: geojson
|
|
190
|
-
url: https://api.example.com/sensors.geojson
|
|
191
|
-
stream:
|
|
192
|
-
type: sse
|
|
193
|
-
url: https://api.example.com/sensor-updates
|
|
194
|
-
eventTypes:
|
|
195
|
-
- temperature
|
|
196
|
-
- humidity
|
|
197
|
-
reconnect: true
|
|
198
|
-
refresh:
|
|
199
|
-
updateStrategy: merge
|
|
200
|
-
updateKey: sensorId
|
|
201
|
-
paint:
|
|
202
|
-
heatmap-weight:
|
|
203
|
-
- interpolate
|
|
204
|
-
- ["linear"]
|
|
205
|
-
- ["get", "temperature"]
|
|
206
|
-
- 0
|
|
207
|
-
- 0
|
|
208
|
-
- 100
|
|
209
|
-
- 1
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Interactive Features
|
|
154
|
+
### Interactive Popups
|
|
213
155
|
|
|
214
156
|
```yaml
|
|
215
157
|
layers:
|
|
@@ -220,111 +162,71 @@ layers:
|
|
|
220
162
|
url: https://example.com/locations.geojson
|
|
221
163
|
layout:
|
|
222
164
|
icon-image: marker
|
|
223
|
-
icon-size: 1.5
|
|
224
165
|
interactions:
|
|
225
166
|
- type: click
|
|
226
167
|
popup:
|
|
227
168
|
content: |
|
|
228
169
|
<h3>{{name}}</h3>
|
|
229
170
|
<p>{{description}}</p>
|
|
230
|
-
<p><strong>Type:</strong> {{category}}</p>
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Data Merge Strategies
|
|
234
|
-
|
|
235
|
-
#### Replace Strategy
|
|
236
|
-
Complete replacement of all data on each update:
|
|
237
|
-
```yaml
|
|
238
|
-
refresh:
|
|
239
|
-
updateStrategy: replace
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
#### Merge Strategy
|
|
243
|
-
Update or add features by unique key:
|
|
244
|
-
```yaml
|
|
245
|
-
refresh:
|
|
246
|
-
updateStrategy: merge
|
|
247
|
-
updateKey: id # Feature property to use as unique identifier
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
#### Append-Window Strategy
|
|
251
|
-
Append new data with size and/or time-based windowing:
|
|
252
|
-
```yaml
|
|
253
|
-
refresh:
|
|
254
|
-
updateStrategy: append-window
|
|
255
|
-
windowSize: 100 # Keep last 100 features
|
|
256
|
-
windowDuration: 300000 # Keep features from last 5 minutes
|
|
257
|
-
timestampField: timestamp # Feature property containing timestamp
|
|
258
171
|
```
|
|
259
172
|
|
|
260
173
|
## API Reference
|
|
261
174
|
|
|
262
|
-
###
|
|
263
|
-
|
|
264
|
-
#### `YAMLParser`
|
|
265
|
-
Parse and validate YAML map configurations.
|
|
175
|
+
### Parser
|
|
266
176
|
|
|
267
177
|
```typescript
|
|
268
|
-
import { YAMLParser } from '@maplibre-yaml/core';
|
|
178
|
+
import { YAMLParser, parseYAMLConfig, safeParseYAMLConfig } from '@maplibre-yaml/core';
|
|
179
|
+
|
|
180
|
+
// Simple parsing
|
|
181
|
+
const config = parseYAMLConfig(yamlString);
|
|
182
|
+
|
|
183
|
+
// Safe parsing with error handling
|
|
184
|
+
const result = safeParseYAMLConfig(yamlString);
|
|
185
|
+
if (result.success) {
|
|
186
|
+
console.log(result.data);
|
|
187
|
+
} else {
|
|
188
|
+
console.error(result.error);
|
|
189
|
+
}
|
|
269
190
|
|
|
191
|
+
// Using the class
|
|
270
192
|
const parser = new YAMLParser();
|
|
271
|
-
const config =
|
|
272
|
-
const errors = parser.validate(config);
|
|
193
|
+
const config = parser.parse(yamlString);
|
|
273
194
|
```
|
|
274
195
|
|
|
275
|
-
|
|
276
|
-
Render maps from parsed configurations.
|
|
196
|
+
### Renderer
|
|
277
197
|
|
|
278
198
|
```typescript
|
|
279
199
|
import { MapRenderer } from '@maplibre-yaml/core';
|
|
280
200
|
|
|
281
201
|
const renderer = new MapRenderer(container, config, {
|
|
282
202
|
onLoad: () => console.log('Map loaded'),
|
|
283
|
-
onError: (error) => console.error(
|
|
203
|
+
onError: (error) => console.error(error),
|
|
284
204
|
});
|
|
285
205
|
|
|
286
|
-
//
|
|
206
|
+
// Control layers
|
|
287
207
|
renderer.setLayerVisibility('layer-id', false);
|
|
288
|
-
|
|
289
|
-
// Update data
|
|
290
208
|
renderer.updateLayerData('layer-id', newGeoJSON);
|
|
291
209
|
|
|
292
|
-
//
|
|
210
|
+
// Cleanup
|
|
293
211
|
renderer.destroy();
|
|
294
212
|
```
|
|
295
213
|
|
|
296
214
|
### Data Management
|
|
297
215
|
|
|
298
|
-
####
|
|
299
|
-
HTTP data fetching with caching and retry.
|
|
216
|
+
#### DataFetcher
|
|
300
217
|
|
|
301
218
|
```typescript
|
|
302
219
|
import { DataFetcher } from '@maplibre-yaml/core';
|
|
303
220
|
|
|
304
221
|
const fetcher = new DataFetcher({
|
|
305
|
-
cache: {
|
|
306
|
-
|
|
307
|
-
defaultTTL: 300000, // 5 minutes
|
|
308
|
-
maxSize: 50
|
|
309
|
-
},
|
|
310
|
-
retry: {
|
|
311
|
-
enabled: true,
|
|
312
|
-
maxRetries: 3,
|
|
313
|
-
initialDelay: 1000,
|
|
314
|
-
maxDelay: 10000
|
|
315
|
-
}
|
|
222
|
+
cache: { enabled: true, defaultTTL: 300000, maxSize: 50 },
|
|
223
|
+
retry: { enabled: true, maxRetries: 3 }
|
|
316
224
|
});
|
|
317
225
|
|
|
318
|
-
const result = await fetcher.fetch('https://example.com/data.geojson'
|
|
319
|
-
ttl: 60000, // Override default TTL
|
|
320
|
-
onRetry: (attempt, delay, error) => {
|
|
321
|
-
console.log(`Retry attempt ${attempt} after ${delay}ms`);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
226
|
+
const result = await fetcher.fetch('https://example.com/data.geojson');
|
|
324
227
|
```
|
|
325
228
|
|
|
326
|
-
####
|
|
327
|
-
Manage periodic data refresh.
|
|
229
|
+
#### PollingManager
|
|
328
230
|
|
|
329
231
|
```typescript
|
|
330
232
|
import { PollingManager } from '@maplibre-yaml/core';
|
|
@@ -333,24 +235,16 @@ const polling = new PollingManager();
|
|
|
333
235
|
|
|
334
236
|
polling.start('layer-id', {
|
|
335
237
|
interval: 5000,
|
|
336
|
-
onTick: async () =>
|
|
337
|
-
|
|
338
|
-
updateMap(data);
|
|
339
|
-
},
|
|
340
|
-
onError: (error) => console.error('Polling error:', error),
|
|
341
|
-
immediate: true, // Execute immediately on start
|
|
342
|
-
pauseWhenHidden: true // Pause when tab is hidden
|
|
238
|
+
onTick: async () => updateMap(await fetchData()),
|
|
239
|
+
pauseWhenHidden: true
|
|
343
240
|
});
|
|
344
241
|
|
|
345
|
-
// Control polling
|
|
346
242
|
polling.pause('layer-id');
|
|
347
243
|
polling.resume('layer-id');
|
|
348
|
-
await polling.triggerNow('layer-id');
|
|
349
244
|
polling.stop('layer-id');
|
|
350
245
|
```
|
|
351
246
|
|
|
352
|
-
####
|
|
353
|
-
Manage WebSocket and SSE connections.
|
|
247
|
+
#### StreamManager
|
|
354
248
|
|
|
355
249
|
```typescript
|
|
356
250
|
import { StreamManager } from '@maplibre-yaml/core';
|
|
@@ -360,855 +254,194 @@ const streams = new StreamManager();
|
|
|
360
254
|
await streams.connect('stream-id', {
|
|
361
255
|
type: 'websocket',
|
|
362
256
|
url: 'wss://example.com/stream',
|
|
363
|
-
onData: (data) =>
|
|
364
|
-
|
|
365
|
-
},
|
|
366
|
-
onStateChange: (state) => {
|
|
367
|
-
console.log('Connection state:', state);
|
|
368
|
-
},
|
|
369
|
-
reconnect: {
|
|
370
|
-
enabled: true,
|
|
371
|
-
maxRetries: 10,
|
|
372
|
-
initialDelay: 1000,
|
|
373
|
-
maxDelay: 30000
|
|
374
|
-
}
|
|
257
|
+
onData: (data) => console.log(data),
|
|
258
|
+
reconnect: { enabled: true, maxRetries: 10 }
|
|
375
259
|
});
|
|
376
260
|
|
|
377
|
-
// Send data (WebSocket only)
|
|
378
|
-
streams.send('stream-id', { type: 'subscribe', channel: 'updates' });
|
|
379
|
-
|
|
380
|
-
// Disconnect
|
|
381
261
|
streams.disconnect('stream-id');
|
|
382
262
|
```
|
|
383
263
|
|
|
384
|
-
####
|
|
385
|
-
Merge GeoJSON data with different strategies.
|
|
264
|
+
#### DataMerger
|
|
386
265
|
|
|
387
266
|
```typescript
|
|
388
267
|
import { DataMerger } from '@maplibre-yaml/core';
|
|
389
268
|
|
|
390
269
|
const merger = new DataMerger();
|
|
391
|
-
|
|
392
|
-
const result = merger.merge(existingData, incomingData, {
|
|
270
|
+
const result = merger.merge(existing, incoming, {
|
|
393
271
|
strategy: 'merge',
|
|
394
272
|
updateKey: 'id'
|
|
395
273
|
});
|
|
396
|
-
|
|
397
|
-
console.log(`Added: ${result.added}, Updated: ${result.updated}`);
|
|
398
|
-
console.log(`Total features: ${result.total}`);
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
#### `LoadingManager`
|
|
402
|
-
Manage loading states with optional UI.
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
import { LoadingManager } from '@maplibre-yaml/core';
|
|
406
|
-
|
|
407
|
-
const loading = new LoadingManager({
|
|
408
|
-
showUI: true,
|
|
409
|
-
messages: {
|
|
410
|
-
loading: 'Loading data...',
|
|
411
|
-
error: 'Failed to load data',
|
|
412
|
-
retry: 'Retrying...'
|
|
413
|
-
},
|
|
414
|
-
spinnerStyle: 'circle',
|
|
415
|
-
minDisplayTime: 300 // Minimum 300ms display to prevent flashing
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// Listen to events
|
|
419
|
-
loading.on('loading:start', ({ layerId }) => {
|
|
420
|
-
console.log('Loading started:', layerId);
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
loading.on('loading:complete', ({ layerId, duration, fromCache }) => {
|
|
424
|
-
console.log('Loading complete:', layerId, duration, fromCache);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// Show loading
|
|
428
|
-
loading.showLoading('layer-id', container, 'Loading...');
|
|
429
|
-
|
|
430
|
-
// Show progress
|
|
431
|
-
loading.updateProgress('layer-id', 50, 100);
|
|
432
|
-
|
|
433
|
-
// Hide loading
|
|
434
|
-
loading.hideLoading('layer-id', { fromCache: false });
|
|
435
|
-
|
|
436
|
-
// Show error with retry
|
|
437
|
-
loading.showError('layer-id', container, new Error('Failed'), () => {
|
|
438
|
-
retryLoad();
|
|
439
|
-
});
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Layer Manager Integration
|
|
443
|
-
|
|
444
|
-
The `LayerManager` integrates all data management components:
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
import { LayerManager } from '@maplibre-yaml/core';
|
|
448
|
-
|
|
449
|
-
const layerManager = new LayerManager(map, {
|
|
450
|
-
onDataLoading: (layerId) => console.log('Loading:', layerId),
|
|
451
|
-
onDataLoaded: (layerId, count) => console.log('Loaded:', layerId, count),
|
|
452
|
-
onDataError: (layerId, error) => console.error('Error:', layerId, error)
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Add layer with all data features
|
|
456
|
-
await layerManager.addLayer({
|
|
457
|
-
id: 'my-layer',
|
|
458
|
-
type: 'circle',
|
|
459
|
-
source: {
|
|
460
|
-
type: 'geojson',
|
|
461
|
-
url: 'https://example.com/data.geojson',
|
|
462
|
-
refresh: {
|
|
463
|
-
refreshInterval: 5000,
|
|
464
|
-
updateStrategy: 'merge',
|
|
465
|
-
updateKey: 'id'
|
|
466
|
-
},
|
|
467
|
-
cache: {
|
|
468
|
-
enabled: true,
|
|
469
|
-
ttl: 60000
|
|
470
|
-
},
|
|
471
|
-
stream: {
|
|
472
|
-
type: 'websocket',
|
|
473
|
-
url: 'wss://example.com/updates',
|
|
474
|
-
reconnect: true
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Control data updates
|
|
480
|
-
layerManager.pauseRefresh('my-layer');
|
|
481
|
-
layerManager.resumeRefresh('my-layer');
|
|
482
|
-
await layerManager.refreshNow('my-layer');
|
|
483
|
-
layerManager.disconnectStream('my-layer');
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
## Schema Validation
|
|
487
|
-
|
|
488
|
-
All YAML configurations are validated using Zod schemas:
|
|
489
|
-
|
|
490
|
-
```typescript
|
|
491
|
-
import { MapConfigSchema } from '@maplibre-yaml/core/schemas';
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
const config = MapConfigSchema.parse(yamlData);
|
|
495
|
-
// Config is valid and type-safe
|
|
496
|
-
} catch (error) {
|
|
497
|
-
console.error('Validation errors:', error.issues);
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
## Schemas
|
|
502
|
-
|
|
503
|
-
The schema system is a core component of `@maplibre-yaml/core`, providing type-safe validation and excellent developer experience. All schemas are built with [Zod](https://zod.dev) and automatically generate TypeScript types.
|
|
504
|
-
|
|
505
|
-
### Schema Architecture
|
|
506
|
-
|
|
507
|
-
```
|
|
508
|
-
MapConfigSchema
|
|
509
|
-
├── PageConfigSchema (scrollytelling)
|
|
510
|
-
│ ├── SectionSchema
|
|
511
|
-
│ └── StepSchema
|
|
512
|
-
└── MapSchema (single map)
|
|
513
|
-
├── MapConfigurationSchema
|
|
514
|
-
├── LayerSchema[]
|
|
515
|
-
├── SourceSchema[]
|
|
516
|
-
├── LegendSchema
|
|
517
|
-
├── ControlsSchema
|
|
518
|
-
└── InteractionSchema
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### Map Configuration Schema
|
|
522
|
-
|
|
523
|
-
The top-level `MapConfigSchema` validates complete map configurations:
|
|
524
|
-
|
|
525
|
-
```typescript
|
|
526
|
-
import { MapConfigSchema } from '@maplibre-yaml/core/schemas';
|
|
527
|
-
|
|
528
|
-
const config = MapConfigSchema.parse({
|
|
529
|
-
type: 'map',
|
|
530
|
-
id: 'my-map',
|
|
531
|
-
config: {
|
|
532
|
-
center: [-122.4, 37.8],
|
|
533
|
-
zoom: 12,
|
|
534
|
-
pitch: 0,
|
|
535
|
-
bearing: 0,
|
|
536
|
-
style: 'https://demotiles.maplibre.org/style.json',
|
|
537
|
-
minZoom: 0,
|
|
538
|
-
maxZoom: 22,
|
|
539
|
-
bounds: [[-180, -90], [180, 90]],
|
|
540
|
-
maxBounds: [[-180, -90], [180, 90]],
|
|
541
|
-
fitBoundsOptions: {
|
|
542
|
-
padding: 50,
|
|
543
|
-
maxZoom: 15
|
|
544
|
-
}
|
|
545
|
-
},
|
|
546
|
-
layers: [],
|
|
547
|
-
sources: [],
|
|
548
|
-
controls: {},
|
|
549
|
-
legend: {},
|
|
550
|
-
interactions: []
|
|
551
|
-
});
|
|
552
274
|
```
|
|
553
275
|
|
|
554
|
-
###
|
|
276
|
+
### Schemas
|
|
555
277
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
```typescript
|
|
559
|
-
import { LayerSchema } from '@maplibre-yaml/core/schemas';
|
|
560
|
-
|
|
561
|
-
// Circle layer
|
|
562
|
-
const circleLayer = LayerSchema.parse({
|
|
563
|
-
id: 'points',
|
|
564
|
-
type: 'circle',
|
|
565
|
-
source: 'points-source',
|
|
566
|
-
paint: {
|
|
567
|
-
'circle-radius': 6,
|
|
568
|
-
'circle-color': '#3b82f6',
|
|
569
|
-
'circle-opacity': 0.8,
|
|
570
|
-
'circle-stroke-width': 2,
|
|
571
|
-
'circle-stroke-color': '#ffffff'
|
|
572
|
-
},
|
|
573
|
-
layout: {
|
|
574
|
-
visibility: 'visible'
|
|
575
|
-
},
|
|
576
|
-
minzoom: 0,
|
|
577
|
-
maxzoom: 22,
|
|
578
|
-
filter: ['==', ['get', 'type'], 'poi']
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// Symbol layer with expressions
|
|
582
|
-
const symbolLayer = LayerSchema.parse({
|
|
583
|
-
id: 'labels',
|
|
584
|
-
type: 'symbol',
|
|
585
|
-
source: 'places',
|
|
586
|
-
layout: {
|
|
587
|
-
'text-field': ['get', 'name'],
|
|
588
|
-
'text-size': 12,
|
|
589
|
-
'text-anchor': 'top',
|
|
590
|
-
'text-offset': [0, 1],
|
|
591
|
-
'icon-image': 'marker',
|
|
592
|
-
'icon-size': 1
|
|
593
|
-
},
|
|
594
|
-
paint: {
|
|
595
|
-
'text-color': '#000000',
|
|
596
|
-
'text-halo-color': '#ffffff',
|
|
597
|
-
'text-halo-width': 2
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
// Heatmap layer
|
|
602
|
-
const heatmapLayer = LayerSchema.parse({
|
|
603
|
-
id: 'density',
|
|
604
|
-
type: 'heatmap',
|
|
605
|
-
source: 'points',
|
|
606
|
-
paint: {
|
|
607
|
-
'heatmap-weight': [
|
|
608
|
-
'interpolate',
|
|
609
|
-
['linear'],
|
|
610
|
-
['get', 'value'],
|
|
611
|
-
0, 0,
|
|
612
|
-
100, 1
|
|
613
|
-
],
|
|
614
|
-
'heatmap-intensity': 1,
|
|
615
|
-
'heatmap-radius': 30,
|
|
616
|
-
'heatmap-opacity': 0.7
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
**Supported Layer Types:**
|
|
622
|
-
- `circle` - Point data as circles
|
|
623
|
-
- `line` - Linear features
|
|
624
|
-
- `fill` - Polygon fills
|
|
625
|
-
- `fill-extrusion` - 3D buildings/polygons
|
|
626
|
-
- `symbol` - Icons and text labels
|
|
627
|
-
- `heatmap` - Density visualization
|
|
628
|
-
- `hillshade` - Terrain shading
|
|
629
|
-
- `raster` - Raster tiles
|
|
630
|
-
- `background` - Map background
|
|
631
|
-
|
|
632
|
-
### Source Schema
|
|
633
|
-
|
|
634
|
-
Multiple source types with comprehensive configuration options:
|
|
635
|
-
|
|
636
|
-
#### GeoJSON Source
|
|
637
|
-
|
|
638
|
-
```typescript
|
|
639
|
-
import { GeoJSONSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
640
|
-
|
|
641
|
-
const source = GeoJSONSourceSchema.parse({
|
|
642
|
-
type: 'geojson',
|
|
643
|
-
|
|
644
|
-
// Data options (one required)
|
|
645
|
-
url: 'https://example.com/data.geojson',
|
|
646
|
-
// OR data: { type: 'FeatureCollection', features: [] },
|
|
647
|
-
// OR prefetchedData: { type: 'FeatureCollection', features: [] },
|
|
648
|
-
|
|
649
|
-
// Fetch strategy
|
|
650
|
-
fetchStrategy: 'runtime', // 'runtime' | 'build' | 'hybrid'
|
|
651
|
-
|
|
652
|
-
// Real-time updates
|
|
653
|
-
refresh: {
|
|
654
|
-
refreshInterval: 5000,
|
|
655
|
-
updateStrategy: 'merge', // 'replace' | 'merge' | 'append-window'
|
|
656
|
-
updateKey: 'id',
|
|
657
|
-
windowSize: 100,
|
|
658
|
-
windowDuration: 300000,
|
|
659
|
-
timestampField: 'timestamp'
|
|
660
|
-
},
|
|
661
|
-
|
|
662
|
-
// Streaming
|
|
663
|
-
stream: {
|
|
664
|
-
type: 'websocket', // 'websocket' | 'sse'
|
|
665
|
-
url: 'wss://example.com/stream',
|
|
666
|
-
reconnect: true,
|
|
667
|
-
reconnectMaxAttempts: 10,
|
|
668
|
-
reconnectDelay: 1000,
|
|
669
|
-
reconnectMaxDelay: 30000,
|
|
670
|
-
eventTypes: ['update', 'delete'],
|
|
671
|
-
protocols: ['v1', 'v2']
|
|
672
|
-
},
|
|
673
|
-
|
|
674
|
-
// Caching
|
|
675
|
-
cache: {
|
|
676
|
-
enabled: true,
|
|
677
|
-
ttl: 300000 // 5 minutes
|
|
678
|
-
},
|
|
679
|
-
|
|
680
|
-
// Loading UI
|
|
681
|
-
loading: {
|
|
682
|
-
enabled: true,
|
|
683
|
-
message: 'Loading data...',
|
|
684
|
-
showErrorOverlay: true
|
|
685
|
-
},
|
|
686
|
-
|
|
687
|
-
// Clustering
|
|
688
|
-
cluster: true,
|
|
689
|
-
clusterRadius: 50,
|
|
690
|
-
clusterMaxZoom: 14,
|
|
691
|
-
clusterMinPoints: 2,
|
|
692
|
-
clusterProperties: {
|
|
693
|
-
sum: ['+', ['get', 'value']],
|
|
694
|
-
max: ['max', ['get', 'value']]
|
|
695
|
-
},
|
|
696
|
-
|
|
697
|
-
// Spatial index
|
|
698
|
-
tolerance: 0.375,
|
|
699
|
-
buffer: 128,
|
|
700
|
-
lineMetrics: false,
|
|
701
|
-
generateId: false
|
|
702
|
-
});
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
#### Vector Tile Source
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
import { VectorSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
709
|
-
|
|
710
|
-
const source = VectorSourceSchema.parse({
|
|
711
|
-
type: 'vector',
|
|
712
|
-
tiles: [
|
|
713
|
-
'https://tiles.example.com/{z}/{x}/{y}.pbf'
|
|
714
|
-
],
|
|
715
|
-
// OR url: 'https://tiles.example.com/tiles.json',
|
|
716
|
-
minzoom: 0,
|
|
717
|
-
maxzoom: 14,
|
|
718
|
-
bounds: [-180, -85.0511, 180, 85.0511],
|
|
719
|
-
attribution: '© Example Maps'
|
|
720
|
-
});
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
#### Raster Source
|
|
724
|
-
|
|
725
|
-
```typescript
|
|
726
|
-
import { RasterSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
727
|
-
|
|
728
|
-
const source = RasterSourceSchema.parse({
|
|
729
|
-
type: 'raster',
|
|
730
|
-
tiles: [
|
|
731
|
-
'https://tiles.example.com/{z}/{x}/{y}.png'
|
|
732
|
-
],
|
|
733
|
-
tileSize: 256,
|
|
734
|
-
minzoom: 0,
|
|
735
|
-
maxzoom: 18,
|
|
736
|
-
attribution: '© Example Imagery'
|
|
737
|
-
});
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
#### Image & Video Sources
|
|
741
|
-
|
|
742
|
-
```typescript
|
|
743
|
-
import { ImageSourceSchema, VideoSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
744
|
-
|
|
745
|
-
// Image overlay
|
|
746
|
-
const imageSource = ImageSourceSchema.parse({
|
|
747
|
-
type: 'image',
|
|
748
|
-
url: 'https://example.com/overlay.png',
|
|
749
|
-
coordinates: [
|
|
750
|
-
[-122.5, 37.9], // top-left
|
|
751
|
-
[-122.3, 37.9], // top-right
|
|
752
|
-
[-122.3, 37.7], // bottom-right
|
|
753
|
-
[-122.5, 37.7] // bottom-left
|
|
754
|
-
]
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
// Video overlay
|
|
758
|
-
const videoSource = VideoSourceSchema.parse({
|
|
759
|
-
type: 'video',
|
|
760
|
-
urls: [
|
|
761
|
-
'https://example.com/video.mp4',
|
|
762
|
-
'https://example.com/video.webm'
|
|
763
|
-
],
|
|
764
|
-
coordinates: [
|
|
765
|
-
[-122.5, 37.9],
|
|
766
|
-
[-122.3, 37.9],
|
|
767
|
-
[-122.3, 37.7],
|
|
768
|
-
[-122.5, 37.7]
|
|
769
|
-
]
|
|
770
|
-
});
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
### Controls Schema
|
|
774
|
-
|
|
775
|
-
Configure map controls with the `ControlsSchema`:
|
|
776
|
-
|
|
777
|
-
```typescript
|
|
778
|
-
import { ControlsSchema } from '@maplibre-yaml/core/schemas';
|
|
779
|
-
|
|
780
|
-
const controls = ControlsSchema.parse({
|
|
781
|
-
navigation: {
|
|
782
|
-
enabled: true,
|
|
783
|
-
position: 'top-right',
|
|
784
|
-
showCompass: true,
|
|
785
|
-
showZoom: true,
|
|
786
|
-
visualizePitch: true
|
|
787
|
-
},
|
|
788
|
-
scale: {
|
|
789
|
-
enabled: true,
|
|
790
|
-
position: 'bottom-left',
|
|
791
|
-
maxWidth: 100,
|
|
792
|
-
unit: 'metric' // 'metric' | 'imperial' | 'nautical'
|
|
793
|
-
},
|
|
794
|
-
geolocation: {
|
|
795
|
-
enabled: true,
|
|
796
|
-
position: 'top-right',
|
|
797
|
-
trackUserLocation: true,
|
|
798
|
-
showUserHeading: true,
|
|
799
|
-
showAccuracyCircle: true
|
|
800
|
-
},
|
|
801
|
-
fullscreen: {
|
|
802
|
-
enabled: true,
|
|
803
|
-
position: 'top-right'
|
|
804
|
-
}
|
|
805
|
-
});
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Legend Schema
|
|
809
|
-
|
|
810
|
-
Create dynamic legends with the `LegendSchema`:
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
import { LegendSchema } from '@maplibre-yaml/core/schemas';
|
|
814
|
-
|
|
815
|
-
const legend = LegendSchema.parse({
|
|
816
|
-
enabled: true,
|
|
817
|
-
position: 'bottom-left',
|
|
818
|
-
title: 'Map Legend',
|
|
819
|
-
entries: [
|
|
820
|
-
{
|
|
821
|
-
label: 'Active',
|
|
822
|
-
color: '#22c55e',
|
|
823
|
-
shape: 'circle' // 'circle' | 'square' | 'line'
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
label: 'Delayed',
|
|
827
|
-
color: '#f59e0b',
|
|
828
|
-
shape: 'circle'
|
|
829
|
-
},
|
|
830
|
-
{
|
|
831
|
-
label: 'Inactive',
|
|
832
|
-
color: '#6b7280',
|
|
833
|
-
shape: 'circle'
|
|
834
|
-
}
|
|
835
|
-
],
|
|
836
|
-
style: {
|
|
837
|
-
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
838
|
-
padding: '10px',
|
|
839
|
-
borderRadius: '4px'
|
|
840
|
-
}
|
|
841
|
-
});
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
### Interaction Schema
|
|
845
|
-
|
|
846
|
-
Define click handlers and popups:
|
|
847
|
-
|
|
848
|
-
```typescript
|
|
849
|
-
import { InteractionSchema } from '@maplibre-yaml/core/schemas';
|
|
850
|
-
|
|
851
|
-
const interaction = InteractionSchema.parse({
|
|
852
|
-
type: 'click',
|
|
853
|
-
layers: ['points', 'polygons'],
|
|
854
|
-
popup: {
|
|
855
|
-
content: `
|
|
856
|
-
<h3>{{name}}</h3>
|
|
857
|
-
<p>{{description}}</p>
|
|
858
|
-
<p><strong>Category:</strong> {{category}}</p>
|
|
859
|
-
<p><strong>Value:</strong> {{value}}</p>
|
|
860
|
-
`,
|
|
861
|
-
closeButton: true,
|
|
862
|
-
closeOnClick: true,
|
|
863
|
-
maxWidth: '300px',
|
|
864
|
-
className: 'custom-popup'
|
|
865
|
-
},
|
|
866
|
-
action: 'popup' // 'popup' | 'toggle-visibility' | 'fly-to'
|
|
867
|
-
});
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
### Scrollytelling Schema (Page Configuration)
|
|
871
|
-
|
|
872
|
-
For narrative-driven maps with synchronized scrolling:
|
|
873
|
-
|
|
874
|
-
```typescript
|
|
875
|
-
import { PageConfigSchema } from '@maplibre-yaml/core/schemas';
|
|
876
|
-
|
|
877
|
-
const page = PageConfigSchema.parse({
|
|
878
|
-
type: 'page',
|
|
879
|
-
id: 'story-map',
|
|
880
|
-
title: 'Urban Growth Story',
|
|
881
|
-
description: 'Explore urban development over time',
|
|
882
|
-
sections: [
|
|
883
|
-
{
|
|
884
|
-
id: 'intro',
|
|
885
|
-
type: 'text',
|
|
886
|
-
title: 'Introduction',
|
|
887
|
-
content: 'This story explores urban development...',
|
|
888
|
-
style: {
|
|
889
|
-
backgroundColor: '#f9fafb',
|
|
890
|
-
padding: '60px'
|
|
891
|
-
}
|
|
892
|
-
},
|
|
893
|
-
{
|
|
894
|
-
id: 'map-section',
|
|
895
|
-
type: 'map',
|
|
896
|
-
config: {
|
|
897
|
-
center: [-122.4, 37.8],
|
|
898
|
-
zoom: 12,
|
|
899
|
-
style: 'https://demotiles.maplibre.org/style.json'
|
|
900
|
-
},
|
|
901
|
-
steps: [
|
|
902
|
-
{
|
|
903
|
-
id: 'step1',
|
|
904
|
-
content: '## 1990s\nUrban core development began...',
|
|
905
|
-
map: {
|
|
906
|
-
center: [-122.4, 37.8],
|
|
907
|
-
zoom: 13,
|
|
908
|
-
pitch: 0,
|
|
909
|
-
bearing: 0,
|
|
910
|
-
duration: 2000
|
|
911
|
-
},
|
|
912
|
-
layers: {
|
|
913
|
-
show: ['buildings-1990'],
|
|
914
|
-
hide: ['buildings-2000', 'buildings-2010']
|
|
915
|
-
}
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
id: 'step2',
|
|
919
|
-
content: '## 2000s\nSuburban expansion accelerated...',
|
|
920
|
-
map: {
|
|
921
|
-
center: [-122.3, 37.9],
|
|
922
|
-
zoom: 12,
|
|
923
|
-
pitch: 45,
|
|
924
|
-
bearing: -17.6,
|
|
925
|
-
duration: 2000
|
|
926
|
-
},
|
|
927
|
-
layers: {
|
|
928
|
-
show: ['buildings-1990', 'buildings-2000'],
|
|
929
|
-
hide: ['buildings-2010']
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
]
|
|
933
|
-
}
|
|
934
|
-
]
|
|
935
|
-
});
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
### Schema Composition
|
|
939
|
-
|
|
940
|
-
Schemas can be composed for complex configurations:
|
|
278
|
+
All schemas are built with Zod and provide TypeScript types:
|
|
941
279
|
|
|
942
280
|
```typescript
|
|
943
281
|
import {
|
|
944
|
-
|
|
282
|
+
// Layer schemas
|
|
945
283
|
LayerSchema,
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const map = MapSchema.parse({
|
|
957
|
-
type: 'map',
|
|
958
|
-
id: 'composed-map',
|
|
959
|
-
config: { /* ... */ },
|
|
960
|
-
layers: [layer],
|
|
961
|
-
sources: [{ id: 'my-source', ...source }],
|
|
962
|
-
controls
|
|
963
|
-
});
|
|
964
|
-
```
|
|
965
|
-
|
|
966
|
-
### Validation and Error Handling
|
|
967
|
-
|
|
968
|
-
Schemas provide detailed validation errors:
|
|
969
|
-
|
|
970
|
-
```typescript
|
|
971
|
-
import { MapConfigSchema } from '@maplibre-yaml/core/schemas';
|
|
972
|
-
import { ZodError } from 'zod';
|
|
973
|
-
|
|
974
|
-
try {
|
|
975
|
-
const config = MapConfigSchema.parse(invalidConfig);
|
|
976
|
-
} catch (error) {
|
|
977
|
-
if (error instanceof ZodError) {
|
|
978
|
-
error.issues.forEach(issue => {
|
|
979
|
-
console.error(
|
|
980
|
-
`${issue.path.join('.')}: ${issue.message}`
|
|
981
|
-
);
|
|
982
|
-
});
|
|
983
|
-
// Example output:
|
|
984
|
-
// layers.0.source: Required
|
|
985
|
-
// config.center: Expected array, received string
|
|
986
|
-
// layers.0.paint.circle-radius: Expected number, received string
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
```
|
|
284
|
+
CircleLayerSchema,
|
|
285
|
+
LineLayerSchema,
|
|
286
|
+
FillLayerSchema,
|
|
287
|
+
SymbolLayerSchema,
|
|
288
|
+
RasterLayerSchema,
|
|
289
|
+
FillExtrusionLayerSchema,
|
|
290
|
+
HeatmapLayerSchema,
|
|
291
|
+
HillshadeLayerSchema,
|
|
292
|
+
BackgroundLayerSchema,
|
|
990
293
|
|
|
991
|
-
|
|
294
|
+
// Source schemas
|
|
295
|
+
GeoJSONSourceSchema,
|
|
296
|
+
VectorSourceSchema,
|
|
297
|
+
RasterSourceSchema,
|
|
298
|
+
ImageSourceSchema,
|
|
299
|
+
VideoSourceSchema,
|
|
992
300
|
|
|
993
|
-
|
|
301
|
+
// Map schemas
|
|
302
|
+
MapConfigSchema,
|
|
303
|
+
MapBlockSchema,
|
|
304
|
+
ControlsConfigSchema,
|
|
305
|
+
LegendConfigSchema,
|
|
306
|
+
|
|
307
|
+
// Scrollytelling schemas
|
|
308
|
+
ChapterSchema,
|
|
309
|
+
ScrollytellingBlockSchema,
|
|
310
|
+
|
|
311
|
+
// Page schemas
|
|
312
|
+
PageSchema,
|
|
313
|
+
RootSchema,
|
|
314
|
+
GlobalConfigSchema,
|
|
315
|
+
|
|
316
|
+
// Base schemas
|
|
317
|
+
LngLatSchema,
|
|
318
|
+
ColorSchema,
|
|
319
|
+
ExpressionSchema,
|
|
320
|
+
} from '@maplibre-yaml/core/schemas';
|
|
994
321
|
|
|
995
|
-
|
|
996
|
-
|
|
322
|
+
// Validation
|
|
323
|
+
const layer = LayerSchema.parse(data);
|
|
997
324
|
|
|
325
|
+
// Safe parsing
|
|
998
326
|
const result = LayerSchema.safeParse(data);
|
|
999
|
-
|
|
1000
327
|
if (result.success) {
|
|
1001
|
-
|
|
1002
|
-
console.log('Valid layer:', result.data);
|
|
1003
|
-
} else {
|
|
1004
|
-
// result.error contains validation issues
|
|
1005
|
-
console.error('Validation failed:', result.error.issues);
|
|
328
|
+
console.log(result.data);
|
|
1006
329
|
}
|
|
1007
330
|
```
|
|
1008
331
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
Schemas automatically generate TypeScript types:
|
|
332
|
+
Type inference:
|
|
1012
333
|
|
|
1013
334
|
```typescript
|
|
1014
|
-
import { LayerSchema, GeoJSONSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
1015
335
|
import type { z } from 'zod';
|
|
336
|
+
import { LayerSchema, MapBlockSchema } from '@maplibre-yaml/core/schemas';
|
|
1016
337
|
|
|
1017
|
-
// Infer types from schemas
|
|
1018
338
|
type Layer = z.infer<typeof LayerSchema>;
|
|
1019
|
-
type
|
|
1020
|
-
|
|
1021
|
-
// Use inferred types
|
|
1022
|
-
const createLayer = (layer: Layer) => {
|
|
1023
|
-
// layer is fully typed with autocomplete
|
|
1024
|
-
console.log(layer.id, layer.type, layer.paint);
|
|
1025
|
-
};
|
|
339
|
+
type MapBlock = z.infer<typeof MapBlockSchema>;
|
|
1026
340
|
```
|
|
1027
341
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
Extend schemas with custom validation:
|
|
1031
|
-
|
|
1032
|
-
```typescript
|
|
1033
|
-
import { LayerSchema } from '@maplibre-yaml/core/schemas';
|
|
1034
|
-
import { z } from 'zod';
|
|
1035
|
-
|
|
1036
|
-
// Add custom refinement
|
|
1037
|
-
const CustomLayerSchema = LayerSchema.refine(
|
|
1038
|
-
(layer) => {
|
|
1039
|
-
if (layer.type === 'circle' && layer.paint) {
|
|
1040
|
-
const radius = layer.paint['circle-radius'];
|
|
1041
|
-
return typeof radius === 'number' && radius > 0;
|
|
1042
|
-
}
|
|
1043
|
-
return true;
|
|
1044
|
-
},
|
|
1045
|
-
{
|
|
1046
|
-
message: 'Circle radius must be a positive number'
|
|
1047
|
-
}
|
|
1048
|
-
);
|
|
1049
|
-
```
|
|
1050
|
-
|
|
1051
|
-
### Schema Defaults
|
|
1052
|
-
|
|
1053
|
-
Many schemas include sensible defaults:
|
|
342
|
+
Exported types:
|
|
1054
343
|
|
|
1055
344
|
```typescript
|
|
1056
|
-
import {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
//
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
345
|
+
import type {
|
|
346
|
+
// Map types
|
|
347
|
+
MapConfig,
|
|
348
|
+
MapBlock,
|
|
349
|
+
MapFullPageBlock,
|
|
350
|
+
ControlPosition,
|
|
351
|
+
ControlsConfig,
|
|
352
|
+
LegendConfig,
|
|
353
|
+
|
|
354
|
+
// Scrollytelling types
|
|
355
|
+
Chapter,
|
|
356
|
+
ChapterAction,
|
|
357
|
+
ChapterLayers,
|
|
358
|
+
ScrollytellingBlock,
|
|
359
|
+
|
|
360
|
+
// Page types
|
|
361
|
+
Page,
|
|
362
|
+
Block,
|
|
363
|
+
MixedBlock,
|
|
364
|
+
GlobalConfig,
|
|
365
|
+
RootConfig,
|
|
366
|
+
} from '@maplibre-yaml/core/schemas';
|
|
1073
367
|
```
|
|
1074
368
|
|
|
1075
|
-
###
|
|
1076
|
-
|
|
1077
|
-
All schemas are exported from `@maplibre-yaml/core/schemas`:
|
|
369
|
+
### Web Components
|
|
1078
370
|
|
|
1079
371
|
```typescript
|
|
1080
|
-
import {
|
|
1081
|
-
// Top-level
|
|
1082
|
-
MapConfigSchema,
|
|
1083
|
-
PageConfigSchema,
|
|
372
|
+
import { MLMap, registerMLMap } from '@maplibre-yaml/core/components';
|
|
1084
373
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
MapConfigurationSchema,
|
|
374
|
+
// Register with default name
|
|
375
|
+
registerMLMap();
|
|
1088
376
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
LineLayerSchema,
|
|
1093
|
-
FillLayerSchema,
|
|
1094
|
-
SymbolLayerSchema,
|
|
1095
|
-
HeatmapLayerSchema,
|
|
377
|
+
// Or with custom name
|
|
378
|
+
customElements.define('my-map', MLMap);
|
|
379
|
+
```
|
|
1096
380
|
|
|
1097
|
-
|
|
1098
|
-
SourceSchema,
|
|
1099
|
-
GeoJSONSourceSchema,
|
|
1100
|
-
VectorSourceSchema,
|
|
1101
|
-
RasterSourceSchema,
|
|
1102
|
-
ImageSourceSchema,
|
|
1103
|
-
VideoSourceSchema,
|
|
381
|
+
Component usage:
|
|
1104
382
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
InteractionSchema,
|
|
383
|
+
```html
|
|
384
|
+
<!-- External YAML file -->
|
|
385
|
+
<ml-map src="/map.yaml"></ml-map>
|
|
1109
386
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
387
|
+
<!-- Inline YAML -->
|
|
388
|
+
<ml-map>
|
|
389
|
+
<script type="text/yaml">
|
|
390
|
+
type: map
|
|
391
|
+
config:
|
|
392
|
+
center: [0, 0]
|
|
393
|
+
zoom: 2
|
|
394
|
+
mapStyle: "https://..."
|
|
395
|
+
</script>
|
|
396
|
+
</ml-map>
|
|
1113
397
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
StreamConfigSchema,
|
|
1117
|
-
CacheConfigSchema,
|
|
1118
|
-
LoadingConfigSchema
|
|
1119
|
-
} from '@maplibre-yaml/core/schemas';
|
|
398
|
+
<!-- JSON attribute -->
|
|
399
|
+
<ml-map config='{"type":"map",...}'></ml-map>
|
|
1120
400
|
```
|
|
1121
401
|
|
|
1122
|
-
##
|
|
402
|
+
## Merge Strategies
|
|
1123
403
|
|
|
1124
|
-
|
|
404
|
+
| Strategy | Description |
|
|
405
|
+
|----------|-------------|
|
|
406
|
+
| `replace` | Replace all data on each update |
|
|
407
|
+
| `merge` | Update/add features by unique key |
|
|
408
|
+
| `append-window` | Append with size/time-based windowing |
|
|
1125
409
|
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
GeoJSONSourceConfig,
|
|
1131
|
-
MergeStrategy,
|
|
1132
|
-
PollingConfig,
|
|
1133
|
-
StreamConfig,
|
|
1134
|
-
LoadingConfig
|
|
1135
|
-
} from '@maplibre-yaml/core';
|
|
1136
|
-
|
|
1137
|
-
const config: MapConfig = {
|
|
1138
|
-
type: 'map',
|
|
1139
|
-
id: 'my-map',
|
|
1140
|
-
config: {
|
|
1141
|
-
center: [-122.4, 37.8],
|
|
1142
|
-
zoom: 12
|
|
1143
|
-
},
|
|
1144
|
-
layers: []
|
|
1145
|
-
};
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
## Performance Considerations
|
|
1149
|
-
|
|
1150
|
-
### Caching
|
|
1151
|
-
- Default cache TTL: 5 minutes
|
|
1152
|
-
- Cache respects `Cache-Control` headers
|
|
1153
|
-
- Cache size limit: 50 entries (LRU eviction)
|
|
1154
|
-
- Disable cache for real-time data
|
|
410
|
+
```yaml
|
|
411
|
+
refresh:
|
|
412
|
+
updateStrategy: merge
|
|
413
|
+
updateKey: id
|
|
1155
414
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
415
|
+
# OR for windowed appending
|
|
416
|
+
refresh:
|
|
417
|
+
updateStrategy: append-window
|
|
418
|
+
windowSize: 100
|
|
419
|
+
windowDuration: 300000
|
|
420
|
+
timestampField: timestamp
|
|
421
|
+
```
|
|
1161
422
|
|
|
1162
|
-
|
|
1163
|
-
- Automatic reconnection with exponential backoff
|
|
1164
|
-
- Connection state tracking
|
|
1165
|
-
- Graceful degradation on connection loss
|
|
1166
|
-
- Efficient binary frame handling (WebSocket)
|
|
423
|
+
## Layer Types
|
|
1167
424
|
|
|
1168
|
-
|
|
1169
|
-
-
|
|
1170
|
-
-
|
|
1171
|
-
-
|
|
1172
|
-
-
|
|
425
|
+
- `circle` - Point data as circles
|
|
426
|
+
- `line` - Linear features
|
|
427
|
+
- `fill` - Polygon fills
|
|
428
|
+
- `fill-extrusion` - 3D buildings/polygons
|
|
429
|
+
- `symbol` - Icons and text labels
|
|
430
|
+
- `heatmap` - Density visualization
|
|
431
|
+
- `hillshade` - Terrain shading
|
|
432
|
+
- `raster` - Raster tiles
|
|
433
|
+
- `background` - Map background
|
|
1173
434
|
|
|
1174
435
|
## Browser Support
|
|
1175
436
|
|
|
1176
437
|
- Modern browsers with ES2022 support
|
|
1177
438
|
- Native `fetch`, `EventSource`, and `WebSocket` APIs
|
|
1178
|
-
- ResizeObserver for responsive layouts
|
|
1179
|
-
- Document visibility API for polling optimization
|
|
1180
|
-
|
|
1181
|
-
## Bundle Size
|
|
1182
|
-
|
|
1183
|
-
- Core package: ~136KB (unminified)
|
|
1184
|
-
- Tree-shakeable ES modules
|
|
1185
|
-
- Zero runtime dependencies (peer dependency: maplibre-gl)
|
|
1186
|
-
|
|
1187
|
-
## Examples
|
|
1188
|
-
|
|
1189
|
-
See the [examples directory](../../examples) for complete working examples:
|
|
1190
|
-
|
|
1191
|
-
- Basic static map
|
|
1192
|
-
- Real-time vehicle tracking
|
|
1193
|
-
- Sensor data heatmap with SSE
|
|
1194
|
-
- Interactive point-of-interest map
|
|
1195
|
-
- Multi-layer dashboard
|
|
1196
|
-
|
|
1197
|
-
## Contributing
|
|
1198
|
-
|
|
1199
|
-
Contributions are welcome! Please read the [contributing guidelines](../../CONTRIBUTING.md) first.
|
|
1200
|
-
|
|
1201
|
-
## License
|
|
1202
|
-
|
|
1203
|
-
MIT � [Your Name]
|
|
1204
439
|
|
|
1205
440
|
## Related Packages
|
|
1206
441
|
|
|
1207
442
|
- [`@maplibre-yaml/astro`](../astro) - Astro component integration
|
|
1208
|
-
- [`@maplibre-yaml/cli`](../cli) - CLI
|
|
443
|
+
- [`@maplibre-yaml/cli`](../cli) - CLI for YAML validation
|
|
1209
444
|
|
|
1210
|
-
##
|
|
445
|
+
## License
|
|
1211
446
|
|
|
1212
|
-
|
|
1213
|
-
- [GeoJSON Specification](https://geojson.org/)
|
|
1214
|
-
- [YAML Specification](https://yaml.org/)
|
|
447
|
+
MIT
|