@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.
- package/LICENSE +19 -0
- package/README.md +14 -0
- package/dist/atmosphere-layer.d.ts +11 -0
- package/dist/atmosphere-layer.d.ts.map +1 -0
- package/dist/atmosphere-layer.js +15 -0
- package/dist/atmosphere-layer.js.map +1 -0
- package/dist/basemap-layer.d.ts +67 -0
- package/dist/basemap-layer.d.ts.map +1 -0
- package/dist/basemap-layer.js +115 -0
- package/dist/basemap-layer.js.map +1 -0
- package/dist/globe-layers.d.ts +22 -0
- package/dist/globe-layers.d.ts.map +1 -0
- package/dist/globe-layers.js +451 -0
- package/dist/globe-layers.js.map +1 -0
- package/dist/index.cjs +947 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/map-style-loader.d.ts +49 -0
- package/dist/map-style-loader.d.ts.map +1 -0
- package/dist/map-style-loader.js +35 -0
- package/dist/map-style-loader.js.map +1 -0
- package/dist/map-style-schema.d.ts +71 -0
- package/dist/map-style-schema.d.ts.map +1 -0
- package/dist/map-style-schema.js +45 -0
- package/dist/map-style-schema.js.map +1 -0
- package/dist/map-style.cjs +250 -0
- package/dist/map-style.cjs.map +7 -0
- package/dist/map-style.d.ts +7 -0
- package/dist/map-style.d.ts.map +1 -0
- package/dist/map-style.js +6 -0
- package/dist/map-style.js.map +1 -0
- package/dist/mapbox-style.d.ts +41 -0
- package/dist/mapbox-style.d.ts.map +1 -0
- package/dist/mapbox-style.js +113 -0
- package/dist/mapbox-style.js.map +1 -0
- package/dist/mvt-label-layer.d.ts +8142 -0
- package/dist/mvt-label-layer.d.ts.map +1 -0
- package/dist/mvt-label-layer.js +175 -0
- package/dist/mvt-label-layer.js.map +1 -0
- package/dist/style-resolver.d.ts +88 -0
- package/dist/style-resolver.d.ts.map +1 -0
- package/dist/style-resolver.js +63 -0
- package/dist/style-resolver.js.map +1 -0
- package/dist/util.d.ts +21 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +18 -0
- package/dist/util.js.map +1 -0
- package/package.json +60 -0
- package/src/atmosphere-layer.ts +15 -0
- package/src/basemap-layer.ts +183 -0
- package/src/globe-layers.ts +780 -0
- package/src/index.ts +3 -0
- package/src/map-style-loader.ts +52 -0
- package/src/map-style-schema.ts +54 -0
- package/src/map-style.ts +18 -0
- package/src/mapbox-style.ts +196 -0
- package/src/mvt-label-layer.ts +269 -0
- package/src/style-resolver.ts +173 -0
- 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
|
+
}
|