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

visgl / loaders.gl / 25070233425

28 Apr 2026 06:20PM UTC coverage: 59.434% (+0.01%) from 59.423%
25070233425

push

github

web-flow
website: Add tabs for navigating between format docs (#3407)

11310 of 20887 branches covered (54.15%)

Branch coverage included in aggregate %.

89 of 136 new or added lines in 13 files covered. (65.44%)

1742 existing lines in 132 files now uncovered.

23500 of 37682 relevant lines covered (62.36%)

16296.63 hits per line

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

2.07
/modules/deck-layers/src/data-driven-tile-3d-layer.ts
1
// loaders.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {Tile3DLayer, type Tile3DLayerProps} from '@deck.gl/geo-layers';
6
import type {DefaultProps, UpdateParameters, Viewport} from '@deck.gl/core';
7
import {TILE_TYPE, Tile3D, Tileset3D} from '@loaders.gl/tiles';
8
import {load} from '@loaders.gl/core';
9

10
/** Color ramp configuration applied to tile content using a numeric feature attribute. */
11
export type ColorsByAttribute = {
12
  /** Feature attribute name. */
13
  attributeName: string;
14
  /** Minimum attribute value. */
15
  minValue: number;
16
  /** Maximum attribute value. */
17
  maxValue: number;
18
  /** Minimum color of the applied gradient. */
19
  minColor: [number, number, number, number];
20
  /** Maximum color of the applied gradient. */
21
  maxColor: [number, number, number, number];
22
  /** Colorization mode. */
23
  mode: string;
24
};
25

26
/** Filter configuration applied to tile content using a numeric feature attribute. */
27
export type FiltersByAttribute = {
28
  /** Feature attribute name. */
29
  attributeName: string;
30
  /** Filter value. */
31
  value: number;
32
};
33

34
type BaseDataDrivenTile3DLayerProps = {
35
  onTraversalComplete?: (selectedTiles: Tile3D[]) => Tile3D[];
36
  colorsByAttribute?: ColorsByAttribute | null;
37
  customizeColors?: (
38
    tile: Tile3D,
39
    colorsByAttribute: ColorsByAttribute | null
40
  ) => Promise<{isColored: boolean; id: string}>;
41
  filtersByAttribute?: FiltersByAttribute | null;
42
  filterTile?: (
43
    tile: Tile3D,
44
    filtersByAttribute: FiltersByAttribute | null
45
  ) => Promise<{isFiltered: boolean; id: string}>;
46
};
47

48
/** Props accepted by `DataDrivenTile3DLayer`. */
49
export type DataDrivenTile3DLayerProps<DataT = unknown> = BaseDataDrivenTile3DLayerProps &
50
  Tile3DLayerProps<DataT>;
51

UNCOV
52
const defaultProps: DefaultProps<DataDrivenTile3DLayerProps> = {
6✔
53
  colorsByAttribute: null,
54
  filtersByAttribute: null
55
};
56

57
/**
58
 * Local copy of the experimental deck.gl-community data-driven 3D tiles layer.
59
 *
60
 * This is vendored into loaders.gl so the examples can run against the exact same
61
 * deck.gl version that the rest of the website bundle uses.
62
 */
63
// @ts-expect-error deck.gl keeps several base methods private on Tile3DLayer.
64
export class DataDrivenTile3DLayer<
65
  DataT = unknown,
66
  ExtraProps extends Record<string, unknown> = Record<string, unknown>
67
> extends Tile3DLayer<DataT, Required<BaseDataDrivenTile3DLayerProps> & ExtraProps> {
68
  /** deck.gl layer name used in diagnostics and defaults. */
UNCOV
69
  static layerName = 'DataDrivenTile3DLayer';
6✔
70

71
  /** Default props for the data-driven tile layer. */
UNCOV
72
  static defaultProps = defaultProps as any;
6✔
73

74
  state: {
75
    activeViewports: any;
76
    frameNumber?: number;
77
    lastUpdatedViewports: {[viewportId: string]: Viewport} | null;
78
    layerMap: {[layerId: string]: any};
79
    tileset3d: Tileset3D | null;
80
    colorsByAttribute: ColorsByAttribute | null;
81
    filtersByAttribute: FiltersByAttribute | null;
82
    loadingCounter: number;
83
  } = undefined!;
×
84

85
  /** Initializes local state used for async recoloring and filtering. */
86
  initializeState(): void {
87
    super.initializeState();
×
88

89
    this.setState({
×
90
      colorsByAttribute: this.props.colorsByAttribute,
91
      filtersByAttribute: this.props.filtersByAttribute,
92
      loadingCounter: 0
93
    });
94
  }
95

96
  /** Updates tileset state and reapplies color/filter hooks when props change. */
97
  updateState(params: UpdateParameters<this>): void {
98
    const {props, oldProps, changeFlags} = params;
×
99

100
    if (props.data && props.data !== oldProps.data) {
×
101
      this._loadTileset(props.data);
×
102
    } else if (props.colorsByAttribute !== oldProps.colorsByAttribute) {
×
103
      this.setState({colorsByAttribute: props.colorsByAttribute});
×
104
      this._colorizeTileset();
×
105
    } else if (props.filtersByAttribute !== oldProps.filtersByAttribute) {
×
106
      this.setState({filtersByAttribute: props.filtersByAttribute});
×
107
      this._filterTileset();
×
108
    } else if (changeFlags.viewportChanged) {
×
109
      const {activeViewports} = this.state;
×
110
      const viewportCount = Object.keys(activeViewports).length;
×
111
      if (viewportCount) {
×
112
        if (!this.state.loadingCounter) {
×
113
          // @ts-expect-error deck.gl keeps this method private on the base class.
114
          super._updateTileset(activeViewports);
×
115
        }
116
        this.state.lastUpdatedViewports = activeViewports;
×
117
        this.state.activeViewports = {};
×
118
      }
119
    } else {
120
      super.updateState(params);
×
121
    }
122
  }
123

124
  /** Loads the root tileset and installs traversal hooks on the resolved `Tileset3D`. */
125
  private override async _loadTileset(tilesetUrl: string): Promise<void> {
126
    const {loadOptions = {}} = this.props;
×
127

128
    let loader: any = this.props.loader || this.props.loaders;
×
129
    if (Array.isArray(loader)) {
×
130
      loader = loader[0];
×
131
    }
132

133
    const options = {loadOptions: {...loadOptions}};
×
134
    if (loader.preload) {
×
135
      const preloadOptions = await loader.preload(tilesetUrl, loadOptions);
×
136

137
      if (preloadOptions.headers) {
×
138
        options.loadOptions.fetch = {
×
139
          ...options.loadOptions.fetch,
140
          headers: preloadOptions.headers
141
        };
142
      }
143
      Object.assign(options, preloadOptions);
×
144
    }
145
    const tilesetJson = await load(tilesetUrl, loader, options.loadOptions);
×
146

147
    // @ts-expect-error deck.gl keeps this method private on the base class.
148
    const onTileUnload = super._onTileUnload.bind(this);
×
149

150
    // @ts-expect-error the base layer passes parsed tileset payloads here.
151
    const tileset3d = new Tileset3D(tilesetJson, {
×
152
      onTileLoad: this._onTileLoad.bind(this),
153
      onTileUnload,
154
      onTileError: this.props.onTileError,
155
      onTraversalComplete: this._onTraversalComplete.bind(this),
156
      ...options
157
    });
158

159
    this.setState({
×
160
      tileset3d,
161
      layerMap: {}
162
    });
163

164
    // @ts-expect-error deck.gl keeps this method private on the base class.
165
    super._updateTileset(this.state.activeViewports);
×
166
    this.props.onTilesetLoad?.(tileset3d);
×
167
  }
168

169
  /** Reapplies data-driven changes when a new tile is loaded. */
170
  private override _onTileLoad(tileHeader: Tile3D): void {
171
    const {lastUpdatedViewports} = this.state;
×
172
    this._colorizeTiles([tileHeader]);
×
173
    this._filterTiles([tileHeader]);
×
174
    this.props.onTileLoad?.(tileHeader);
×
175
    if (!this.state.colorsByAttribute && !this.state.filtersByAttribute) {
×
176
      // @ts-expect-error deck.gl keeps this method private on the base class.
177
      super._updateTileset(lastUpdatedViewports);
×
178
      this.setNeedsUpdate();
×
179
    }
180
  }
181

182
  /** Applies data-driven tile updates after traversal finishes. */
183
  private _onTraversalComplete(selectedTiles: Tile3D[]): Tile3D[] {
184
    this._colorizeTiles(selectedTiles);
×
185
    this._filterTiles(selectedTiles);
×
186
    return this.props.onTraversalComplete
×
187
      ? this.props.onTraversalComplete(selectedTiles)
188
      : selectedTiles;
189
  }
190

191
  /** Applies custom per-tile color updates. */
192
  private _colorizeTiles(tiles: Tile3D[]): void {
193
    if (this.props.customizeColors && tiles[0]?.type === TILE_TYPE.MESH) {
×
194
      const {layerMap, colorsByAttribute} = this.state;
×
195
      const promises: Promise<{isColored: boolean; id: string}>[] = [];
×
196
      for (const tile of tiles) {
×
197
        promises.push(this.props.customizeColors(tile, colorsByAttribute));
×
198
      }
199
      this.setState({
×
200
        loadingCounter: this.state.loadingCounter + 1
201
      });
202
      Promise.allSettled(promises).then(result => {
×
203
        this.setState({
×
204
          loadingCounter: this.state.loadingCounter - 1
205
        });
206
        let isTileChanged = false;
×
207
        for (const item of result) {
×
208
          if (item.status === 'fulfilled' && item.value.isColored) {
×
209
            isTileChanged = true;
×
210
            delete layerMap[item.value.id];
×
211
          }
212
        }
213
        if (isTileChanged && !this.state.loadingCounter) {
×
214
          // @ts-expect-error deck.gl keeps this method private on the base class.
215
          super._updateTileset(this.state.activeViewports);
×
216
          this.setNeedsUpdate();
×
217
        }
218
      });
219
    }
220
  }
221

222
  /** Reapplies custom color state to the currently selected tileset tiles. */
223
  private _colorizeTileset(): void {
224
    const {tileset3d} = this.state;
×
225

226
    if (tileset3d) {
×
227
      this._colorizeTiles(tileset3d.selectedTiles);
×
228
    }
229
  }
230

231
  /** Applies custom per-tile filtering updates. */
232
  private _filterTiles(tiles: Tile3D[]): void {
233
    if (this.props.filterTile && tiles[0]?.type === TILE_TYPE.MESH) {
×
234
      const {layerMap, filtersByAttribute} = this.state;
×
235
      const promises: Promise<{isFiltered: boolean; id: string}>[] = [];
×
236
      for (const tile of tiles) {
×
237
        promises.push(this.props.filterTile(tile, filtersByAttribute));
×
238
      }
239
      this.setState({
×
240
        loadingCounter: this.state.loadingCounter + 1
241
      });
242
      Promise.allSettled(promises).then(result => {
×
243
        this.setState({
×
244
          loadingCounter: this.state.loadingCounter - 1
245
        });
246
        let isTileChanged = false;
×
247
        for (const item of result) {
×
248
          if (item.status === 'fulfilled' && item.value.isFiltered) {
×
249
            isTileChanged = true;
×
250
            delete layerMap[item.value.id];
×
251
          }
252
        }
253
        if (isTileChanged && !this.state.loadingCounter) {
×
254
          // @ts-expect-error deck.gl keeps this method private on the base class.
255
          super._updateTileset(this.state.activeViewports);
×
256
          this.setNeedsUpdate();
×
257
        }
258
      });
259
    }
260
  }
261

262
  /** Reapplies filter state to the currently selected tileset tiles. */
263
  private _filterTileset(): void {
264
    const {tileset3d} = this.state;
×
265

266
    if (tileset3d) {
×
267
      this._filterTiles(tileset3d.selectedTiles);
×
268
    }
269
  }
270
}
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