@deck.gl-community/basemap-layers 9.3.0-beta.2

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.
Files changed (62) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +14 -0
  3. package/dist/atmosphere-layer.d.ts +11 -0
  4. package/dist/atmosphere-layer.d.ts.map +1 -0
  5. package/dist/atmosphere-layer.js +15 -0
  6. package/dist/atmosphere-layer.js.map +1 -0
  7. package/dist/basemap-layer.d.ts +67 -0
  8. package/dist/basemap-layer.d.ts.map +1 -0
  9. package/dist/basemap-layer.js +115 -0
  10. package/dist/basemap-layer.js.map +1 -0
  11. package/dist/globe-layers.d.ts +22 -0
  12. package/dist/globe-layers.d.ts.map +1 -0
  13. package/dist/globe-layers.js +451 -0
  14. package/dist/globe-layers.js.map +1 -0
  15. package/dist/index.cjs +947 -0
  16. package/dist/index.cjs.map +7 -0
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +4 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/map-style-loader.d.ts +49 -0
  22. package/dist/map-style-loader.d.ts.map +1 -0
  23. package/dist/map-style-loader.js +35 -0
  24. package/dist/map-style-loader.js.map +1 -0
  25. package/dist/map-style-schema.d.ts +71 -0
  26. package/dist/map-style-schema.d.ts.map +1 -0
  27. package/dist/map-style-schema.js +45 -0
  28. package/dist/map-style-schema.js.map +1 -0
  29. package/dist/map-style.cjs +250 -0
  30. package/dist/map-style.cjs.map +7 -0
  31. package/dist/map-style.d.ts +7 -0
  32. package/dist/map-style.d.ts.map +1 -0
  33. package/dist/map-style.js +6 -0
  34. package/dist/map-style.js.map +1 -0
  35. package/dist/mapbox-style.d.ts +41 -0
  36. package/dist/mapbox-style.d.ts.map +1 -0
  37. package/dist/mapbox-style.js +113 -0
  38. package/dist/mapbox-style.js.map +1 -0
  39. package/dist/mvt-label-layer.d.ts +8142 -0
  40. package/dist/mvt-label-layer.d.ts.map +1 -0
  41. package/dist/mvt-label-layer.js +175 -0
  42. package/dist/mvt-label-layer.js.map +1 -0
  43. package/dist/style-resolver.d.ts +88 -0
  44. package/dist/style-resolver.d.ts.map +1 -0
  45. package/dist/style-resolver.js +63 -0
  46. package/dist/style-resolver.js.map +1 -0
  47. package/dist/util.d.ts +21 -0
  48. package/dist/util.d.ts.map +1 -0
  49. package/dist/util.js +18 -0
  50. package/dist/util.js.map +1 -0
  51. package/package.json +60 -0
  52. package/src/atmosphere-layer.ts +15 -0
  53. package/src/basemap-layer.ts +183 -0
  54. package/src/globe-layers.ts +780 -0
  55. package/src/index.ts +3 -0
  56. package/src/map-style-loader.ts +52 -0
  57. package/src/map-style-schema.ts +54 -0
  58. package/src/map-style.ts +18 -0
  59. package/src/mapbox-style.ts +196 -0
  60. package/src/mvt-label-layer.ts +269 -0
  61. package/src/style-resolver.ts +173 -0
  62. package/src/util.ts +38 -0
@@ -0,0 +1,780 @@
1
+ import {COORDINATE_SYSTEM, log} from '@deck.gl/core';
2
+ import {MVTLayer, TileLayer, _getURLFromTemplate} from '@deck.gl/geo-layers';
3
+ import {BitmapLayer, GeoJsonLayer, SolidPolygonLayer} from '@deck.gl/layers';
4
+ import {MVTWorkerLoader} from '@loaders.gl/mvt';
5
+ import {getGlobeAtmosphereLayer, getGlobeAtmosphereSkyLayer} from './atmosphere-layer';
6
+ import {MVTLabelLayer} from './mvt-label-layer';
7
+ import {filterFeatures, parseProperties} from './map-style';
8
+ import type {BasemapGlobeConfig, BasemapLayerProps} from './basemap-layer';
9
+ import type {
10
+ BasemapLoadOptions,
11
+ BasemapSource,
12
+ BasemapStyleLayer,
13
+ ResolvedBasemapStyle
14
+ } from './style-resolver';
15
+
16
+ type BasemapMode = NonNullable<BasemapLayerProps['mode']>;
17
+
18
+ type BasemapLayerConfig = {
19
+ atmosphere: boolean;
20
+ basemap: boolean;
21
+ labels: boolean;
22
+ };
23
+
24
+ type BasemapLayerGroup = {
25
+ idPrefix?: string;
26
+ mode?: BasemapMode;
27
+ globe?: {config?: BasemapGlobeConfig};
28
+ styleDefinition: ResolvedBasemapStyle;
29
+ zoom?: number;
30
+ loadOptions?: BasemapLoadOptions;
31
+ };
32
+
33
+ type VectorSourceGroup = {
34
+ sourceId: string;
35
+ source: BasemapSource;
36
+ styleLayers: BasemapStyleLayer[];
37
+ };
38
+
39
+ function logBasemapRuntimeEvent(message: string, details?: unknown): void {
40
+ log.info(`[BasemapLayer] ${message}`, details ?? '')();
41
+ }
42
+
43
+ function logBasemapRuntimeError(message: string, error: unknown, details?: unknown): void {
44
+ log.error(`[BasemapLayer] ${message}`, details || '', error)();
45
+ }
46
+
47
+ function getBackgroundParameters(mode: BasemapMode) {
48
+ return (
49
+ mode === 'globe'
50
+ ? {depthTest: true, depthWriteEnabled: true, depthCompare: 'less-equal', cullMode: 'back'}
51
+ : {depthTest: false, cullMode: 'none'}
52
+ ) as any;
53
+ }
54
+
55
+ function getTileParameters(mode: BasemapMode) {
56
+ return (
57
+ mode === 'globe'
58
+ ? {depthTest: true, depthWriteEnabled: true, depthCompare: 'less-equal', cullMode: 'back'}
59
+ : {depthTest: false, cullMode: 'none'}
60
+ ) as any;
61
+ }
62
+
63
+ const BACKGROUND_DATA = [
64
+ [
65
+ [-180, 90],
66
+ [0, 90],
67
+ [180, 90],
68
+ [180, -90],
69
+ [0, -90],
70
+ [-180, -90]
71
+ ]
72
+ ];
73
+
74
+ const BACKGROUND_NORTH_POLE_DATA = [
75
+ [
76
+ [-180, 90],
77
+ [0, 90],
78
+ [180, 90],
79
+ [180, 85],
80
+ [0, 85],
81
+ [-180, 85]
82
+ ]
83
+ ];
84
+
85
+ const SUPPORTED_TYPES = new Set(['background', 'fill', 'line', 'symbol', 'raster']);
86
+ const DEFAULT_CONFIG: BasemapLayerConfig = {atmosphere: false, basemap: true, labels: true};
87
+ function withOpacity(
88
+ color: number[] | null | undefined,
89
+ opacity = 1
90
+ ): [number, number, number, number] {
91
+ if (!color) {
92
+ return [0, 0, 0, 0];
93
+ }
94
+
95
+ const alpha = color.length > 3 ? (color[3] <= 1 ? color[3] * 255 : color[3]) : 255;
96
+ return [color[0], color[1], color[2], Math.round(alpha * opacity)];
97
+ }
98
+
99
+ function getPaint(layer: BasemapStyleLayer, zoom: number): Record<string, any> {
100
+ const properties = parseProperties(layer, {zoom});
101
+ return Object.fromEntries(
102
+ properties.map((entry) => [Object.keys(entry)[0], Object.values(entry)[0]])
103
+ );
104
+ }
105
+
106
+ function filterTileFeatures(features: any[], styleLayer: BasemapStyleLayer, zoom: number): any[] {
107
+ const sourceLayer = styleLayer['source-layer'];
108
+ const sourceFeatures = sourceLayer
109
+ ? features.filter((feature) => feature.properties?.layerName === sourceLayer)
110
+ : features;
111
+
112
+ if (!styleLayer.filter) {
113
+ return sourceFeatures;
114
+ }
115
+
116
+ return filterFeatures({
117
+ features: sourceFeatures,
118
+ filter: styleLayer.filter,
119
+ globalProperties: {zoom}
120
+ });
121
+ }
122
+
123
+ function isStyleLayerVisibleAtZoom(
124
+ styleLayer: BasemapStyleLayer,
125
+ zoom: number,
126
+ source?: BasemapSource
127
+ ): boolean {
128
+ const minZoom = styleLayer.minzoom ?? source?.minzoom ?? 0;
129
+ const maxZoom = styleLayer.maxzoom ?? source?.maxzoom ?? 22;
130
+
131
+ return zoom >= minZoom && zoom < maxZoom;
132
+ }
133
+
134
+ function getTileFeatures(data: unknown): any[] {
135
+ if (Array.isArray(data)) {
136
+ return data;
137
+ }
138
+
139
+ if (
140
+ data &&
141
+ typeof data === 'object' &&
142
+ Array.isArray((data as {features?: unknown[]}).features)
143
+ ) {
144
+ return (data as {features: unknown[]}).features as any[];
145
+ }
146
+
147
+ return [];
148
+ }
149
+
150
+ function getSubLayerBaseProps(props: any) {
151
+ const {
152
+ pickable,
153
+ visible,
154
+ opacity,
155
+ modelMatrix,
156
+ coordinateSystem,
157
+ coordinateOrigin,
158
+ extensions,
159
+ highlightedObjectIndex,
160
+ highlightColor,
161
+ parameters,
162
+ wrapLongitude
163
+ } = props;
164
+
165
+ return {
166
+ pickable,
167
+ visible,
168
+ opacity,
169
+ modelMatrix,
170
+ coordinateSystem,
171
+ coordinateOrigin,
172
+ extensions,
173
+ highlightedObjectIndex,
174
+ highlightColor,
175
+ parameters,
176
+ wrapLongitude
177
+ };
178
+ }
179
+
180
+ function getLineWidthScale(styleLayer: BasemapStyleLayer): number {
181
+ const sourceLayer = styleLayer['source-layer'] || '';
182
+ const id = styleLayer.id || '';
183
+
184
+ if (sourceLayer === 'transportation' || sourceLayer === 'boundary' || id.includes('road-')) {
185
+ return 0.55;
186
+ }
187
+
188
+ if (sourceLayer === 'waterway' || sourceLayer === 'aeroway') {
189
+ return 0.75;
190
+ }
191
+
192
+ return 1;
193
+ }
194
+
195
+ function getGlobeFillColor(color: [number, number, number, number], mode: BasemapMode) {
196
+ if (mode !== 'globe') {
197
+ return color;
198
+ }
199
+
200
+ return [color[0], color[1], color[2], 255] as [number, number, number, number];
201
+ }
202
+
203
+ function getConfig(globe?: {config?: BasemapGlobeConfig}): BasemapLayerConfig {
204
+ return {...DEFAULT_CONFIG, ...(globe?.config || {})};
205
+ }
206
+
207
+ function createBackgroundLayer({
208
+ idPrefix,
209
+ layer,
210
+ zoom,
211
+ mode
212
+ }: {
213
+ idPrefix: string;
214
+ layer: BasemapStyleLayer;
215
+ zoom: number;
216
+ mode: BasemapMode;
217
+ }) {
218
+ const paint = getPaint(layer, zoom);
219
+
220
+ return new SolidPolygonLayer({
221
+ id: `${idPrefix}-${layer.id}`,
222
+ data: BACKGROUND_DATA,
223
+ getPolygon: (d) => d,
224
+ stroked: false,
225
+ filled: true,
226
+ getFillColor: withOpacity(paint['background-color'], paint['background-opacity'] ?? 1),
227
+ parameters: getBackgroundParameters(mode)
228
+ });
229
+ }
230
+
231
+ function createRasterLayer({
232
+ idPrefix,
233
+ layer,
234
+ source,
235
+ mode
236
+ }: {
237
+ idPrefix: string;
238
+ layer: BasemapStyleLayer;
239
+ source: BasemapSource;
240
+ mode: BasemapMode;
241
+ }) {
242
+ return new TileLayer({
243
+ id: `${idPrefix}-${layer.id}`,
244
+ data: source.tiles,
245
+ minZoom: layer.minzoom ?? source.minzoom ?? 0,
246
+ maxZoom: layer.maxzoom ?? source.maxzoom ?? 22,
247
+ tileSize: source.tileSize || 512,
248
+ renderSubLayers: (props) => {
249
+ const {west, south, east, north} = (props.tile?.bbox || {}) as {
250
+ west: number;
251
+ south: number;
252
+ east: number;
253
+ north: number;
254
+ };
255
+
256
+ return new BitmapLayer({
257
+ ...props,
258
+ _imageCoordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
259
+ data: null,
260
+ image: props.data,
261
+ bounds: [west, south, east, north],
262
+ parameters: getTileParameters(mode)
263
+ } as any);
264
+ },
265
+ onTileError: (error) => {
266
+ logBasemapRuntimeError('Raster tile failed to load', error, {
267
+ layerId: layer.id,
268
+ sourceId: layer.source
269
+ });
270
+ },
271
+ parameters: getTileParameters(mode)
272
+ });
273
+ }
274
+
275
+ class StyledMVTLayer extends MVTLayer<any> {
276
+ getTileData(loadProps: {index: {x: number; y: number; z: number}; signal?: AbortSignal}) {
277
+ const data = this.props.data;
278
+ const url = _getURLFromTemplate(data, loadProps as any);
279
+ if (!url) {
280
+ return Promise.reject(new Error('Invalid URL'));
281
+ }
282
+
283
+ const loadOptions = this.getLoadOptions();
284
+ const coordinates = this.context.viewport.resolution ? 'wgs84' : 'local';
285
+
286
+ return this.props.fetch(url, {
287
+ propName: 'data',
288
+ layer: this,
289
+ signal: loadProps.signal,
290
+ loadOptions: {
291
+ ...loadOptions,
292
+ mimeType: 'application/x-protobuf',
293
+ mvt: {
294
+ ...((loadOptions?.mvt as Record<string, unknown> | undefined) || {}),
295
+ shape: 'geojson',
296
+ coordinates,
297
+ tileIndex: loadProps.index
298
+ }
299
+ }
300
+ });
301
+ }
302
+ }
303
+
304
+ StyledMVTLayer.layerName = 'StyledMVTLayer';
305
+ StyledMVTLayer.defaultProps = {
306
+ ...MVTLayer.defaultProps,
307
+ binary: false,
308
+ loaders: [MVTWorkerLoader]
309
+ };
310
+
311
+ function createStyledVectorSubLayer({
312
+ idPrefix,
313
+ sourceId,
314
+ styleLayer,
315
+ features,
316
+ props,
317
+ zoom,
318
+ config,
319
+ mode
320
+ }: {
321
+ idPrefix: string;
322
+ sourceId: string;
323
+ styleLayer: BasemapStyleLayer;
324
+ features: any[];
325
+ props: any;
326
+ zoom: number;
327
+ config: BasemapLayerConfig;
328
+ mode: BasemapMode;
329
+ }) {
330
+ if (features.length === 0) {
331
+ return null;
332
+ }
333
+
334
+ const paint = getPaint(styleLayer, zoom);
335
+ const opacity =
336
+ paint[`${styleLayer.type}-opacity`] ??
337
+ (styleLayer.type === 'fill' ? paint['fill-opacity'] : paint['line-opacity']) ??
338
+ 1;
339
+ const fillColor = withOpacity(paint['fill-color'], opacity);
340
+ const lineColor = withOpacity(
341
+ paint['line-color'] || paint['fill-outline-color'] || [0, 0, 0, 0],
342
+ opacity
343
+ );
344
+
345
+ if (styleLayer.type === 'symbol') {
346
+ return createSymbolSubLayer({props, styleLayer, features, config, mode, zoom, opacity, paint});
347
+ }
348
+
349
+ return createGeometrySubLayer({props, styleLayer, features, mode, fillColor, lineColor, paint});
350
+ }
351
+
352
+ function createVectorLayerGroup({
353
+ idPrefix,
354
+ sourceId,
355
+ source,
356
+ styleLayers,
357
+ zoom,
358
+ config,
359
+ loadOptions,
360
+ mode
361
+ }: {
362
+ idPrefix: string;
363
+ sourceId: string;
364
+ source: BasemapSource;
365
+ styleLayers: BasemapStyleLayer[];
366
+ zoom: number;
367
+ config: BasemapLayerConfig;
368
+ loadOptions?: BasemapLoadOptions;
369
+ mode: BasemapMode;
370
+ }) {
371
+ const minZoom = Math.min(...styleLayers.map((layer) => layer.minzoom ?? source.minzoom ?? 0));
372
+ const maxZoom = Math.max(...styleLayers.map((layer) => layer.maxzoom ?? source.maxzoom ?? 22));
373
+
374
+ return new StyledMVTLayer({
375
+ id: `${idPrefix}-${sourceId}`,
376
+ data: source.tiles,
377
+ binary: false,
378
+ minZoom,
379
+ maxZoom,
380
+ tileSize: source.tileSize || 512,
381
+ loadOptions: {
382
+ ...(loadOptions || {}),
383
+ mvt: {
384
+ ...((loadOptions?.mvt as Record<string, unknown> | undefined) || {}),
385
+ shape: 'geojson'
386
+ }
387
+ },
388
+ onTileError: (error) => {
389
+ logBasemapRuntimeError('Vector tile layer failed', error, {
390
+ sourceId
391
+ });
392
+ },
393
+ onTileLoad: (tile) => {
394
+ const features = Array.isArray(tile.content) ? tile.content : [];
395
+ if (features.length === 0) {
396
+ logBasemapRuntimeEvent('Loaded empty vector tile', {
397
+ sourceId,
398
+ tileIndex: tile.index
399
+ });
400
+ }
401
+ },
402
+ parameters: getTileParameters(mode),
403
+ renderSubLayers: (props) => {
404
+ const features = getTileFeatures(props.data);
405
+ const layers = styleLayers
406
+ .map((styleLayer) => {
407
+ if (!isStyleLayerVisibleAtZoom(styleLayer, zoom, source)) {
408
+ return null;
409
+ }
410
+
411
+ const filteredData = filterTileFeatures(features, styleLayer, zoom);
412
+
413
+ if (features.length > 0 && filteredData.length === 0) {
414
+ logBasemapRuntimeEvent('Vector tile rendered no matching features', {
415
+ sourceId,
416
+ layerId: styleLayer.id,
417
+ sourceLayer: styleLayer['source-layer'],
418
+ tileIndex: props.tile?.index
419
+ });
420
+ }
421
+
422
+ return createStyledVectorSubLayer({
423
+ idPrefix,
424
+ sourceId,
425
+ styleLayer,
426
+ features: filteredData,
427
+ props,
428
+ zoom,
429
+ config,
430
+ mode
431
+ });
432
+ })
433
+ .filter((layer) => Boolean(layer));
434
+
435
+ return layers as any;
436
+ }
437
+ } as any);
438
+ }
439
+
440
+ function inferWaterColor(
441
+ styleLayers: BasemapStyleLayer[],
442
+ zoom: number
443
+ ): [number, number, number, number] {
444
+ const waterLayer = styleLayers.find(
445
+ (layer) => layer.type === 'fill' && `${layer['source-layer'] || ''}`.includes('water')
446
+ );
447
+
448
+ if (!waterLayer) {
449
+ return [20, 40, 68, 255];
450
+ }
451
+
452
+ const paint = getPaint(waterLayer, zoom);
453
+ return withOpacity(paint['fill-color'], paint['fill-opacity'] ?? 1);
454
+ }
455
+
456
+ function getVectorSourceGroups(
457
+ styleLayers: BasemapStyleLayer[],
458
+ styleDefinition: ResolvedBasemapStyle
459
+ ): VectorSourceGroup[] {
460
+ const groups = new Map<string, VectorSourceGroup>();
461
+
462
+ for (const layer of styleLayers) {
463
+ const sourceId = layer.source;
464
+ const source = sourceId ? styleDefinition.sources?.[sourceId] : null;
465
+ if (sourceId && source?.tiles && source.type === 'vector') {
466
+ appendVectorSourceGroup(groups, sourceId, source, layer);
467
+ }
468
+ }
469
+
470
+ return [...groups.values()];
471
+ }
472
+
473
+ export function getBasemapLayers({
474
+ idPrefix = 'basemap',
475
+ mode = 'map',
476
+ globe,
477
+ styleDefinition,
478
+ zoom = 0,
479
+ loadOptions
480
+ }: BasemapLayerGroup) {
481
+ const config = getConfig(globe);
482
+ const styleLayers = (styleDefinition.layers || []).filter((layer) =>
483
+ SUPPORTED_TYPES.has(layer.type)
484
+ );
485
+ const layers: any[] = [];
486
+ logBasemapRuntimeEvent('Generating basemap layers', {
487
+ mode,
488
+ styleLayerCount: styleLayers.length,
489
+ sourceCount: Object.keys(styleDefinition.sources || {}).length
490
+ });
491
+
492
+ layers.push(...getGlobePreLayers({idPrefix, mode, config, styleLayers}));
493
+
494
+ if (config.basemap) {
495
+ layers.push(...getBackgroundLayers({idPrefix, styleLayers, zoom, mode}));
496
+ layers.push(
497
+ ...getVectorLayers({idPrefix, styleLayers, styleDefinition, zoom, config, loadOptions, mode})
498
+ );
499
+ layers.push(...getRasterLayers({idPrefix, styleLayers, styleDefinition, zoom, mode}));
500
+ }
501
+
502
+ layers.push(...getGlobePostLayers({idPrefix, mode, config, styleLayers, zoom}));
503
+
504
+ return layers.filter((layer) => Boolean(layer));
505
+ }
506
+
507
+ export function getGlobeBaseLayers({
508
+ globe,
509
+ styleDefinition,
510
+ idPrefix = 'globe-basemap',
511
+ zoom = 0,
512
+ loadOptions
513
+ }: Omit<BasemapLayerGroup, 'mode'>) {
514
+ return getBasemapLayers({idPrefix, mode: 'globe', globe, styleDefinition, zoom, loadOptions});
515
+ }
516
+
517
+ export function getGlobeTopLayers({globe}: {globe: {config: BasemapGlobeConfig}}) {
518
+ const {config} = globe;
519
+ return config.atmosphere ? [getGlobeAtmosphereLayer()] : [];
520
+ }
521
+
522
+ function createSymbolSubLayer({
523
+ props,
524
+ styleLayer,
525
+ features,
526
+ config,
527
+ mode,
528
+ zoom,
529
+ opacity,
530
+ paint
531
+ }: {
532
+ props: any;
533
+ styleLayer: BasemapStyleLayer;
534
+ features: any[];
535
+ config: BasemapLayerConfig;
536
+ mode: BasemapMode;
537
+ zoom: number;
538
+ opacity: number;
539
+ paint: Record<string, any>;
540
+ }) {
541
+ return new MVTLabelLayer({
542
+ ...getSubLayerBaseProps(props),
543
+ id: `${props.id}-${styleLayer.id}`,
544
+ data: features,
545
+ config,
546
+ mode,
547
+ styleLayer,
548
+ zoom,
549
+ textColor: withOpacity(paint['text-color'], opacity),
550
+ labelBackground: paint['text-halo-color']
551
+ ? withOpacity(paint['text-halo-color'], paint['text-halo-width'] ? 255 : opacity)
552
+ : null,
553
+ billboard: true
554
+ });
555
+ }
556
+
557
+ function createGeometrySubLayer({
558
+ props,
559
+ styleLayer,
560
+ features,
561
+ mode,
562
+ fillColor,
563
+ lineColor,
564
+ paint
565
+ }: {
566
+ props: any;
567
+ styleLayer: BasemapStyleLayer;
568
+ features: any[];
569
+ mode: BasemapMode;
570
+ fillColor: [number, number, number, number];
571
+ lineColor: [number, number, number, number];
572
+ paint: Record<string, any>;
573
+ }) {
574
+ const isLine = styleLayer.type === 'line';
575
+ const isFill = styleLayer.type === 'fill';
576
+
577
+ return new GeoJsonLayer({
578
+ ...getSubLayerBaseProps(props),
579
+ id: `${props.id}-${styleLayer.id}`,
580
+ data: features,
581
+ stroked: isLine,
582
+ filled: isFill,
583
+ getFillColor: isFill ? getGlobeFillColor(fillColor, mode) : [0, 0, 0, 0],
584
+ getLineColor: lineColor,
585
+ getLineWidth: isLine
586
+ ? Math.max(0.25, Number(paint['line-width'] ?? 1) * getLineWidthScale(styleLayer))
587
+ : 0,
588
+ lineWidthUnits: 'pixels',
589
+ lineWidthMinPixels: 0,
590
+ lineWidthMaxPixels: 20,
591
+ lineCapRounded: isLine,
592
+ lineJointRounded: isLine,
593
+ getPointRadius: 0,
594
+ pointRadiusMinPixels: 0,
595
+ parameters: getTileParameters(mode)
596
+ });
597
+ }
598
+
599
+ function getBackgroundLayers({
600
+ idPrefix,
601
+ styleLayers,
602
+ zoom,
603
+ mode
604
+ }: {
605
+ idPrefix: string;
606
+ styleLayers: BasemapStyleLayer[];
607
+ zoom: number;
608
+ mode: BasemapMode;
609
+ }) {
610
+ return styleLayers
611
+ .filter((layer) => layer.type === 'background' && isStyleLayerVisibleAtZoom(layer, zoom))
612
+ .map((layer) => createBackgroundLayer({idPrefix, layer, zoom, mode}));
613
+ }
614
+
615
+ function getVectorLayers({
616
+ idPrefix,
617
+ styleLayers,
618
+ styleDefinition,
619
+ zoom,
620
+ config,
621
+ loadOptions,
622
+ mode
623
+ }: {
624
+ idPrefix: string;
625
+ styleLayers: BasemapStyleLayer[];
626
+ styleDefinition: ResolvedBasemapStyle;
627
+ zoom: number;
628
+ config: BasemapLayerConfig;
629
+ loadOptions?: BasemapLoadOptions;
630
+ mode: BasemapMode;
631
+ }) {
632
+ const visibleVectorLayers = styleLayers.filter((layer) => {
633
+ if (!isStyleLayerVisibleAtZoom(layer, zoom, styleDefinition.sources?.[layer.source || ''])) {
634
+ return false;
635
+ }
636
+
637
+ if (layer.type === 'symbol') {
638
+ return config.labels;
639
+ }
640
+ return layer.type === 'fill' || layer.type === 'line';
641
+ });
642
+
643
+ return getVectorSourceGroups(visibleVectorLayers, styleDefinition).map((group) =>
644
+ createVectorLayerGroup({
645
+ idPrefix,
646
+ sourceId: group.sourceId,
647
+ source: group.source,
648
+ styleLayers: group.styleLayers,
649
+ zoom,
650
+ config,
651
+ loadOptions,
652
+ mode
653
+ })
654
+ );
655
+ }
656
+
657
+ function getRasterLayers({
658
+ idPrefix,
659
+ styleLayers,
660
+ styleDefinition,
661
+ zoom,
662
+ mode
663
+ }: {
664
+ idPrefix: string;
665
+ styleLayers: BasemapStyleLayer[];
666
+ styleDefinition: ResolvedBasemapStyle;
667
+ zoom: number;
668
+ mode: BasemapMode;
669
+ }) {
670
+ const rasterLayers = [];
671
+
672
+ for (const layer of styleLayers) {
673
+ if (
674
+ layer.type === 'raster' &&
675
+ isStyleLayerVisibleAtZoom(layer, zoom, styleDefinition.sources?.[layer.source || ''])
676
+ ) {
677
+ const source = styleDefinition.sources?.[layer.source];
678
+ if (!source?.tiles) {
679
+ logBasemapRuntimeEvent('Skipping style layer without resolved tiles', {
680
+ layerId: layer.id,
681
+ sourceId: layer.source
682
+ });
683
+ } else {
684
+ rasterLayers.push(createRasterLayer({idPrefix, layer, source, mode}));
685
+ }
686
+ }
687
+ }
688
+
689
+ return rasterLayers;
690
+ }
691
+
692
+ function appendVectorSourceGroup(
693
+ groups: Map<string, VectorSourceGroup>,
694
+ sourceId: string,
695
+ source: BasemapSource,
696
+ layer: BasemapStyleLayer
697
+ ) {
698
+ const group = groups.get(sourceId);
699
+ if (group) {
700
+ group.styleLayers.push(layer);
701
+ return;
702
+ }
703
+
704
+ groups.set(sourceId, {
705
+ sourceId,
706
+ source,
707
+ styleLayers: [layer]
708
+ });
709
+ }
710
+
711
+ function getGlobePreLayers({
712
+ idPrefix,
713
+ mode,
714
+ config,
715
+ styleLayers
716
+ }: {
717
+ idPrefix: string;
718
+ mode: BasemapMode;
719
+ config: BasemapLayerConfig;
720
+ styleLayers: BasemapStyleLayer[];
721
+ }) {
722
+ const layers = [];
723
+
724
+ if (mode === 'globe' && config.atmosphere) {
725
+ layers.push(getGlobeAtmosphereSkyLayer());
726
+ }
727
+
728
+ const hasBackground = styleLayers.some((layer) => layer.type === 'background');
729
+ if (mode === 'globe' && !hasBackground) {
730
+ layers.push(
731
+ new SolidPolygonLayer({
732
+ id: `${idPrefix}-background-fallback`,
733
+ data: BACKGROUND_DATA,
734
+ getPolygon: (d) => d,
735
+ stroked: false,
736
+ filled: true,
737
+ getFillColor: [10, 24, 46, 255],
738
+ parameters: getBackgroundParameters(mode)
739
+ })
740
+ );
741
+ }
742
+
743
+ return layers;
744
+ }
745
+
746
+ function getGlobePostLayers({
747
+ idPrefix,
748
+ mode,
749
+ config,
750
+ styleLayers,
751
+ zoom
752
+ }: {
753
+ idPrefix: string;
754
+ mode: BasemapMode;
755
+ config: BasemapLayerConfig;
756
+ styleLayers: BasemapStyleLayer[];
757
+ zoom: number;
758
+ }) {
759
+ const layers = [];
760
+
761
+ if (mode === 'globe' && config.basemap) {
762
+ layers.push(
763
+ new SolidPolygonLayer({
764
+ id: `${idPrefix}-background-north-pole`,
765
+ data: BACKGROUND_NORTH_POLE_DATA,
766
+ getPolygon: (d) => d,
767
+ stroked: false,
768
+ filled: true,
769
+ getFillColor: inferWaterColor(styleLayers, zoom),
770
+ parameters: getBackgroundParameters(mode)
771
+ })
772
+ );
773
+ }
774
+
775
+ if (mode === 'globe' && config.atmosphere) {
776
+ layers.push(getGlobeAtmosphereLayer());
777
+ }
778
+
779
+ return layers;
780
+ }