@tangram.gl/basemap-layers 0.0.0-stage → 0.0.1

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 (105) hide show
  1. package/README.md +34 -2
  2. package/THIRD-PARTY-NOTICES.md +205 -0
  3. package/dist/geojson-worker.js +2160 -0
  4. package/dist/geojson-worker.js.map +6 -0
  5. package/dist/index.cjs +11388 -0
  6. package/dist/index.cjs.map +6 -0
  7. package/dist/index.js +11369 -0
  8. package/dist/index.js.map +6 -0
  9. package/dist/map-style.cjs +445 -0
  10. package/dist/map-style.cjs.map +6 -0
  11. package/dist/map-style.js +431 -0
  12. package/dist/map-style.js.map +6 -0
  13. package/dist/types/arabic-shaping.d.ts +7 -0
  14. package/dist/types/atmosphere-layer.d.ts +10 -0
  15. package/dist/types/basemap-layer.d.ts +150 -0
  16. package/dist/types/bidi.d.ts +8 -0
  17. package/dist/types/circle-layer.d.ts +49 -0
  18. package/dist/types/fill-extrusion-lighting-extension.d.ts +49 -0
  19. package/dist/types/fill-outline-extension.d.ts +22 -0
  20. package/dist/types/fill-pattern.d.ts +99 -0
  21. package/dist/types/fill-style-extensions.d.ts +100 -0
  22. package/dist/types/geojson-source.d.ts +29 -0
  23. package/dist/types/geojson-tiler.d.ts +48 -0
  24. package/dist/types/geojson-worker.d.ts +40 -0
  25. package/dist/types/globe-horizon-extension.d.ts +61 -0
  26. package/dist/types/globe-layers.d.ts +239 -0
  27. package/dist/types/globe-line-angle-extension.d.ts +37 -0
  28. package/dist/types/glyph-font-renderer.d.ts +37 -0
  29. package/dist/types/glyph-pbf.d.ts +37 -0
  30. package/dist/types/glyph-store.d.ts +103 -0
  31. package/dist/types/grid-index.d.ts +47 -0
  32. package/dist/types/hillshade-layer.d.ts +154 -0
  33. package/dist/types/index.d.ts +9 -0
  34. package/dist/types/is-globe-viewport.d.ts +2 -0
  35. package/dist/types/label-font-renderer.d.ts +18 -0
  36. package/dist/types/label-placement.d.ts +247 -0
  37. package/dist/types/label-text-layer.d.ts +231 -0
  38. package/dist/types/line-blur-extension.d.ts +73 -0
  39. package/dist/types/line-glyphs.d.ts +60 -0
  40. package/dist/types/line-placement.d.ts +93 -0
  41. package/dist/types/map-style-loader.d.ts +48 -0
  42. package/dist/types/map-style-schema.d.ts +72 -0
  43. package/dist/types/map-style.d.ts +6 -0
  44. package/dist/types/mapbox-style.d.ts +40 -0
  45. package/dist/types/mvt-label-layer.d.ts +923 -0
  46. package/dist/types/polylabel.d.ts +17 -0
  47. package/dist/types/raster-color-extension.d.ts +146 -0
  48. package/dist/types/shared-texture.d.ts +34 -0
  49. package/dist/types/sort-key.d.ts +10 -0
  50. package/dist/types/sprite.d.ts +82 -0
  51. package/dist/types/style-accessor.d.ts +45 -0
  52. package/dist/types/style-expression.d.ts +83 -0
  53. package/dist/types/style-resolver.d.ts +94 -0
  54. package/dist/types/text-font.d.ts +36 -0
  55. package/dist/types/text-halo-blur-extension.d.ts +55 -0
  56. package/dist/types/text-layout.d.ts +71 -0
  57. package/dist/types/util.d.ts +22 -0
  58. package/dist/types/zoom-paint-extension.d.ts +72 -0
  59. package/package.json +80 -3
  60. package/src/arabic-shaping.ts +366 -0
  61. package/src/atmosphere-layer.ts +19 -0
  62. package/src/basemap-layer.ts +533 -0
  63. package/src/bidi.ts +199 -0
  64. package/src/circle-layer.ts +191 -0
  65. package/src/fill-extrusion-lighting-extension.ts +206 -0
  66. package/src/fill-outline-extension.ts +70 -0
  67. package/src/fill-pattern.ts +400 -0
  68. package/src/fill-style-extensions.ts +177 -0
  69. package/src/geojson-source.ts +318 -0
  70. package/src/geojson-tiler.ts +185 -0
  71. package/src/geojson-worker.ts +98 -0
  72. package/src/globe-horizon-extension.ts +64 -0
  73. package/src/globe-layers.ts +3487 -0
  74. package/src/globe-line-angle-extension.ts +131 -0
  75. package/src/glyph-font-renderer.ts +173 -0
  76. package/src/glyph-pbf.ts +164 -0
  77. package/src/glyph-store.ts +326 -0
  78. package/src/grid-index.ts +177 -0
  79. package/src/hillshade-layer.ts +775 -0
  80. package/src/index.ts +18 -0
  81. package/src/is-globe-viewport.ts +24 -0
  82. package/src/label-font-renderer.ts +159 -0
  83. package/src/label-placement.ts +727 -0
  84. package/src/label-text-layer.ts +913 -0
  85. package/src/line-blur-extension.ts +237 -0
  86. package/src/line-glyphs.ts +364 -0
  87. package/src/line-placement.ts +446 -0
  88. package/src/map-style-loader.ts +56 -0
  89. package/src/map-style-schema.ts +83 -0
  90. package/src/map-style.ts +23 -0
  91. package/src/mapbox-style.ts +168 -0
  92. package/src/mvt-label-layer.ts +3744 -0
  93. package/src/polylabel.ts +312 -0
  94. package/src/raster-color-extension.ts +245 -0
  95. package/src/shared-texture.ts +153 -0
  96. package/src/sort-key.ts +63 -0
  97. package/src/sprite.ts +244 -0
  98. package/src/style-accessor.ts +95 -0
  99. package/src/style-expression.ts +327 -0
  100. package/src/style-resolver.ts +301 -0
  101. package/src/text-font.ts +150 -0
  102. package/src/text-halo-blur-extension.ts +115 -0
  103. package/src/text-layout.ts +441 -0
  104. package/src/util.ts +47 -0
  105. package/src/zoom-paint-extension.ts +207 -0
@@ -0,0 +1,533 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {SharedMVTLayer, SharedTileset2D} from '@deck.gl-community/geo-layers';
6
+ import {isGlobeViewport} from './is-globe-viewport';
7
+ import type {BasemapSource} from './style-resolver';
8
+ import {
9
+ CompositeLayer,
10
+ log,
11
+ type DefaultProps,
12
+ type UpdateParameters,
13
+ type Viewport
14
+ } from '@deck.gl/core';
15
+ import {load} from '@loaders.gl/core';
16
+ import {
17
+ createDemTileset,
18
+ getBasemapLayers,
19
+ getSourceLoadOptions,
20
+ getStyleDefinitionZoomLimits,
21
+ getStyleSourceLayers,
22
+ getTilePyramidProps,
23
+ getVectorTileLoader,
24
+ isVectorTileSource,
25
+ VECTOR_TILE_ZOOM_OFFSET
26
+ } from './globe-layers';
27
+ import {
28
+ acquireSharedLabelPlacement,
29
+ DEFAULT_PLACEMENT_INTERVAL,
30
+ getSharedLabelPlacementKey,
31
+ LabelPlacement,
32
+ releaseSharedLabelPlacement
33
+ } from './label-placement';
34
+ import {MapStyleLoader, type MapStyleLoaderOptions} from './map-style-loader';
35
+ import {getDemUnpackVector} from './hillshade-layer';
36
+ import {getStyleZoomKey} from './style-accessor';
37
+ import type {BasemapStyle, ResolvedBasemapStyle} from './style-resolver';
38
+ import {GlyphStore} from './glyph-store';
39
+ import {loadSpriteAtlases} from './sprite';
40
+ import type {SpriteAtlas} from './sprite';
41
+ import type {LabelFontFamily} from './text-font';
42
+
43
+ /**
44
+ * Logs a non-error basemap-layer runtime event to deck.gl logging.
45
+ */
46
+ function logBasemapLayerEvent(message: string, details?: unknown): void {
47
+ log.probe(1, message, details ?? '')();
48
+ }
49
+
50
+ /**
51
+ * Logs a basemap-layer runtime error to deck.gl logging.
52
+ */
53
+ function logBasemapLayerError(message: string, error: Error): void {
54
+ log.error(`${message}: ${error.message}`)();
55
+ }
56
+
57
+ function getMapStyleLoaderOptions(
58
+ loadOptions: BasemapLayerProps['loadOptions']
59
+ ): MapStyleLoaderOptions {
60
+ return {
61
+ mapStyle: (loadOptions || undefined) as NonNullable<MapStyleLoaderOptions['mapStyle']>
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Globe-related runtime toggles used by {@link BasemapLayer}.
67
+ */
68
+ export type BasemapGlobeConfig = {
69
+ /** Renders the atmosphere overlays returned by the globe helpers when available. */
70
+ atmosphere?: boolean;
71
+ /** Renders the main globe basemap content when `true`. */
72
+ basemap?: boolean;
73
+ /** Enables symbol-label rendering on the globe path when `true`. */
74
+ labels?: boolean;
75
+ };
76
+
77
+ /**
78
+ * Props accepted by {@link BasemapLayer}.
79
+ */
80
+ export type BasemapLayerProps = {
81
+ /** Optional deck.gl layer identifier. */
82
+ id?: string;
83
+ /** MapLibre or Mapbox style JSON, or a URL that resolves to one. */
84
+ style: string | BasemapStyle | null;
85
+ /** Load options forwarded to {@link resolveBasemapStyle}. */
86
+ loadOptions?: Record<string, unknown> | null;
87
+ /** Selects whether deck layers are generated for a flat map or a globe. */
88
+ mode?: 'map' | 'globe';
89
+ /** Optional globe-specific configuration used when `mode` is `'globe'`. */
90
+ globe?: {
91
+ /** Rendering toggles for globe-specific basemap behavior. */
92
+ config?: BasemapGlobeConfig;
93
+ };
94
+ /**
95
+ * Draws labels with the browser's fonts instead of the style's `glyphs`. By default labels are
96
+ * drawn with the style's glyphs, as in MapLibre GL JS, and a style without `glyphs` maps each
97
+ * symbol layer's `text-font` to a CSS family list, weight and style (see the module docs). A
98
+ * string replaces the family list for every label; a function receives the `text-font` names
99
+ * and returns a family list or `LabelFont` fields. Pass a stable function: tiles regenerate when
100
+ * it changes.
101
+ */
102
+ fontFamily?: LabelFontFamily | null;
103
+ /**
104
+ * Milliseconds between two placements of the labels while the view keeps changing, as MapLibre
105
+ * GL JS's `fadeDuration` (default 300). The view the map comes to rest in is always placed; 0
106
+ * places every frame.
107
+ */
108
+ labelPlacementInterval?: number;
109
+ /**
110
+ * Places this layer's labels apart from those of the other `BasemapLayer`s of the deck. By
111
+ * default (`false`) the labels of every `BasemapLayer` in a deck are placed together, as the
112
+ * layers of one style: a label hides those it overlaps, and those of a `BasemapLayer` drawn later
113
+ * are placed first. With `true`, this layer's labels collide only with each other.
114
+ */
115
+ separateLabelPlacement?: boolean;
116
+ };
117
+
118
+ const fractionalZoomLimitsCache = new WeakMap<ResolvedBasemapStyle, number[]>();
119
+
120
+ /**
121
+ * The fractional `minzoom`/`maxzoom` limits of a style, computed once per resolved style:
122
+ * `shouldUpdateState` reads them on every viewport change, but they only change with the style.
123
+ */
124
+ function getFractionalZoomLimits(resolvedStyle: ResolvedBasemapStyle): number[] {
125
+ let limits = fractionalZoomLimitsCache.get(resolvedStyle);
126
+ if (!limits) {
127
+ limits = getStyleDefinitionZoomLimits(resolvedStyle).filter(
128
+ (limit): limit is number => limit !== undefined && !Number.isInteger(limit)
129
+ );
130
+ fractionalZoomLimitsCache.set(resolvedStyle, limits);
131
+ }
132
+ return limits;
133
+ }
134
+
135
+ /**
136
+ * Internal state tracked by {@link BasemapLayer}.
137
+ */
138
+ type BasemapLayerState = {
139
+ /**
140
+ * Places the labels of every symbol style layer together (see `LabelPlacement`): the deck's
141
+ * shared placement, or this layer's own with `separateLabelPlacement`.
142
+ */
143
+ labelPlacement: LabelPlacement | null;
144
+ /** The deck whose shared placement `labelPlacement` is, or null for a placement of its own. */
145
+ sharedLabelPlacementKey: object | null;
146
+ /** The fully resolved style definition currently backing the generated sublayers. */
147
+ resolvedStyle: ResolvedBasemapStyle | null;
148
+ /** The most recent style-resolution error, if one occurred. */
149
+ loadError: Error | null;
150
+ /** Monotonic token used to discard stale async style loads. */
151
+ loadToken: number;
152
+ /** `getStyleZoomKey` of the zoom the current sublayers were generated at. */
153
+ zoomKey: string | null;
154
+ /** Sprites of the resolved style, once loaded. */
155
+ spriteAtlases: SpriteAtlas[] | null;
156
+ /** The glyphs of the resolved style's `glyphs` URL, or null without one. */
157
+ glyphs: GlyphStore | null;
158
+ /** Decoded data caches owned by this basemap, shared across style runs. */
159
+ tilesets: Map<BasemapSource, SharedTileset2D>;
160
+ /**
161
+ * What each tileset decodes: for a vector source its source layers, as JSON (see
162
+ * `getStyleSourceLayers`), for a raster-dem source its encoding.
163
+ */
164
+ tilesetSourceLayers: Map<BasemapSource, string>;
165
+ /** Loading configuration used to construct the current source caches. */
166
+ tileLoadOptions: unknown;
167
+ /** Fetch identity, kept separate from render-only props such as view mode. */
168
+ tileFetch: unknown;
169
+ };
170
+
171
+ /**
172
+ * A deck.gl {@link CompositeLayer} that loads a style document and renders the
173
+ * corresponding basemap sublayers.
174
+ */
175
+ export class BasemapLayer extends CompositeLayer<Required<BasemapLayerProps>> {
176
+ /** Deck.gl layer name. */
177
+ static layerName = 'BasemapLayer';
178
+
179
+ /** Default props for {@link BasemapLayer}. */
180
+ static defaultProps: DefaultProps<BasemapLayerProps> = {
181
+ mode: 'map',
182
+ globe: {
183
+ type: 'object',
184
+ value: {
185
+ config: {
186
+ atmosphere: false,
187
+ basemap: true,
188
+ labels: true
189
+ }
190
+ }
191
+ },
192
+ style: null,
193
+ loadOptions: null,
194
+ fontFamily: null,
195
+ labelPlacementInterval: DEFAULT_PLACEMENT_INTERVAL,
196
+ separateLabelPlacement: false
197
+ };
198
+
199
+ /** Current layer state. */
200
+ state: BasemapLayerState = undefined!;
201
+
202
+ /** Initializes the asynchronous style-loading state. */
203
+ initializeState(): void {
204
+ this.state = {
205
+ labelPlacement: null,
206
+ sharedLabelPlacementKey: null,
207
+ resolvedStyle: null,
208
+ loadError: null,
209
+ loadToken: 0,
210
+ zoomKey: null,
211
+ tilesets: new Map(),
212
+ tilesetSourceLayers: new Map(),
213
+ tileLoadOptions: null,
214
+ tileFetch: null,
215
+ spriteAtlases: null,
216
+ glyphs: null
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Sublayers depend on the zoom, but only through `getStyleZoomKey`: style values step every
222
+ * `STYLE_ZOOM_STEP`, filters at integer zooms, and layer visibility changes at
223
+ * `minzoom`/`maxzoom`. Re-render when that key changes, not on every viewport change.
224
+ */
225
+ shouldUpdateState(params: UpdateParameters<this>): boolean {
226
+ if (super.shouldUpdateState(params)) {
227
+ return true;
228
+ }
229
+ const {resolvedStyle, zoomKey} = this.state;
230
+ if (!resolvedStyle || !params.changeFlags.viewportChanged) {
231
+ return false;
232
+ }
233
+ return this.getZoomKey(resolvedStyle, params.context.viewport) !== zoomKey;
234
+ }
235
+
236
+ /** Returns the key of what the sublayers depend on in `viewport`, for a resolved style. */
237
+ getZoomKey(resolvedStyle: ResolvedBasemapStyle, viewport: Viewport | undefined): string {
238
+ const zoomKey = getStyleZoomKey(viewport?.zoom || 0, getFractionalZoomLimits(resolvedStyle));
239
+ return `${this.getDrawMode(viewport)}:${zoomKey}`;
240
+ }
241
+
242
+ /**
243
+ * The mode the sublayers are drawn in. Above zoom 12, `GlobeView` draws with a Web Mercator
244
+ * viewport, so a globe basemap draws as a map there: globe depth and culling parameters assume
245
+ * longitude/latitude geometry on a sphere.
246
+ */
247
+ getDrawMode(viewport: Viewport | undefined): 'map' | 'globe' {
248
+ const {mode = 'map'} = this.props;
249
+ return mode === 'globe' && viewport && !isGlobeViewport(viewport) ? 'map' : mode;
250
+ }
251
+
252
+ /** Reacts to changes in the input style definition. */
253
+ updateState({props, oldProps, changeFlags}: UpdateParameters<this>): void {
254
+ this.updateLabelPlacement();
255
+ if (
256
+ changeFlags.dataChanged ||
257
+ props.style !== oldProps.style ||
258
+ props.loadOptions !== oldProps.loadOptions
259
+ ) {
260
+ this.loadStyle(props.style, props.loadOptions);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Resolves the configured style input and stores the latest successful result
266
+ * in layer state.
267
+ */
268
+ loadStyle(
269
+ style: BasemapLayerProps['style'],
270
+ loadOptions: BasemapLayerProps['loadOptions']
271
+ ): void {
272
+ if (!style) {
273
+ const loadToken = this.state.loadToken + 1;
274
+ logBasemapLayerEvent('Clearing basemap style');
275
+ this.clearSourceTilesets();
276
+ this.setState({
277
+ resolvedStyle: null,
278
+ loadError: null,
279
+ loadToken,
280
+ spriteAtlases: null,
281
+ glyphs: null
282
+ });
283
+ return;
284
+ }
285
+
286
+ const loadToken = this.state.loadToken + 1;
287
+ this.setState({loadToken});
288
+ logBasemapLayerEvent('Resolving basemap style', {
289
+ style: typeof style === 'string' ? style : 'inline-style-object',
290
+ loadToken
291
+ });
292
+
293
+ Promise.resolve(
294
+ typeof style === 'string'
295
+ ? load(style, MapStyleLoader, getMapStyleLoaderOptions(loadOptions))
296
+ : MapStyleLoader.parse(
297
+ new TextEncoder().encode(JSON.stringify(style)).buffer,
298
+ getMapStyleLoaderOptions(loadOptions)
299
+ )
300
+ )
301
+ .then(resolvedStyle => {
302
+ if (this.state.loadToken === loadToken) {
303
+ logBasemapLayerEvent('Resolved basemap style', {
304
+ loadToken,
305
+ sources: Object.keys(resolvedStyle.sources || {}),
306
+ layers: resolvedStyle.layers?.length || 0
307
+ });
308
+ this.setState({
309
+ resolvedStyle,
310
+ loadError: null,
311
+ spriteAtlases: null,
312
+ glyphs: getGlyphStore(resolvedStyle, style, loadOptions)
313
+ });
314
+ this.loadSprites(resolvedStyle, style, loadOptions, loadToken);
315
+ }
316
+ })
317
+ .catch((error: Error) => {
318
+ if (this.state.loadToken === loadToken) {
319
+ logBasemapLayerError('Failed to resolve basemap style', error);
320
+ this.clearSourceTilesets();
321
+ this.setState({resolvedStyle: null, loadError: error});
322
+ }
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Loads the resolved style's sprites, if it has any, and stores them for the icon layers.
328
+ * Relative sprite URLs resolve against the style URL.
329
+ */
330
+ loadSprites(
331
+ resolvedStyle: ResolvedBasemapStyle,
332
+ style: BasemapLayerProps['style'],
333
+ loadOptions: BasemapLayerProps['loadOptions'],
334
+ loadToken: number
335
+ ): void {
336
+ if (!resolvedStyle.sprite) {
337
+ // No sprites to wait for (`null` means loading, see `MVTLabelLayerProps.spriteAtlases`).
338
+ this.setState({spriteAtlases: []});
339
+ return;
340
+ }
341
+ const options = (loadOptions || {}) as {
342
+ baseUrl?: string;
343
+ fetch?: typeof fetch;
344
+ fetchOptions?: RequestInit;
345
+ };
346
+ loadSpriteAtlases(resolvedStyle.sprite, {
347
+ baseUrl: typeof style === 'string' ? style : options.baseUrl,
348
+ fetch: options.fetch,
349
+ fetchOptions: options.fetchOptions,
350
+ pixelRatio: typeof devicePixelRatio === 'number' ? devicePixelRatio : 1
351
+ }).then(spriteAtlases => {
352
+ if (this.state.loadToken === loadToken) {
353
+ this.setState({spriteAtlases});
354
+ }
355
+ });
356
+ }
357
+
358
+ /** The basemap owns caches; style-run consumers only attach/detach. */
359
+ finalizeState(): void {
360
+ this.state.loadToken++;
361
+ this.clearSourceTilesets();
362
+ this.releaseLabelPlacement();
363
+ }
364
+
365
+ /**
366
+ * Takes the deck's shared label placement, or one of its own with `separateLabelPlacement`, and
367
+ * gives back the one it had when that prop changed.
368
+ */
369
+ private updateLabelPlacement(): void {
370
+ const separate = Boolean(this.props.separateLabelPlacement);
371
+ const {labelPlacement, sharedLabelPlacementKey} = this.state;
372
+ if (labelPlacement && separate === !sharedLabelPlacementKey) {
373
+ return;
374
+ }
375
+ this.releaseLabelPlacement();
376
+ if (separate) {
377
+ this.state.labelPlacement = new LabelPlacement();
378
+ } else {
379
+ const key = getSharedLabelPlacementKey(this);
380
+ this.state.sharedLabelPlacementKey = key;
381
+ this.state.labelPlacement = acquireSharedLabelPlacement(key);
382
+ }
383
+ }
384
+
385
+ /** Gives back the deck's shared placement, or releases this layer's own. */
386
+ private releaseLabelPlacement(): void {
387
+ const {labelPlacement, sharedLabelPlacementKey} = this.state;
388
+ if (sharedLabelPlacementKey) {
389
+ releaseSharedLabelPlacement(sharedLabelPlacementKey);
390
+ } else {
391
+ labelPlacement?.destroy();
392
+ }
393
+ this.state.labelPlacement = null;
394
+ this.state.sharedLabelPlacementKey = null;
395
+ }
396
+
397
+ private clearSourceTilesets(): void {
398
+ for (const tileset of this.state.tilesets.values()) tileset.finalize();
399
+ this.state.tilesets.clear();
400
+ this.state.tilesetSourceLayers.clear();
401
+ }
402
+
403
+ private updateSourceTilesets(style: ResolvedBasemapStyle): void {
404
+ const sources = new Set(
405
+ Object.values(style.sources || {}).filter(source => isVectorTileSource(source))
406
+ );
407
+ // The decoded tiles of a raster-dem source are shared by its hillshade layers.
408
+ const demSources = new Set(
409
+ Object.values(style.sources || {}).filter(
410
+ source => source?.type === 'raster-dem' && source.tiles
411
+ )
412
+ );
413
+ const optionsChanged =
414
+ this.state.tileLoadOptions !== this.props.loadOptions ||
415
+ this.state.tileFetch !== this.props.fetch;
416
+ // Tiles decode only the source layers the style draws: a style edit or a labels toggle that
417
+ // changes the layers the loader decodes reloads the source's tiles.
418
+ const sourceLayers = getStyleSourceLayers(style, this.props.globe);
419
+ const getDecodedLayers = (source: BasemapSource) =>
420
+ (getSourceLoadOptions(this.props.loadOptions, sourceLayers.get(source)) as any)?.mvt?.layers;
421
+ const sourceLayerKeys = new Map([
422
+ ...[...sources].map(
423
+ source => [source, JSON.stringify(getDecodedLayers(source) ?? null)] as const
424
+ ),
425
+ // An encoding edited in place reloads the source's tiles.
426
+ ...[...demSources].map(source => [source, `dem:${getDemUnpackVector(source)}`] as const)
427
+ ]);
428
+ for (const [source, tileset] of this.state.tilesets) {
429
+ if (
430
+ !sourceLayerKeys.has(source) ||
431
+ optionsChanged ||
432
+ this.state.tilesetSourceLayers.get(source) !== sourceLayerKeys.get(source)
433
+ ) {
434
+ tileset.finalize();
435
+ this.state.tilesets.delete(source);
436
+ this.state.tilesetSourceLayers.delete(source);
437
+ }
438
+ }
439
+ this.state.tileLoadOptions = this.props.loadOptions;
440
+ this.state.tileFetch = this.props.fetch;
441
+ for (const source of sources) {
442
+ if (!this.state.tilesets.has(source)) {
443
+ const loader = new SharedMVTLayer({
444
+ id: `${this.id}-loader`,
445
+ data: source.tiles!,
446
+ fetch: this.props.fetch,
447
+ loadOptions: getSourceLoadOptions(this.props.loadOptions, sourceLayers.get(source))
448
+ });
449
+ loader.context = this.context;
450
+ const tileset = new SharedTileset2D({
451
+ ...loader._getTilesetOptions(),
452
+ ...getTilePyramidProps(source),
453
+ tileSize: source.tileSize || 512,
454
+ zoomOffset: VECTOR_TILE_ZOOM_OFFSET,
455
+ getTileData: getVectorTileLoader(source, loader)
456
+ });
457
+ this.state.tilesets.set(source, tileset);
458
+ this.state.tilesetSourceLayers.set(source, sourceLayerKeys.get(source)!);
459
+ }
460
+ }
461
+ for (const source of demSources) {
462
+ if (!this.state.tilesets.has(source)) {
463
+ const tileset = createDemTileset(source, this.props.loadOptions);
464
+ this.state.tilesets.set(source, tileset);
465
+ this.state.tilesetSourceLayers.set(source, sourceLayerKeys.get(source)!);
466
+ }
467
+ }
468
+ }
469
+
470
+ /** The label placement, with the interval of the current props. */
471
+ private getLabelPlacement(): LabelPlacement {
472
+ this.updateLabelPlacement();
473
+ const labelPlacement = this.state.labelPlacement!;
474
+ // A shared placement takes the interval of the last of its layers rendered.
475
+ labelPlacement.interval = Math.max(0, Number(this.props.labelPlacementInterval) || 0);
476
+ return labelPlacement;
477
+ }
478
+
479
+ /** Renders the sublayers generated from the resolved style definition. */
480
+ renderLayers() {
481
+ const {resolvedStyle} = this.state;
482
+ if (!resolvedStyle) {
483
+ return [];
484
+ }
485
+
486
+ this.updateSourceTilesets(resolvedStyle);
487
+ const {viewport} = this.context;
488
+ const zoom = viewport?.zoom || 0;
489
+ // Recorded without setState: this only tracks what the current sublayers were built from.
490
+ this.state.zoomKey = this.getZoomKey(resolvedStyle, viewport);
491
+
492
+ return getBasemapLayers({
493
+ idPrefix: this.props.id,
494
+ mode: this.getDrawMode(viewport),
495
+ globe: this.props.globe,
496
+ styleDefinition: resolvedStyle,
497
+ zoom,
498
+ loadOptions: this.props.loadOptions,
499
+ spriteAtlases: this.state.spriteAtlases,
500
+ fontFamily: this.props.fontFamily,
501
+ // A label font of the caller's is drawn with the browser's fonts.
502
+ glyphs: this.props.fontFamily ? null : this.state.glyphs,
503
+ labelPlacement: this.getLabelPlacement(),
504
+ tilesets: this.state.tilesets
505
+ });
506
+ }
507
+ }
508
+
509
+ /**
510
+ * The glyph store of a style's `glyphs` URL, or null without one. A relative URL resolves against
511
+ * the style URL, as sprite URLs do.
512
+ */
513
+ function getGlyphStore(
514
+ resolvedStyle: ResolvedBasemapStyle,
515
+ style: BasemapLayerProps['style'],
516
+ loadOptions: BasemapLayerProps['loadOptions']
517
+ ): GlyphStore | null {
518
+ const url = resolvedStyle.glyphs;
519
+ if (typeof url !== 'string' || !url) {
520
+ return null;
521
+ }
522
+ const options = (loadOptions || {}) as {
523
+ baseUrl?: string;
524
+ fetch?: typeof fetch;
525
+ fetchOptions?: RequestInit;
526
+ };
527
+ return new GlyphStore({
528
+ url,
529
+ baseUrl: typeof style === 'string' ? style : options.baseUrl,
530
+ fetch: options.fetch,
531
+ fetchOptions: options.fetchOptions
532
+ });
533
+ }