@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,327 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ // The only module that imports the style-spec package, so the expression engine can be swapped
6
+ // in one place.
7
+ import {log} from '@deck.gl/core';
8
+ import {
9
+ Color,
10
+ ColorArray,
11
+ NumberArray,
12
+ Padding,
13
+ VariableAnchorOffsetCollection,
14
+ expression,
15
+ featureFilter,
16
+ latest as Reference
17
+ } from '@maplibre/maplibre-gl-style-spec';
18
+
19
+ /** A feature as seen by style expressions: a GeoJSON feature or a numeric-typed tile feature. */
20
+ export type StyleFeature = {
21
+ type?: number | string;
22
+ id?: unknown;
23
+ geometry?: {type: string};
24
+ properties?: Record<string, unknown>;
25
+ };
26
+
27
+ /** A style property compiled once and evaluated per zoom and feature. */
28
+ export type CompiledStyleProperty = {
29
+ /** True when the value depends on `["zoom"]`. */
30
+ isZoomDependent: boolean;
31
+ /** True when the value depends on feature data (`["get", ...]`, `match`, ...). */
32
+ isFeatureDependent: boolean;
33
+ /**
34
+ * Evaluates the property. Colors are returned as `[r, g, b, a]`, with `a` in 0-1.
35
+ * `availableImages` lists the loaded sprite image names, which `["image", ...]` checks.
36
+ */
37
+ evaluate: (zoom: number, feature?: StyleFeature, availableImages?: string[]) => unknown;
38
+ /**
39
+ * Evaluates the property with every global input, such as `heatmapDensity` or `lineProgress`
40
+ * as well as `zoom`. Colors are returned as in `evaluate`.
41
+ */
42
+ evaluateWithGlobals: (globals: Record<string, unknown>, feature?: StyleFeature) => unknown;
43
+ };
44
+
45
+ type StyleLayerLike = {
46
+ paint?: Record<string, unknown>;
47
+ layout?: Record<string, unknown>;
48
+ };
49
+
50
+ type PropertyReference = Record<string, unknown> | null;
51
+
52
+ const GEOMETRY_TYPES: Record<string, number> = {
53
+ Point: 1,
54
+ MultiPoint: 1,
55
+ LineString: 2,
56
+ MultiLineString: 2,
57
+ Polygon: 3,
58
+ MultiPolygon: 3
59
+ };
60
+
61
+ type CompiledPropertyEntry = {
62
+ propertyType: 'paint' | 'layout';
63
+ value: unknown;
64
+ compiled: CompiledStyleProperty | null;
65
+ };
66
+
67
+ const compiledPropertyCache = new WeakMap<object, Map<string, CompiledPropertyEntry>>();
68
+ /** Compiled filters per filter array, with the array's contents when it was compiled. */
69
+ const compiledFilterCache = new WeakMap<object, {snapshot: string; compiled: StyleFilter}>();
70
+ const warnedValues = new Set<string>();
71
+
72
+ type StyleFilter = (globalProperties: Record<string, unknown>, feature: StyleFeature) => boolean;
73
+
74
+ /** Logs an invalid style value once per property and value. */
75
+ function warnInvalidValue(propertyName: string, value: unknown, error: unknown): void {
76
+ const key = `${propertyName}:${JSON.stringify(value)}`;
77
+ if (!warnedValues.has(key)) {
78
+ warnedValues.add(key);
79
+ log.warn(`Ignoring invalid style value for ${propertyName}: ${String(error)}`)();
80
+ }
81
+ }
82
+
83
+ /** Returns whether a style property is a `layout` or a `paint` property. */
84
+ export function getStylePropertyType(propertyName: string): 'layout' | 'paint' {
85
+ return Reference.layout.some((group: string) => Reference[group]?.[propertyName]) ? 'layout' : 'paint';
86
+ }
87
+
88
+ /** Resolves the style-spec reference metadata for a layout or paint property name. */
89
+ export function getStylePropertyReference(propertyName: string): PropertyReference {
90
+ for (const group of [...Reference.layout, ...Reference.paint]) {
91
+ const reference = Reference[group]?.[propertyName];
92
+ if (reference) {
93
+ return reference as PropertyReference;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /** Returns the style-spec geometry type code (1 point, 2 line, 3 polygon) of a feature. */
100
+ export function getStyleGeometryType(feature: StyleFeature): number | undefined {
101
+ if (feature.type === 1 || feature.type === 2 || feature.type === 3) {
102
+ return feature.type;
103
+ }
104
+ return GEOMETRY_TYPES[feature.geometry?.type || ''];
105
+ }
106
+
107
+ function toEvaluationFeature(feature?: StyleFeature) {
108
+ if (!feature) {
109
+ return undefined;
110
+ }
111
+ return {
112
+ type: getStyleGeometryType(feature),
113
+ id: feature.id,
114
+ properties: feature.properties || {}
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Converts a style-spec color to `[r, g, b, a]` with RGB in 0-255 and alpha in 0-1, the shape
120
+ * `@mapbox/mapbox-gl-style-spec`'s `Color#toArray()` returned. MapLibre's `Color` stores
121
+ * premultiplied components; its `rgb` getter returns them un-premultiplied in 0-1.
122
+ */
123
+ export function colorToArray(color: Color): [number, number, number, number] {
124
+ const [r, g, b, a] = color.rgb;
125
+ return a === 0 ? [0, 0, 0, 0] : [r * 255, g * 255, b * 255, a];
126
+ }
127
+
128
+ /**
129
+ * Returns the style specification's default for a property, in the shape `evaluate` returns:
130
+ * colors as `[r, g, b, a]`, paddings as `[top, right, bottom, left]`. Undefined for a property
131
+ * without a default.
132
+ */
133
+ export function getStylePropertyDefault(propertyName: string): unknown {
134
+ const reference = getStylePropertyReference(propertyName);
135
+ const value = reference?.default;
136
+ if (reference?.type === 'color' && typeof value === 'string') {
137
+ const color = Color.parse(value);
138
+ return color ? colorToArray(color) : undefined;
139
+ }
140
+ if (reference?.type === 'padding') {
141
+ return Padding.parse(value as number | number[])?.values;
142
+ }
143
+ if (reference?.type === 'numberArray') {
144
+ return NumberArray.parse(value as number | number[])?.values;
145
+ }
146
+ if (reference?.type === 'colorArray') {
147
+ return ColorArray.parse(value as string | string[])?.values.map(colorToArray);
148
+ }
149
+ return value;
150
+ }
151
+
152
+ /**
153
+ * Converts an evaluated value to plain data: colors as `[r, g, b, a]`, paddings and number arrays
154
+ * as arrays of numbers, color arrays as arrays of `[r, g, b, a]`.
155
+ */
156
+ function toPlainValue(value: any): unknown {
157
+ if (value instanceof Padding || value instanceof NumberArray) {
158
+ return value.values;
159
+ }
160
+ if (value instanceof ColorArray) {
161
+ return value.values.map(colorToArray);
162
+ }
163
+ if (value instanceof VariableAnchorOffsetCollection) {
164
+ // `[anchor, [x, y], anchor, [x, y], ...]`.
165
+ return value.values;
166
+ }
167
+ return value instanceof Color ? colorToArray(value) : value;
168
+ }
169
+
170
+ /**
171
+ * Compiles a single property value against its style-spec reference. Returns `null` and logs a
172
+ * warning once for a value the style spec rejects, so the property is treated as unset, as
173
+ * MapLibre skips invalid properties.
174
+ */
175
+ export function compileStylePropertyValue(
176
+ propertyName: string,
177
+ value: unknown
178
+ ): CompiledStyleProperty | null {
179
+ const reference = getStylePropertyReference(propertyName);
180
+ let compiled: any;
181
+ try {
182
+ // An array whose first element is not a known operator is read as a literal. That is only
183
+ // valid for array-typed properties (`text-font`, `line-dasharray`, ...), paddings
184
+ // (`icon-padding: [2, 4]`), number and color arrays and `text-variable-anchor-offset`.
185
+ const takesArray =
186
+ reference?.type === 'array' ||
187
+ reference?.type === 'padding' ||
188
+ reference?.type === 'numberArray' ||
189
+ reference?.type === 'colorArray' ||
190
+ reference?.type === 'variableAnchorOffsetCollection';
191
+ if (Array.isArray(value) && !takesArray && !expression.isExpression(value)) {
192
+ throw new Error(`Unknown expression "${String(value[0])}"`);
193
+ }
194
+ compiled = expression.normalizePropertyExpression(value as any, reference as any);
195
+ } catch (error) {
196
+ warnInvalidValue(propertyName, value, error);
197
+ return null;
198
+ }
199
+ const kind: string = compiled.kind;
200
+
201
+ return {
202
+ isZoomDependent: kind === 'camera' || kind === 'composite',
203
+ isFeatureDependent: kind === 'source' || kind === 'composite',
204
+ evaluate: (zoom, feature, availableImages) =>
205
+ toPlainValue(
206
+ compiled.evaluate(
207
+ {zoom},
208
+ toEvaluationFeature(feature) as any,
209
+ undefined,
210
+ undefined,
211
+ availableImages
212
+ )
213
+ ),
214
+ evaluateWithGlobals: (globals, feature) =>
215
+ toPlainValue(compiled.evaluate(globals as any, toEvaluationFeature(feature) as any))
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Returns a compiled `paint` or `layout` property of a style layer, or `null` when the layer
221
+ * does not set it. Compilation is cached per style layer object and property; the cached entry is
222
+ * reused only while the layer still holds the same value, so editing a property in place
223
+ * recompiles it.
224
+ */
225
+ export function getCompiledStyleProperty(
226
+ styleLayer: StyleLayerLike,
227
+ propertyName: string
228
+ ): CompiledStyleProperty | null {
229
+ let layerCache = compiledPropertyCache.get(styleLayer);
230
+ if (!layerCache) {
231
+ layerCache = new Map();
232
+ compiledPropertyCache.set(styleLayer, layerCache);
233
+ }
234
+
235
+ let entry = layerCache.get(propertyName);
236
+ const propertyType = entry?.propertyType ?? getStylePropertyType(propertyName);
237
+ const value = styleLayer[propertyType]?.[propertyName];
238
+ if (!entry || entry.value !== value) {
239
+ entry = {
240
+ propertyType,
241
+ value,
242
+ compiled: value === undefined ? null : compileStylePropertyValue(propertyName, value)
243
+ };
244
+ layerCache.set(propertyName, entry);
245
+ }
246
+ return entry.compiled;
247
+ }
248
+
249
+ /**
250
+ * Compiles a style-spec filter into a feature predicate, cached per filter array. The cached
251
+ * predicate is reused only while the array's contents are unchanged, so a filter edited in place
252
+ * is recompiled. A filter the style spec rejects logs a warning once and matches no features.
253
+ */
254
+ export function compileStyleFilter(filter: unknown): StyleFilter {
255
+ const isCacheable = Boolean(filter) && typeof filter === 'object';
256
+ const snapshot = isCacheable ? JSON.stringify(filter) : '';
257
+ const cached = isCacheable ? compiledFilterCache.get(filter as object) : undefined;
258
+ if (cached && cached.snapshot === snapshot) {
259
+ return cached.compiled;
260
+ }
261
+
262
+ let compiled: StyleFilter;
263
+ try {
264
+ const filterFn = featureFilter(filter as any).filter;
265
+ compiled = (globalProperties, feature) =>
266
+ filterFn(globalProperties as any, toEvaluationFeature(feature) as any);
267
+ } catch (error) {
268
+ warnInvalidValue('filter', filter, error);
269
+ compiled = () => false;
270
+ }
271
+
272
+ if (isCacheable) {
273
+ compiledFilterCache.set(filter as object, {snapshot, compiled});
274
+ }
275
+ return compiled;
276
+ }
277
+
278
+ /** A style's `light`, evaluated at one zoom. Colors are `[r, g, b, a]`, RGB in 0-255. */
279
+ export type EvaluatedStyleLight = {
280
+ anchor: unknown;
281
+ position: unknown;
282
+ color: unknown;
283
+ intensity: unknown;
284
+ };
285
+
286
+ const LIGHT_PROPERTIES = ['anchor', 'position', 'color', 'intensity'] as const;
287
+
288
+ /** The compiled properties per `light` object, `null` where it does not set a valid value. */
289
+ const compiledLightCache = new WeakMap<
290
+ object,
291
+ Map<string, {evaluate: (globals: any) => any} | null>
292
+ >();
293
+
294
+ /**
295
+ * Evaluates a style's `light` at a zoom, property by property. A property the style does not set,
296
+ * or sets to a value the style spec rejects (logged once), takes the spec's default.
297
+ */
298
+ export function evaluateStyleLight(light: unknown, zoom: number): EvaluatedStyleLight {
299
+ const values = (light && typeof light === 'object' ? light : {}) as Record<string, unknown>;
300
+ let compiled = compiledLightCache.get(values);
301
+ if (!compiled) {
302
+ compiled = new Map();
303
+ for (const name of LIGHT_PROPERTIES) {
304
+ let property: {evaluate: (globals: any) => any} | null = null;
305
+ if (values[name] !== undefined) {
306
+ try {
307
+ property = expression.normalizePropertyExpression(
308
+ values[name] as any,
309
+ (Reference as any).light[name]
310
+ ) as any;
311
+ } catch (error) {
312
+ warnInvalidValue(`light.${name}`, values[name], error);
313
+ }
314
+ }
315
+ compiled.set(name, property);
316
+ }
317
+ compiledLightCache.set(values, compiled);
318
+ }
319
+ const evaluated = {} as EvaluatedStyleLight;
320
+ for (const name of LIGHT_PROPERTIES) {
321
+ const property = compiled.get(name);
322
+ const value = property ? property.evaluate({zoom}) : (Reference as any).light[name].default;
323
+ evaluated[name] = typeof value === 'string' && name === 'color' ? Color.parse(value) : value;
324
+ evaluated[name] = toPlainValue(evaluated[name]);
325
+ }
326
+ return evaluated;
327
+ }
@@ -0,0 +1,301 @@
1
+ // tangram-layers
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {log} from '@deck.gl/core';
6
+ import {derefLayers} from '@maplibre/maplibre-gl-style-spec';
7
+ import {
8
+ BasemapSourceSchema,
9
+ BasemapStyleSchema,
10
+ ResolvedBasemapStyleSchema
11
+ } from './map-style-schema';
12
+
13
+ /**
14
+ * A basemap source entry from a style document.
15
+ */
16
+ export type BasemapSource = {
17
+ /** Source kind such as `vector` or `raster`. */
18
+ type?: string;
19
+ /** Optional TileJSON URL used to resolve the source metadata. */
20
+ url?: string;
21
+ /** Inline tile templates for the source. */
22
+ tiles?: string[];
23
+ /** Minimum source zoom, when supplied by the style or TileJSON. */
24
+ minzoom?: number;
25
+ /** Maximum source zoom, when supplied by the style or TileJSON. */
26
+ maxzoom?: number;
27
+ /** Tile size in pixels. */
28
+ tileSize?: number;
29
+ /** Additional source properties are preserved verbatim. */
30
+ [key: string]: unknown;
31
+ };
32
+
33
+ /**
34
+ * A style layer entry used by the basemap runtime.
35
+ */
36
+ export type BasemapStyleLayer = {
37
+ /** Unique layer identifier. */
38
+ id: string;
39
+ /** Optional concrete style layer type such as `background`, `fill`, `line`, or `raster`. */
40
+ type?: string;
41
+ /** Legacy style layer id whose structural render properties are inherited. */
42
+ ref?: string;
43
+ /** Referenced source identifier. */
44
+ source?: string;
45
+ /** Referenced vector source-layer identifier. */
46
+ 'source-layer'?: string;
47
+ /** Optional minimum zoom. */
48
+ minzoom?: number;
49
+ /** Optional maximum zoom. */
50
+ maxzoom?: number;
51
+ /** Optional style-spec filter expression. */
52
+ filter?: unknown[];
53
+ /** Paint properties from the source style layer. */
54
+ paint?: Record<string, unknown>;
55
+ /** Layout properties from the source style layer. */
56
+ layout?: Record<string, unknown>;
57
+ /** Additional layer properties are preserved verbatim. */
58
+ [key: string]: unknown;
59
+ };
60
+
61
+ /** A style layer after legacy `ref` inheritance has been resolved. */
62
+ export type ResolvedBasemapStyleLayer = BasemapStyleLayer & {
63
+ /** Concrete style layer type after optional `ref` inheritance. */
64
+ type: string;
65
+ };
66
+
67
+ /**
68
+ * A MapLibre or Mapbox style document consumed by the basemap runtime.
69
+ */
70
+ export type BasemapStyle = {
71
+ /** Style-spec version number. */
72
+ version?: number;
73
+ /** Optional style metadata bag. */
74
+ metadata?: Record<string, unknown>;
75
+ /** Named source definitions used by the style. */
76
+ sources?: Record<string, BasemapSource>;
77
+ /** Ordered list of style layers. */
78
+ layers?: BasemapStyleLayer[];
79
+ /** Additional style properties are preserved verbatim. */
80
+ [key: string]: unknown;
81
+ };
82
+
83
+ /**
84
+ * A style document after all sources have been normalized and TileJSON-backed
85
+ * sources have been resolved.
86
+ */
87
+ export type ResolvedBasemapStyle = Omit<BasemapStyle, 'sources' | 'layers'> & {
88
+ /** Fully resolved source definitions. */
89
+ sources: Record<string, BasemapSource>;
90
+ /** Style layers copied into a mutable array. */
91
+ layers: ResolvedBasemapStyleLayer[];
92
+ };
93
+
94
+ /**
95
+ * Load options accepted by {@link resolveBasemapStyle}.
96
+ */
97
+ export type BasemapLoadOptions = {
98
+ /** Base URL used to resolve relative source or tile URLs for in-memory styles. */
99
+ baseUrl?: string;
100
+ /** Optional custom fetch implementation. */
101
+ fetch?: typeof fetch;
102
+ /** Optional init object passed to the selected fetch implementation. */
103
+ fetchOptions?: RequestInit;
104
+ /** Additional loader options are accepted for forward compatibility. */
105
+ [key: string]: unknown;
106
+ } | null;
107
+
108
+ /** Resolves a possibly relative URL against the provided base URL. */
109
+ function normalizeUrl(url: string | undefined, baseUrl?: string) {
110
+ if (!url) {
111
+ return url;
112
+ }
113
+
114
+ try {
115
+ return decodeURI(new URL(url, baseUrl).toString());
116
+ } catch {
117
+ return url;
118
+ }
119
+ }
120
+
121
+ /** Resolves all tile templates in a source against the source base URL. */
122
+ function normalizeTiles(tiles: string[] | undefined, baseUrl?: string) {
123
+ return Array.isArray(tiles) ? tiles.map(tile => normalizeUrl(tile, baseUrl) || tile) : tiles;
124
+ }
125
+
126
+ /** Fetches and parses a JSON resource. */
127
+ async function fetchJson(url: string, loadOptions?: BasemapLoadOptions) {
128
+ const fetchFn = loadOptions?.fetch || fetch;
129
+ const response = await fetchFn(url, loadOptions?.fetchOptions);
130
+
131
+ if (!response.ok) {
132
+ throw new Error(`Failed to load basemap resource: ${url} (${response.status})`);
133
+ }
134
+
135
+ return await response.json();
136
+ }
137
+
138
+ /**
139
+ * Source types whose `url` is fetched as TileJSON. Only the types this module renders are fetched:
140
+ * for `image` and `video` the `url` is the media itself.
141
+ */
142
+ const TILEJSON_SOURCE_TYPES = new Set(['vector', 'raster', 'raster-dem']);
143
+
144
+ /**
145
+ * TileJSON fields that may fill in a source, as MapLibre picks them. Every other TileJSON field,
146
+ * including `type` and `url`, is ignored.
147
+ */
148
+ const TILEJSON_SOURCE_KEYS = [
149
+ 'tiles',
150
+ 'minzoom',
151
+ 'maxzoom',
152
+ 'attribution',
153
+ 'bounds',
154
+ 'scheme',
155
+ 'tileSize',
156
+ 'encoding'
157
+ ] as const;
158
+
159
+ /** Copies the TileJSON fields a source may take into a fresh object. */
160
+ function pickTileJsonFields(tileJson: unknown): Partial<BasemapSource> {
161
+ const fields: Record<string, unknown> = {};
162
+ if (!tileJson || typeof tileJson !== 'object' || Array.isArray(tileJson)) {
163
+ return fields;
164
+ }
165
+ for (const key of TILEJSON_SOURCE_KEYS) {
166
+ if (Object.prototype.hasOwnProperty.call(tileJson, key)) {
167
+ fields[key] = (tileJson as Record<string, unknown>)[key];
168
+ }
169
+ }
170
+ return fields as Partial<BasemapSource>;
171
+ }
172
+
173
+ /**
174
+ * Resolves a GeoJSON source whose `data` is a URL to one whose `data` is the fetched GeoJSON, as
175
+ * MapLibre loads it. Relative URLs resolve against the style's base URL. Throws if the data cannot
176
+ * be fetched or is not a GeoJSON object.
177
+ */
178
+ async function resolveGeoJsonSource(
179
+ source: BasemapSource,
180
+ baseUrl: string | undefined,
181
+ loadOptions?: BasemapLoadOptions
182
+ ): Promise<BasemapSource> {
183
+ if (typeof source.data !== 'string') {
184
+ return {...source};
185
+ }
186
+ const dataUrl = normalizeUrl(source.data, baseUrl) || source.data;
187
+ const data = await fetchJson(dataUrl, loadOptions);
188
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
189
+ throw new Error(`Invalid GeoJSON: ${dataUrl}`);
190
+ }
191
+ return {...source, data};
192
+ }
193
+
194
+ /**
195
+ * Resolves a single source, including optional TileJSON indirection. As in MapLibre, the style's
196
+ * own source properties take precedence over the TileJSON's. Inline `tiles` resolve against the
197
+ * style's base URL and TileJSON `tiles` against the TileJSON URL. Throws if the TileJSON cannot be
198
+ * fetched or the resolved source is invalid.
199
+ */
200
+ async function resolveSource(
201
+ source: BasemapSource | undefined,
202
+ baseUrl: string | undefined,
203
+ loadOptions?: BasemapLoadOptions
204
+ ) {
205
+ if (!source) {
206
+ return source;
207
+ }
208
+
209
+ if (source.type === 'geojson') {
210
+ return await resolveGeoJsonSource(source, baseUrl, loadOptions);
211
+ }
212
+
213
+ if (!source.url || !TILEJSON_SOURCE_TYPES.has(String(source.type))) {
214
+ return source.tiles ? {...source, tiles: normalizeTiles(source.tiles, baseUrl)} : {...source};
215
+ }
216
+
217
+ const tileJsonUrl = normalizeUrl(source.url, baseUrl);
218
+ const tileJsonFields = pickTileJsonFields(
219
+ await fetchJson(tileJsonUrl || source.url, loadOptions)
220
+ );
221
+ const resolvedSource: BasemapSource = {
222
+ ...tileJsonFields,
223
+ ...source,
224
+ url: tileJsonUrl,
225
+ tiles: source.tiles
226
+ ? normalizeTiles(source.tiles, baseUrl)
227
+ : normalizeTiles(tileJsonFields.tiles as string[] | undefined, tileJsonUrl)
228
+ };
229
+
230
+ const result = BasemapSourceSchema.safeParse(resolvedSource);
231
+ if (!result.success) {
232
+ throw new Error(`Invalid TileJSON: ${tileJsonUrl} (${result.error.issues[0]?.message})`);
233
+ }
234
+ if (!resolvedSource.tiles?.length) {
235
+ throw new Error(`TileJSON has no tiles: ${tileJsonUrl}`);
236
+ }
237
+ return resolvedSource;
238
+ }
239
+
240
+ /**
241
+ * Whether a source failure means the whole load was cancelled. The caller's signal decides; an
242
+ * `AbortError` is also treated as a cancellation. A timeout raised inside a custom `fetch`
243
+ * (`TimeoutError`) is an ordinary source failure, unless it aborted the caller's own signal.
244
+ */
245
+ function isLoadCancelled(error: unknown, loadOptions?: BasemapLoadOptions) {
246
+ return (
247
+ Boolean(loadOptions?.fetchOptions?.signal?.aborted) || (error as Error)?.name === 'AbortError'
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Resolves a basemap style input into a style object whose sources contain
253
+ * directly consumable tile templates and source metadata.
254
+ */
255
+ export async function resolveBasemapStyle(
256
+ style: string | BasemapStyle,
257
+ loadOptions?: BasemapLoadOptions
258
+ ): Promise<ResolvedBasemapStyle> {
259
+ const styleDefinition = BasemapStyleSchema.parse(
260
+ typeof style === 'string' ? await fetchJson(style, loadOptions) : structuredClone(style)
261
+ );
262
+ const baseUrl = typeof style === 'string' ? style : loadOptions?.baseUrl;
263
+ const resolvedSources: Record<string, BasemapSource> = {};
264
+
265
+ await Promise.all(
266
+ Object.entries(styleDefinition.sources || {}).map(async ([sourceId, source]) => {
267
+ try {
268
+ resolvedSources[sourceId] = (await resolveSource(source, baseUrl, loadOptions)) || {};
269
+ } catch (error) {
270
+ if (isLoadCancelled(error, loadOptions)) {
271
+ throw loadOptions?.fetchOptions?.signal?.aborted
272
+ ? (loadOptions.fetchOptions.signal.reason ?? error)
273
+ : error;
274
+ }
275
+ // One source that cannot be loaded must not fail the whole style. A source that also
276
+ // lists its own `tiles` keeps them; otherwise it keeps no tile templates, and the layers
277
+ // that use it are skipped. A GeoJSON source whose data cannot be loaded keeps no data.
278
+ const inlineTiles = source?.tiles?.length
279
+ ? normalizeTiles(source.tiles, baseUrl)
280
+ : undefined;
281
+ log.warn(
282
+ `[BasemapLayer] Source "${sourceId}" could not be loaded; ${
283
+ inlineTiles ? 'using its inline tiles' : 'its layers are skipped'
284
+ }: ${String((error as Error)?.message ?? error)}`
285
+ )();
286
+ resolvedSources[sourceId] =
287
+ source?.type === 'geojson'
288
+ ? {...source, data: undefined}
289
+ : {...source, url: undefined, tiles: inlineTiles};
290
+ }
291
+ })
292
+ );
293
+
294
+ return ResolvedBasemapStyleSchema.parse({
295
+ ...styleDefinition,
296
+ sources: resolvedSources,
297
+ layers: derefLayers([...(styleDefinition.layers || [])] as Parameters<
298
+ typeof derefLayers
299
+ >[0]) as ResolvedBasemapStyleLayer[]
300
+ });
301
+ }