@maplibre-yaml/core 0.1.0-alpha.0 → 0.1.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/README.md +1214 -0
- package/dist/components/index.d.ts +4 -83
- package/dist/components/index.js +4318 -3919
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +6 -315
- package/dist/index.js +115 -18
- package/dist/index.js.map +1 -1
- package/dist/map-renderer-DOLO9y-3.d.ts +522 -0
- package/dist/{map.schema-EnZRrtIh.d.ts → page.schema-CzdCyPFI.d.ts} +775 -368
- package/dist/register.d.ts +205 -0
- package/dist/register.js +4704 -0
- package/dist/register.js.map +1 -0
- package/dist/schemas/index.d.ts +12 -419
- package/dist/schemas/index.js +4 -2
- package/dist/schemas/index.js.map +1 -1
- package/package.json +18 -13
- package/LICENSE.md +0 -21
- package/dist/map-renderer-RQc5_bdo.d.ts +0 -149
package/README.md
ADDED
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
# @maplibre-yaml/core
|
|
2
|
+
|
|
3
|
+
> Declarative web maps with YAML configuration. Build interactive MapLibre maps using simple, readable YAML syntax.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@maplibre-yaml/core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
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
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @maplibre-yaml/core maplibre-gl
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### Using Web Components
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<!DOCTYPE html>
|
|
54
|
+
<html>
|
|
55
|
+
<head>
|
|
56
|
+
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css">
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<ml-map config="./map.yaml"></ml-map>
|
|
60
|
+
|
|
61
|
+
<script type="module">
|
|
62
|
+
import '@maplibre-yaml/core/components';
|
|
63
|
+
</script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Using JavaScript API
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { YAMLParser, MapRenderer } from '@maplibre-yaml/core';
|
|
72
|
+
|
|
73
|
+
const yaml = `
|
|
74
|
+
type: map
|
|
75
|
+
config:
|
|
76
|
+
center: [-122.4, 37.8]
|
|
77
|
+
zoom: 12
|
|
78
|
+
style: https://demotiles.maplibre.org/style.json
|
|
79
|
+
layers:
|
|
80
|
+
- id: points
|
|
81
|
+
type: circle
|
|
82
|
+
source:
|
|
83
|
+
type: geojson
|
|
84
|
+
url: https://example.com/data.geojson
|
|
85
|
+
paint:
|
|
86
|
+
circle-radius: 6
|
|
87
|
+
circle-color: "#3b82f6"
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const parser = new YAMLParser();
|
|
91
|
+
const config = await parser.parse(yaml);
|
|
92
|
+
|
|
93
|
+
const container = document.getElementById('map');
|
|
94
|
+
const renderer = new MapRenderer(container, config);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## YAML Configuration
|
|
98
|
+
|
|
99
|
+
### Basic Map
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
type: map
|
|
103
|
+
id: my-map
|
|
104
|
+
config:
|
|
105
|
+
center: [-74.006, 40.7128]
|
|
106
|
+
zoom: 10
|
|
107
|
+
style: https://demotiles.maplibre.org/style.json
|
|
108
|
+
|
|
109
|
+
layers:
|
|
110
|
+
- id: cities
|
|
111
|
+
type: circle
|
|
112
|
+
source:
|
|
113
|
+
type: geojson
|
|
114
|
+
data:
|
|
115
|
+
type: FeatureCollection
|
|
116
|
+
features:
|
|
117
|
+
- type: Feature
|
|
118
|
+
geometry:
|
|
119
|
+
type: Point
|
|
120
|
+
coordinates: [-74.006, 40.7128]
|
|
121
|
+
properties:
|
|
122
|
+
name: New York
|
|
123
|
+
paint:
|
|
124
|
+
circle-radius: 8
|
|
125
|
+
circle-color: "#ef4444"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Real-time Data with Polling
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
layers:
|
|
132
|
+
- id: vehicles
|
|
133
|
+
type: circle
|
|
134
|
+
source:
|
|
135
|
+
type: geojson
|
|
136
|
+
url: https://api.transit.example.com/vehicles.geojson
|
|
137
|
+
refresh:
|
|
138
|
+
refreshInterval: 5000 # Poll every 5 seconds
|
|
139
|
+
updateStrategy: merge # Merge by key
|
|
140
|
+
updateKey: vehicleId # Unique identifier
|
|
141
|
+
cache:
|
|
142
|
+
enabled: false # Disable cache for real-time data
|
|
143
|
+
loading:
|
|
144
|
+
enabled: true
|
|
145
|
+
message: Loading vehicles...
|
|
146
|
+
paint:
|
|
147
|
+
circle-radius: 6
|
|
148
|
+
circle-color:
|
|
149
|
+
- match
|
|
150
|
+
- ["get", "status"]
|
|
151
|
+
- "active"
|
|
152
|
+
- "#22c55e"
|
|
153
|
+
- "delayed"
|
|
154
|
+
- "#f59e0b"
|
|
155
|
+
- "#6b7280"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Real-time Streaming with WebSocket
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
layers:
|
|
162
|
+
- id: live-events
|
|
163
|
+
type: circle
|
|
164
|
+
source:
|
|
165
|
+
type: geojson
|
|
166
|
+
url: https://api.example.com/initial-events.geojson
|
|
167
|
+
stream:
|
|
168
|
+
type: websocket
|
|
169
|
+
url: wss://api.example.com/events
|
|
170
|
+
reconnect: true
|
|
171
|
+
reconnectMaxAttempts: 10
|
|
172
|
+
refresh:
|
|
173
|
+
updateStrategy: append-window
|
|
174
|
+
windowSize: 100 # Keep last 100 events
|
|
175
|
+
windowDuration: 300000 # Keep events from last 5 minutes
|
|
176
|
+
timestampField: timestamp
|
|
177
|
+
paint:
|
|
178
|
+
circle-radius: 8
|
|
179
|
+
circle-color: "#8b5cf6"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Server-Sent Events (SSE)
|
|
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
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
layers:
|
|
216
|
+
- id: locations
|
|
217
|
+
type: symbol
|
|
218
|
+
source:
|
|
219
|
+
type: geojson
|
|
220
|
+
url: https://example.com/locations.geojson
|
|
221
|
+
layout:
|
|
222
|
+
icon-image: marker
|
|
223
|
+
icon-size: 1.5
|
|
224
|
+
interactions:
|
|
225
|
+
- type: click
|
|
226
|
+
popup:
|
|
227
|
+
content: |
|
|
228
|
+
<h3>{{name}}</h3>
|
|
229
|
+
<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
|
+
```
|
|
259
|
+
|
|
260
|
+
## API Reference
|
|
261
|
+
|
|
262
|
+
### Core Classes
|
|
263
|
+
|
|
264
|
+
#### `YAMLParser`
|
|
265
|
+
Parse and validate YAML map configurations.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { YAMLParser } from '@maplibre-yaml/core';
|
|
269
|
+
|
|
270
|
+
const parser = new YAMLParser();
|
|
271
|
+
const config = await parser.parse(yamlString);
|
|
272
|
+
const errors = parser.validate(config);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### `MapRenderer`
|
|
276
|
+
Render maps from parsed configurations.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { MapRenderer } from '@maplibre-yaml/core';
|
|
280
|
+
|
|
281
|
+
const renderer = new MapRenderer(container, config, {
|
|
282
|
+
onLoad: () => console.log('Map loaded'),
|
|
283
|
+
onError: (error) => console.error('Map error:', error),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Update visibility
|
|
287
|
+
renderer.setLayerVisibility('layer-id', false);
|
|
288
|
+
|
|
289
|
+
// Update data
|
|
290
|
+
renderer.updateLayerData('layer-id', newGeoJSON);
|
|
291
|
+
|
|
292
|
+
// Clean up
|
|
293
|
+
renderer.destroy();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Data Management
|
|
297
|
+
|
|
298
|
+
#### `DataFetcher`
|
|
299
|
+
HTTP data fetching with caching and retry.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { DataFetcher } from '@maplibre-yaml/core';
|
|
303
|
+
|
|
304
|
+
const fetcher = new DataFetcher({
|
|
305
|
+
cache: {
|
|
306
|
+
enabled: true,
|
|
307
|
+
defaultTTL: 300000, // 5 minutes
|
|
308
|
+
maxSize: 50
|
|
309
|
+
},
|
|
310
|
+
retry: {
|
|
311
|
+
enabled: true,
|
|
312
|
+
maxRetries: 3,
|
|
313
|
+
initialDelay: 1000,
|
|
314
|
+
maxDelay: 10000
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
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
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `PollingManager`
|
|
327
|
+
Manage periodic data refresh.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { PollingManager } from '@maplibre-yaml/core';
|
|
331
|
+
|
|
332
|
+
const polling = new PollingManager();
|
|
333
|
+
|
|
334
|
+
polling.start('layer-id', {
|
|
335
|
+
interval: 5000,
|
|
336
|
+
onTick: async () => {
|
|
337
|
+
const data = await fetchData();
|
|
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
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Control polling
|
|
346
|
+
polling.pause('layer-id');
|
|
347
|
+
polling.resume('layer-id');
|
|
348
|
+
await polling.triggerNow('layer-id');
|
|
349
|
+
polling.stop('layer-id');
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### `StreamManager`
|
|
353
|
+
Manage WebSocket and SSE connections.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { StreamManager } from '@maplibre-yaml/core';
|
|
357
|
+
|
|
358
|
+
const streams = new StreamManager();
|
|
359
|
+
|
|
360
|
+
await streams.connect('stream-id', {
|
|
361
|
+
type: 'websocket',
|
|
362
|
+
url: 'wss://example.com/stream',
|
|
363
|
+
onData: (data) => {
|
|
364
|
+
console.log('Received:', data);
|
|
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
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Send data (WebSocket only)
|
|
378
|
+
streams.send('stream-id', { type: 'subscribe', channel: 'updates' });
|
|
379
|
+
|
|
380
|
+
// Disconnect
|
|
381
|
+
streams.disconnect('stream-id');
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### `DataMerger`
|
|
385
|
+
Merge GeoJSON data with different strategies.
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import { DataMerger } from '@maplibre-yaml/core';
|
|
389
|
+
|
|
390
|
+
const merger = new DataMerger();
|
|
391
|
+
|
|
392
|
+
const result = merger.merge(existingData, incomingData, {
|
|
393
|
+
strategy: 'merge',
|
|
394
|
+
updateKey: 'id'
|
|
395
|
+
});
|
|
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
|
+
```
|
|
553
|
+
|
|
554
|
+
### Layer Schema
|
|
555
|
+
|
|
556
|
+
Supports all MapLibre layer types with full paint and layout properties:
|
|
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:
|
|
941
|
+
|
|
942
|
+
```typescript
|
|
943
|
+
import {
|
|
944
|
+
MapSchema,
|
|
945
|
+
LayerSchema,
|
|
946
|
+
GeoJSONSourceSchema,
|
|
947
|
+
ControlsSchema,
|
|
948
|
+
LegendSchema
|
|
949
|
+
} from '@maplibre-yaml/core/schemas';
|
|
950
|
+
|
|
951
|
+
// Build configuration programmatically
|
|
952
|
+
const layer = LayerSchema.parse({ /* ... */ });
|
|
953
|
+
const source = GeoJSONSourceSchema.parse({ /* ... */ });
|
|
954
|
+
const controls = ControlsSchema.parse({ /* ... */ });
|
|
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
|
+
```
|
|
990
|
+
|
|
991
|
+
### Safe Parsing
|
|
992
|
+
|
|
993
|
+
Use `safeParse` for non-throwing validation:
|
|
994
|
+
|
|
995
|
+
```typescript
|
|
996
|
+
import { LayerSchema } from '@maplibre-yaml/core/schemas';
|
|
997
|
+
|
|
998
|
+
const result = LayerSchema.safeParse(data);
|
|
999
|
+
|
|
1000
|
+
if (result.success) {
|
|
1001
|
+
// result.data is typed and valid
|
|
1002
|
+
console.log('Valid layer:', result.data);
|
|
1003
|
+
} else {
|
|
1004
|
+
// result.error contains validation issues
|
|
1005
|
+
console.error('Validation failed:', result.error.issues);
|
|
1006
|
+
}
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Type Inference
|
|
1010
|
+
|
|
1011
|
+
Schemas automatically generate TypeScript types:
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
import { LayerSchema, GeoJSONSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
1015
|
+
import type { z } from 'zod';
|
|
1016
|
+
|
|
1017
|
+
// Infer types from schemas
|
|
1018
|
+
type Layer = z.infer<typeof LayerSchema>;
|
|
1019
|
+
type GeoJSONSource = z.infer<typeof GeoJSONSourceSchema>;
|
|
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
|
+
};
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Custom Validation
|
|
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:
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
import { GeoJSONSourceSchema } from '@maplibre-yaml/core/schemas';
|
|
1057
|
+
|
|
1058
|
+
const source = GeoJSONSourceSchema.parse({
|
|
1059
|
+
type: 'geojson',
|
|
1060
|
+
url: 'https://example.com/data.geojson'
|
|
1061
|
+
// Defaults applied:
|
|
1062
|
+
// - fetchStrategy: 'runtime'
|
|
1063
|
+
// - cluster: false
|
|
1064
|
+
// - clusterRadius: 50
|
|
1065
|
+
// - tolerance: 0.375
|
|
1066
|
+
// - cache.enabled: true
|
|
1067
|
+
// - loading.enabled: false
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
console.log(source.fetchStrategy); // 'runtime'
|
|
1071
|
+
console.log(source.cluster); // false
|
|
1072
|
+
console.log(source.clusterRadius); // 50
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
### Available Schemas
|
|
1076
|
+
|
|
1077
|
+
All schemas are exported from `@maplibre-yaml/core/schemas`:
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
import {
|
|
1081
|
+
// Top-level
|
|
1082
|
+
MapConfigSchema,
|
|
1083
|
+
PageConfigSchema,
|
|
1084
|
+
|
|
1085
|
+
// Map components
|
|
1086
|
+
MapSchema,
|
|
1087
|
+
MapConfigurationSchema,
|
|
1088
|
+
|
|
1089
|
+
// Layers
|
|
1090
|
+
LayerSchema,
|
|
1091
|
+
CircleLayerSchema,
|
|
1092
|
+
LineLayerSchema,
|
|
1093
|
+
FillLayerSchema,
|
|
1094
|
+
SymbolLayerSchema,
|
|
1095
|
+
HeatmapLayerSchema,
|
|
1096
|
+
|
|
1097
|
+
// Sources
|
|
1098
|
+
SourceSchema,
|
|
1099
|
+
GeoJSONSourceSchema,
|
|
1100
|
+
VectorSourceSchema,
|
|
1101
|
+
RasterSourceSchema,
|
|
1102
|
+
ImageSourceSchema,
|
|
1103
|
+
VideoSourceSchema,
|
|
1104
|
+
|
|
1105
|
+
// Configuration
|
|
1106
|
+
ControlsSchema,
|
|
1107
|
+
LegendSchema,
|
|
1108
|
+
InteractionSchema,
|
|
1109
|
+
|
|
1110
|
+
// Scrollytelling
|
|
1111
|
+
SectionSchema,
|
|
1112
|
+
StepSchema,
|
|
1113
|
+
|
|
1114
|
+
// Data management
|
|
1115
|
+
RefreshConfigSchema,
|
|
1116
|
+
StreamConfigSchema,
|
|
1117
|
+
CacheConfigSchema,
|
|
1118
|
+
LoadingConfigSchema
|
|
1119
|
+
} from '@maplibre-yaml/core/schemas';
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
## TypeScript Support
|
|
1123
|
+
|
|
1124
|
+
Full TypeScript support with exported types:
|
|
1125
|
+
|
|
1126
|
+
```typescript
|
|
1127
|
+
import type {
|
|
1128
|
+
MapConfig,
|
|
1129
|
+
LayerConfig,
|
|
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
|
|
1155
|
+
|
|
1156
|
+
### Polling
|
|
1157
|
+
- Minimum interval: 1000ms (1 second)
|
|
1158
|
+
- Non-overlapping execution (waits for previous tick to complete)
|
|
1159
|
+
- Automatic pause when document is hidden
|
|
1160
|
+
- Configurable error handling and continuation
|
|
1161
|
+
|
|
1162
|
+
### Streaming
|
|
1163
|
+
- Automatic reconnection with exponential backoff
|
|
1164
|
+
- Connection state tracking
|
|
1165
|
+
- Graceful degradation on connection loss
|
|
1166
|
+
- Efficient binary frame handling (WebSocket)
|
|
1167
|
+
|
|
1168
|
+
### Memory Management
|
|
1169
|
+
- All managers implement proper cleanup via `destroy()`
|
|
1170
|
+
- Automatic timer and connection cleanup
|
|
1171
|
+
- DOM element removal
|
|
1172
|
+
- No memory leaks in long-running applications
|
|
1173
|
+
|
|
1174
|
+
## Browser Support
|
|
1175
|
+
|
|
1176
|
+
- Modern browsers with ES2022 support
|
|
1177
|
+
- 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
|
+
|
|
1205
|
+
## Related Packages
|
|
1206
|
+
|
|
1207
|
+
- [`@maplibre-yaml/astro`](../astro) - Astro component integration
|
|
1208
|
+
- [`@maplibre-yaml/cli`](../cli) - CLI tools for YAML validation and processing
|
|
1209
|
+
|
|
1210
|
+
## Resources
|
|
1211
|
+
|
|
1212
|
+
- [MapLibre GL JS Documentation](https://maplibre.org/maplibre-gl-js-docs/)
|
|
1213
|
+
- [GeoJSON Specification](https://geojson.org/)
|
|
1214
|
+
- [YAML Specification](https://yaml.org/)
|