• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 13707131737

06 Mar 2025 08:02PM UTC coverage: 66.11% (-0.2%) from 66.286%
13707131737

push

github

web-flow
[fix] fixes for vector-tile layer (#3013)

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>

6046 of 10674 branches covered (56.64%)

Branch coverage included in aggregate %.

2 of 57 new or added lines in 6 files covered. (3.51%)

2 existing lines in 2 files now uncovered.

12410 of 17243 relevant lines covered (71.97%)

87.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.87
/src/layers/src/vector-tile/vector-tile-layer.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import {FeatureCollection, Feature} from 'geojson';
5

6
import {Layer as DeckLayer} from '@deck.gl/core/typed';
7
import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers/typed';
8
import {GeoJsonLayer, PathLayer} from '@deck.gl/layers/typed';
9
import {MVTSource, MVTTileSource} from '@loaders.gl/mvt';
10
import {PMTilesSource, PMTilesTileSource} from '@loaders.gl/pmtiles';
11
import GL from '@luma.gl/constants';
12

13
import {notNullorUndefined} from '@kepler.gl/common-utils';
14
import {
15
  DatasetType,
16
  LAYER_TYPES,
17
  RemoteTileFormat,
18
  VectorTileDatasetMetadata,
19
  SCALE_TYPES,
20
  CHANNEL_SCALES,
21
  DEFAULT_COLOR_UI,
22
  LAYER_VIS_CONFIGS
23
} from '@kepler.gl/constants';
24
import {
25
  getTileUrl,
26
  getLoaderOptions,
27
  KeplerTable as KeplerDataset,
28
  Datasets as KeplerDatasets,
29
  GpuFilter,
30
  VectorTileMetadata
31
} from '@kepler.gl/table';
32
import {
33
  AnimationConfig,
34
  Field as KeplerField,
35
  LayerColorConfig,
36
  LayerHeightConfig,
37
  Merge,
38
  MapState,
39
  BindedLayerCallbacks,
40
  VisConfigRange,
41
  VisConfigNumber,
42
  DomainStops
43
} from '@kepler.gl/types';
44
import {DataContainerInterface} from '@kepler.gl/utils';
45

46
import {MVTLayer as CustomMVTLayer} from './mvt-layer';
47
import VectorTileIcon from './vector-tile-icon';
48
import {
49
  default as KeplerLayer,
50
  LayerBaseConfig,
51
  LayerBaseConfigPartial,
52
  VisualChannel,
53
  VisualChannelDomain,
54
  VisualChannelField
55
} from '../base-layer';
56
import {FindDefaultLayerPropsReturnValue} from '../layer-utils';
57

58
import AbstractTileLayer, {
59
  LayerData as CommonLayerData,
60
  commonTileVisConfigs,
61
  AbstractTileLayerConfig,
62
  AbstractTileLayerVisConfigSettings
63
} from './abstract-tile-layer';
64
import TileDataset from './common-tile/tile-dataset';
65
import {
66
  isDomainStops,
67
  isDomainQuantiles,
68
  isIndexedField,
69
  getPropertyByZoom
70
} from './common-tile/tile-utils';
71

72
export const DEFAULT_HIGHLIGHT_FILL_COLOR = [252, 242, 26, 150];
13✔
73
export const DEFAULT_HIGHLIGHT_STROKE_COLOR = [252, 242, 26, 255];
13✔
74
export const MAX_CACHE_SIZE_MOBILE = 1; // Minimize caching, visible tiles will always be loaded
13✔
75
export const DEFAULT_STROKE_WIDTH = 1;
13✔
76

77
/**
78
 * Type for transformRequest returned parameters.
79
 */
80
export type RequestParameters = {
81
  /** The URL to be requested. */
82
  url: string;
83
  /** Search parameters to be added onto the URL. */
84
  searchParams: URLSearchParams;
85
  /** Options passed to fetch. */
86
  options: RequestInit;
87
};
88

89
// This type *seems* to be what loaders.gl currently returns for tile content.
90
// Apparently this might be different depending on the loaders version, and for...
91
// reasons we use two different versions of loaders right now.
92
// TODO: The Features[] version should not be needed when we update to a newer
93
// version of Deck.gl and use only one version of loaders
94
type TileContent =
95
  | (FeatureCollection & {shape: 'geojson-table'})
96
  | (Feature[] & {shape: undefined});
97

98
type VectorTile = Tile2DHeader<TileContent>;
99

100
type LayerData = CommonLayerData & {
101
  tilesetDataUrl?: string | null;
102
  tileSource: MVTTileSource | PMTilesTileSource | null;
103
};
104

105
type VectorTileLayerRenderOptions = Merge<
106
  {
107
    idx: number;
108
    visible: boolean;
109
    mapState: MapState;
110
    data: any;
111
    animationConfig: AnimationConfig;
112
    gpuFilter: GpuFilter;
113
    layerCallbacks: BindedLayerCallbacks;
114
    objectHovered: {
115
      index: number;
116
      tile: VectorTile;
117
      sourceLayer: typeof GeoJsonLayer;
118
    };
119
  },
120
  LayerData
121
>;
122

123
export const vectorTileVisConfigs = {
13✔
124
  ...commonTileVisConfigs,
125

126
  stroked: {
127
    ...LAYER_VIS_CONFIGS.stroked,
128
    defaultValue: false
129
  },
130

131
  // TODO figure out why strokeColorScale can't be const
132
  strokeColorScale: 'strokeColorScale' as any,
133
  strokeColorRange: 'strokeColorRange' as const,
134

135
  sizeRange: 'strokeWidthRange' as const,
136
  strokeWidth: {
137
    ...LAYER_VIS_CONFIGS.thickness,
138
    property: 'strokeWidth',
139
    defaultValue: 0.5,
140
    allowCustomValue: false
141
  },
142

143
  radiusScale: 'radiusScale' as any,
144
  radiusRange: {
145
    ...LAYER_VIS_CONFIGS.radiusRange,
146
    type: 'number',
147
    defaultValue: [0, 1],
148
    isRanged: true,
149
    range: [0, 1],
150
    step: 0.01
151
  } as VisConfigRange
152
};
153

154
export type VectorTileLayerConfig = Merge<
155
  AbstractTileLayerConfig,
156
  {
157
    sizeField?: VisualChannelField;
158
    sizeScale?: string;
159
    sizeDomain?: VisualChannelDomain;
160

161
    strokeColorField: VisualChannelField;
162

163
    radiusField?: VisualChannelField;
164
    radiusScale?: string;
165
    radiusDomain?: VisualChannelDomain;
166
    radiusRange?: any;
167
  }
168
>;
169

170
export type VectorTileLayerVisConfigSettings = Merge<
171
  AbstractTileLayerVisConfigSettings,
172
  {
173
    sizeRange: VisConfigRange;
174
    strokeWidth: VisConfigNumber;
175
  }
176
>;
177

178
export function tileLayerBoundsLayer(id: string, props: {bounds?: number[]}): DeckLayer[] {
NEW
179
  const {bounds} = props;
×
NEW
180
  if (bounds?.length !== 4) return [];
×
181

NEW
182
  const data = [
×
183
    {
184
      path: [
185
        [bounds[0], bounds[1]],
186
        [bounds[2], bounds[1]],
187
        [bounds[2], bounds[3]],
188
        [bounds[0], bounds[3]],
189
        [bounds[0], bounds[1]]
190
      ]
191
    }
192
  ];
193

NEW
194
  const layer = new PathLayer({
×
195
    id: `${id}-vector-tile-bounds`,
196
    data,
NEW
197
    getPath: d => d.path,
×
198
    getColor: [128, 128, 128, 255],
199
    getWidth: 1,
200
    widthUnits: 'pixels',
201
    pickable: false
202
  });
203

NEW
204
  return [layer];
×
205
}
206

207
export default class VectorTileLayer extends AbstractTileLayer<VectorTile, Feature[]> {
208
  declare config: VectorTileLayerConfig;
209
  declare visConfigSettings: VectorTileLayerVisConfigSettings;
210

211
  constructor(props: ConstructorParameters<typeof AbstractTileLayer>[0]) {
212
    super(props);
27✔
213
    this.registerVisConfig(vectorTileVisConfigs);
27✔
214
    this.tileDataset = this.initTileDataset();
27✔
215
  }
216

217
  meta = {};
27✔
218

219
  static findDefaultLayerProps(dataset: KeplerDataset): FindDefaultLayerPropsReturnValue {
220
    if (dataset.type !== DatasetType.VECTOR_TILE) {
90!
221
      return {props: []};
90✔
222
    }
223
    return super.findDefaultLayerProps(dataset);
×
224
  }
225

226
  initTileDataset(): TileDataset<VectorTile, Feature[]> {
227
    return new TileDataset({
54✔
228
      getTileId: (tile: VectorTile): string => tile.id,
×
229
      getIterable: (tile: VectorTile): Feature[] => {
230
        if (tile.content) {
×
231
          return tile.content.shape === 'geojson-table' ? tile.content.features : tile.content;
×
232
        }
233
        return [];
×
234
      },
235
      getRowCount: (features: Feature[]): number => features.length,
×
236
      getRowValue: this.accessRowValue
237
    });
238
  }
239

240
  get type() {
241
    return LAYER_TYPES.vectorTile;
×
242
  }
243

244
  get name(): string {
245
    return 'Vector Tile';
27✔
246
  }
247

248
  get layerIcon(): KeplerLayer['layerIcon'] {
249
    return VectorTileIcon;
27✔
250
  }
251

252
  get supportedDatasetTypes(): DatasetType[] {
253
    return [DatasetType.VECTOR_TILE];
×
254
  }
255

256
  get visualChannels(): Record<string, VisualChannel> {
257
    const visualChannels = super.visualChannels;
×
258
    return {
×
259
      ...visualChannels,
260
      strokeColor: {
261
        property: 'strokeColor',
262
        field: 'strokeColorField',
263
        scale: 'strokeColorScale',
264
        domain: 'strokeColorDomain',
265
        range: 'strokeColorRange',
266
        key: 'strokeColor',
267
        channelScaleType: CHANNEL_SCALES.color,
268
        accessor: 'getLineColor',
269
        condition: config => config.visConfig.stroked,
×
270
        nullValue: visualChannels.color.nullValue,
271
        getAttributeValue: config => config.visConfig.strokeColor || config.color
×
272
      },
273
      size: {
274
        property: 'stroke',
275
        field: 'sizeField',
276
        scale: 'sizeScale',
277
        domain: 'sizeDomain',
278
        range: 'sizeRange',
279
        key: 'size',
280
        channelScaleType: CHANNEL_SCALES.size,
281
        nullValue: 0,
282
        accessor: 'getLineWidth',
283
        condition: config => config.visConfig.stroked,
×
284
        getAttributeValue: config => config.visConfig.strokeWidth || DEFAULT_STROKE_WIDTH
×
285
      },
286
      radius: {
287
        property: 'radius',
288
        field: 'radiusField',
289
        scale: 'radiusScale',
290
        domain: 'radiusDomain',
291
        range: 'radiusRange',
292
        key: 'radius',
293
        channelScaleType: CHANNEL_SCALES.size,
294
        nullValue: 0,
295
        getAttributeValue: config => {
296
          return config.visConfig.radius || config.radius;
×
297
        },
298
        accessor: 'getPointRadius',
299
        defaultValue: config => config.radius
×
300
      }
301
    };
302
  }
303

304
  getDefaultLayerConfig(
305
    props: LayerBaseConfigPartial
306
  ): LayerBaseConfig & Partial<LayerColorConfig & LayerHeightConfig> {
307
    const defaultLayerConfig = super.getDefaultLayerConfig(props);
27✔
308
    return {
27✔
309
      ...defaultLayerConfig,
310
      colorScale: SCALE_TYPES.quantize,
311

312
      strokeColorField: null,
313
      strokeColorDomain: [0, 1],
314
      strokeColorScale: SCALE_TYPES.quantile,
315
      colorUI: {
316
        ...defaultLayerConfig.colorUI,
317
        // @ts-expect-error LayerConfig
318
        strokeColorRange: DEFAULT_COLOR_UI
319
      },
320

321
      radiusField: null,
322
      radiusDomain: [0, 1],
323
      radiusScale: SCALE_TYPES.linear
324
    };
325
  }
326

327
  getHoverData(
328
    object: {properties?: Record<string, Record<string, unknown>>},
329
    dataContainer: DataContainerInterface,
330
    fields: KeplerField[]
331
  ): (Record<string, unknown> | null)[] {
332
    return fields.map(f => object.properties?.[f.name] ?? null);
×
333
  }
334

335
  calculateLayerDomain(
336
    dataset: KeplerDataset,
337
    visualChannel: VisualChannel
338
  ): DomainStops | number[] {
339
    const defaultDomain = [0, 1];
×
340

341
    const field = this.config[visualChannel.field];
×
342
    const scale = this.config[visualChannel.scale];
×
343
    if (!field) {
×
344
      // if colorField or sizeField were set back to null
345
      return defaultDomain;
×
346
    }
347
    if (scale === SCALE_TYPES.quantile && isDomainQuantiles(field?.filterProps?.domainQuantiles)) {
×
348
      return field.filterProps.domainQuantiles;
×
349
    }
350
    if (isDomainStops(field?.filterProps?.domainStops)) {
×
351
      return field.filterProps.domainStops;
×
352
    } else if (Array.isArray(field?.filterProps?.domain)) {
×
353
      return field.filterProps.domain;
×
354
    }
355

356
    return defaultDomain;
×
357
  }
358

359
  getScaleOptions(channelKey: string): string[] {
360
    let options = KeplerLayer.prototype.getScaleOptions.call(this, channelKey);
×
361

362
    const channel = this.visualChannels.strokeColor;
×
363
    const field = this.config[channel.field];
×
364
    if (
×
365
      !(
366
        isDomainQuantiles(field?.filterProps?.domainQuantiles) ||
×
367
        this.config.visConfig.dynamicColor ||
368
        // If we've set the scale to quantile, we need to include it - there's a loading
369
        // period in which the visConfig isn't set yet, but if we don't return the right
370
        // scale type we lose it
371
        this.config.colorScale === SCALE_TYPES.quantile
372
      )
373
    ) {
374
      options = options.filter(scale => scale !== SCALE_TYPES.quantile);
×
375
    }
376

377
    return options;
×
378
  }
379

380
  accessRowValue(
381
    field?: KeplerField,
382
    indexKey?: number | null
383
  ): (field: KeplerField, datum: Feature) => number | null {
384
    // if is indexed field
385
    if (isIndexedField(field) && indexKey !== null) {
×
386
      const fieldName = indexKey && field?.indexBy?.mappedValue[indexKey];
×
387
      if (fieldName) {
×
388
        return (f, datum) => {
×
389
          if (datum.properties) {
×
390
            return datum.properties[fieldName];
×
391
          }
392
          // TODO debug this with indexed tiled dataset
393
          return datum[fieldName];
×
394
        };
395
      }
396
    }
397

398
    // default
399
    return (f, datum) => {
×
400
      if (f && datum.properties) {
×
401
        return datum.properties[f.name];
×
402
      }
403
      // support picking & highlighting
404
      return f ? datum[f.fieldIdx] : null;
×
405
    };
406
  }
407

408
  updateLayerMeta(dataset: KeplerDataset, datasets: KeplerDatasets): void {
409
    if (dataset.type !== DatasetType.VECTOR_TILE) {
×
410
      return;
×
411
    }
412

413
    const datasetMeta = dataset.metadata as VectorTileMetadata & VectorTileDatasetMetadata;
×
414
    this.updateMeta({
×
415
      datasetId: dataset.id,
416
      datasets,
417
      bounds: datasetMeta.bounds
418
    });
419
  }
420

421
  formatLayerData(
422
    datasets: KeplerDatasets,
423
    oldLayerData: unknown,
424
    animationConfig: AnimationConfig
425
  ): LayerData {
426
    const {dataId} = this.config;
×
427
    if (!notNullorUndefined(dataId)) {
×
428
      return {tileSource: null};
×
429
    }
430
    const dataset = datasets[dataId];
×
431

432
    let tilesetDataUrl: string | undefined;
433
    let tileSource: LayerData['tileSource'] = null;
×
434

435
    if (dataset?.type === DatasetType.VECTOR_TILE) {
×
436
      const datasetMetadata = dataset.metadata as VectorTileMetadata & VectorTileDatasetMetadata;
×
437
      const remoteTileFormat = datasetMetadata?.remoteTileFormat;
×
438
      if (remoteTileFormat === RemoteTileFormat.MVT) {
×
439
        const transformFetch = async (input: RequestInfo | URL, init?: RequestInit | undefined) => {
×
440
          const requestData: RequestParameters = {
×
441
            url: input as string,
442
            searchParams: new URLSearchParams(),
443
            options: init ?? {}
×
444
          };
445

446
          return fetch(requestData.url, requestData.options);
×
447
        };
448

449
        tilesetDataUrl = datasetMetadata?.tilesetDataUrl;
×
450
        tileSource = tilesetDataUrl
×
451
          ? MVTSource.createDataSource(decodeURIComponent(tilesetDataUrl), {
452
              mvt: {
453
                metadataUrl: datasetMetadata?.tilesetMetadataUrl ?? null,
×
454
                loadOptions: {
455
                  fetch: transformFetch
456
                }
457
              }
458
            })
459
          : null;
460
      } else if (remoteTileFormat === RemoteTileFormat.PMTILES) {
×
461
        // TODO: to render image pmtiles need to use TileLayer and BitmapLayer (https://github.com/visgl/loaders.gl/blob/master/examples/website/tiles/components/tile-source-layer.ts)
462
        tilesetDataUrl = datasetMetadata?.tilesetDataUrl;
×
463
        tileSource = tilesetDataUrl ? PMTilesSource.createDataSource(tilesetDataUrl, {}) : null;
×
464
      }
465
    }
466

467
    return {
×
468
      ...super.formatLayerData(datasets, oldLayerData, animationConfig),
469
      tilesetDataUrl: typeof tilesetDataUrl === 'string' ? getTileUrl(tilesetDataUrl) : null,
×
470
      tileSource
471
    };
472
  }
473

474
  hasHoveredObject(objectInfo) {
475
    if (super.hasHoveredObject(objectInfo)) {
×
476
      const features = objectInfo?.tile?.content?.features;
×
477
      return features[objectInfo.index];
×
478
    }
479
    return null;
×
480
  }
481

482
  renderSubLayers(props: Record<string, any>): DeckLayer | DeckLayer[] {
483
    let {data} = props;
×
484

485
    data = data?.shape === 'geojson-table' ? data.features : data;
×
486
    if (!data?.length) {
×
487
      return [];
×
488
    }
489

490
    const tile: Tile2DHeader = props.tile;
×
491
    const zoom = tile.index.z;
×
492

493
    return new GeoJsonLayer({
×
494
      ...props,
495
      data,
496
      getFillColor: props.getFillColorByZoom ? props.getFillColor(zoom) : props.getFillColor,
×
497
      getElevation: props.getElevationByZoom ? props.getElevation(zoom) : props.getElevation,
×
498
      // radius for points
499
      pointRadiusScale: props.pointRadiusScale, // props.getPointRadiusScaleByZoom(zoom),
500
      pointRadiusUnits: props.pointRadiusUnits,
501
      getPointRadius: props.getPointRadius,
502
      // For some reason tile Layer reset autoHighlight to false
503
      pickable: true,
504
      autoHighlight: true,
505
      stroked: props.stroked,
506
      // wrapLongitude: true causes missing side polygon when extrude is enabled
507
      wrapLongitude: false
508
    });
509
  }
510

511
  // generate a deck layer
512
  renderLayer(opts: VectorTileLayerRenderOptions): DeckLayer[] {
513
    const {mapState, data, animationConfig, gpuFilter, objectHovered, layerCallbacks} = opts;
×
514
    const {animation, visConfig} = this.config;
×
515

516
    this.setLayerDomain = layerCallbacks.onSetLayerDomain;
×
517

518
    const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
×
519
    const eleZoomFactor = this.getElevationZoomFactor(mapState);
×
520

521
    const transitions = this.config.visConfig.transition
×
522
      ? {
523
          getFillColor: {
524
            duration: animationConfig.duration
525
          },
526
          getElevation: {
527
            duration: animationConfig.duration
528
          }
529
        }
530
      : undefined;
531

532
    const colorField = this.config.colorField as KeplerField;
×
533
    const heightField = this.config.heightField as KeplerField;
×
534
    const strokeColorField = this.config.strokeColorField as KeplerField;
×
535
    const sizeField = this.config.sizeField as KeplerField;
×
536
    const radiusField = this.config.radiusField as KeplerField;
×
537

538
    if (data.tileSource) {
×
539
      const hoveredObject = this.hasHoveredObject(objectHovered);
×
540

541
      const layers = [
×
542
        new CustomMVTLayer({
543
          ...defaultLayerProps,
544
          ...data,
545
          onViewportLoad: this.onViewportLoad,
546
          data: data.tilesetDataUrl,
547
          getTileData: data.tileSource?.getTileData,
548
          tileSource: data.tileSource,
549
          getFilterValue: this.getGpuFilterValueAccessor(opts),
550
          filterRange: gpuFilter.filterRange,
551
          lineWidthUnits: 'pixels',
552

553
          binary: false,
554
          elevationScale: visConfig.elevationScale * eleZoomFactor,
555
          extruded: visConfig.enable3d,
556
          stroked: visConfig.stroked,
557

558
          // TODO: this is hard coded, design a UI to allow user assigned unique property id
559
          // uniqueIdProperty: 'ufid',
560
          renderSubLayers: this.renderSubLayers,
561
          // when radiusUnits is meter
562
          getPointRadiusScaleByZoom: getPropertyByZoom(visConfig.radiusByZoom, visConfig.radius),
563
          pointRadiusUnits: visConfig.radiusUnits ? 'pixels' : 'meters',
×
564
          pointRadiusScale: radiusField ? visConfig.radius : 1,
×
565

566
          pointRadiusMinPixels: 1,
567
          autoHighlight: true,
568
          highlightColor: DEFAULT_HIGHLIGHT_FILL_COLOR,
569
          pickable: true,
570
          transitions,
571
          updateTriggers: {
572
            getFilterValue: {
573
              ...gpuFilter.filterValueUpdateTriggers,
574
              currentTime: animation.enabled ? animationConfig.currentTime : null
×
575
            },
576
            getFillColor: {
577
              color: this.config.color,
578
              colorField: this.config.colorField,
579
              colorScale: this.config.colorScale,
580
              colorDomain: this.config.colorDomain,
581
              colorRange: visConfig.colorRange,
582
              currentTime: isIndexedField(colorField) ? animationConfig.currentTime : null
×
583
            },
584
            getElevation: {
585
              heightField: this.config.heightField,
586
              heightScaleType: this.config.heightScale,
587
              heightRange: visConfig.heightRange,
588
              currentTime: isIndexedField(heightField) ? animationConfig.currentTime : null
×
589
            },
590
            getLineColor: {
591
              strokeColor: visConfig.strokeColor,
592
              strokeColorField: this.config.strokeColorField,
593
              // @ts-expect-error prop not in LayerConfig
594
              strokeColorScale: this.config.strokeColorScale,
595
              // @ts-expect-error prop not in LayerConfig
596
              strokeColorDomain: this.config.strokeColorDomain,
597
              // FIXME: Strip out empty arrays from individual color map steps, and replace with `null`, otherwise the layer may show the incorrect color.
598
              // So far it seems that it uses the previous color chosen in the palette rather than the currently chosen color for the specific custom ordinal value when there are "sparse" color maps.
599
              // In other words, a color map with "holes" of colors with unassigned field values, which may have been assigned in the past.
600
              // For example "abc" was green, stored as `["abc"]`. Then "abc" was reassigned to the red color map step, stored as `["abc"]`. Now the green color map step's stored value is `[]`, and the layer will incorrectly still render "abc" in green.
601
              // Quick patch example:
602
              // strokeColorRange: visConfig?.strokeColorRange?.colorMap?.map(cm =>
603
              //   cm[0]?.length === 0 ? [null, cm[1]] : cm
604
              // ),
605
              // Note: for regular scales the colorMap in the above patch is undefined and breaks strokeColorRange update trigger.
606
              strokeColorRange: visConfig.strokeColorRange,
607
              currentTime: isIndexedField(strokeColorField) ? animationConfig.currentTime : null
×
608
            },
609
            getLineWidth: {
610
              sizeRange: visConfig.sizeRange,
611
              strokeWidth: visConfig.strokeWidth,
612
              sizeField: this.config.sizeField,
613
              sizeScale: this.config.sizeScale,
614
              sizeDomain: this.config.sizeDomain,
615
              currentTime: isIndexedField(sizeField) ? animationConfig.currentTime : null
×
616
            },
617
            getPointRadius: {
618
              radius: visConfig.radius,
619
              radiusField: this.config.radiusField,
620
              radiusScale: this.config.radiusScale,
621
              radiusDomain: this.config.radiusDomain,
622
              radiusRange: this.config.radiusRange,
623
              currentTime: isIndexedField(radiusField) ? animationConfig.currentTime : null
×
624
            }
625
          },
626
          _subLayerProps: {
627
            'polygons-stroke': {opacity: visConfig.strokeOpacity},
628
            'polygons-fill': {
629
              parameters: {
630
                cullFace: GL.BACK
631
              }
632
            }
633
          },
634
          loadOptions: {
635
            mvt: getLoaderOptions().mvt
636
          }
637
        }),
638
        // hover layer
639
        ...(hoveredObject
×
640
          ? [
641
              new GeoJsonLayer({
642
                // @ts-expect-error props not typed?
643
                ...objectHovered.sourceLayer?.props,
644
                ...(this.getDefaultHoverLayerProps() as any),
645
                visible: true,
646
                wrapLongitude: false,
647
                data: [hoveredObject],
648
                getLineColor: DEFAULT_HIGHLIGHT_STROKE_COLOR,
649
                getFillColor: DEFAULT_HIGHLIGHT_FILL_COLOR,
650
                getLineWidth: visConfig.strokeWidth + 1,
651
                lineWidthUnits: 'pixels',
652
                stroked: true,
653
                filled: true
654
              })
655
            ]
656
          : [])
657
        // ...tileLayerBoundsLayer(defaultLayerProps.id, data),
658
      ];
659

660
      return layers;
×
661
    }
662
    return [];
×
663
  }
664
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc