• 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.0
/modules/deck-layers/src/tile-source-layer.ts
1
// loaders.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {CompositeLayer} from '@deck.gl/core';
6
import {MVTLayer, TileLayer, type TileLayerProps} from '@deck.gl/geo-layers';
7
import {BitmapLayer, GeoJsonLayer, PathLayer} from '@deck.gl/layers';
8

9
import type {GetTileDataParameters, TileSource, TileSourceMetadata} from '@loaders.gl/loader-utils';
10

11
/**
12
 * Runtime shape used by the example layer adapter.
13
 *
14
 * Some tile source implementations add deck.gl-specific flags such as
15
 * `localCoordinates`, MIME type metadata, and mutable table coordinate options.
16
 */
17
export type TileSourceRuntime = TileSource & {
18
  /** Indicates that vector tiles can be rendered in local coordinates. */
19
  localCoordinates?: boolean;
20
  /** MIME type that identifies vector or image payload handling. */
21
  mimeType: string | null;
22
  /** Mutable source options forwarded to loaders.gl tile fetches. */
23
  options: {
24
    table?: {
25
      /** Coordinate mode requested for table-backed tiles. */
26
      coordinates?: string;
27
    };
28
  };
29
  /** Source URL used for stable layer ids. */
30
  url?: string;
31
};
32

33
/**
34
 * Internal deck.gl MVT helper used by `TileSourceLayer`.
35
 *
36
 * This class is not part of the supported public API and is documented only through TSDoc.
37
 */
38
class MVTSourceLoaderLayer extends MVTLayer<any> {
39
  /** Sync the cached vector tile source whenever deck.gl reports a data change. */
40
  updateState(params: any): void {
41
    super.updateState(params);
×
42

43
    const {props, changeFlags} = params;
×
44
    if (changeFlags.dataChanged && props.data) {
×
45
      this.setState({
×
46
        vectorTileSource: props.data,
47
        binary: false
48
      });
49
    }
50
  }
51

52
  /** Fetch tile payloads directly from the wrapped loaders.gl tile source. */
53
  async getTileData(parameters: GetTileDataParameters): Promise<any> {
54
    try {
×
55
      const vectorTileSource = (this.state as any).vectorTileSource as TileSourceRuntime | null;
×
56
      return vectorTileSource ? await vectorTileSource.getTileData(parameters) : null;
×
57
    } catch (error) {
58
      this.props.onTileError?.(error, parameters);
×
59
      return null;
×
60
    }
61
  }
62

63
  /** Render standard `MVTLayer` sublayers after the custom fetch path resolves. */
64
  renderSubLayers(props: any) {
65
    return super.renderSubLayers(props);
×
66
  }
67
}
68

69
/**
70
 * Props for `TileSourceLayer`.
71
 *
72
 * This layer adapts loaders.gl `TileSource` instances for deck.gl rendering.
73
 */
74
export type TileSourceLayerProps = Omit<TileLayerProps, 'data'> & {
75
  /** A source of vector or image tiles. */
76
  data: TileSourceRuntime;
77
  /** Optional tileset metadata used for zoom bounds and attribution. */
78
  metadata?: TileSourceMetadata | null;
79
  /** Show borders around tiles. Currently only works in tile mode. */
80
  showTileBorders?: boolean;
81
  /** A unique id for each feature or row. Associates parts of a geometry across tiles. */
82
  uniquePropertyId?: string;
83
  /** The currently highlighted unique property id. */
84
  highlightedFeatureId?: string;
85
  /** Called when a tile payload cannot be fetched or parsed. */
86
  onTileError?: (error: unknown, tileParameters?: unknown) => void;
87
  /** Called when deck.gl reports tiles loaded. */
88
  onTilesLoad?: (...args: any[]) => void;
89
};
90

91
/**
92
 * Internal deck.gl layer that renders a loaders.gl tile source.
93
 *
94
 * It automatically switches between vector-tile and bitmap rendering paths and
95
 * can draw debug tile borders.
96
 *
97
 * This class is exported for internal repository use and examples, and is not documented
98
 * beyond these TSDoc comments.
99
 */
100
export class TileSourceLayer extends CompositeLayer<TileSourceLayerProps> {
101
  /** deck.gl layer name used in debugging output. */
UNCOV
102
  static layerName = 'TileSourceLayer';
6✔
103

104
  /** Default props shared by the vector and raster rendering paths. */
UNCOV
105
  static defaultProps = {
6✔
106
    ...TileLayer.defaultProps,
107
    layerMode: 'tile',
108
    showTileBorders: true
109
  };
110

111
  /** Initialize local state before props are first rendered. */
112
  initializeState(): void {
113
    this.setState({
×
114
      tileSource: null
115
    });
116
  }
117

118
  /** Mirror the latest `data` prop into layer state. */
119
  updateState({props}: any): void {
120
    this.setState({
×
121
      tileSource: props.data
122
    });
123
  }
124

125
  /** Render either the vector-tile or generic tile path for the current source. */
126
  renderLayers() {
127
    const {tileSource} = this.state as {tileSource: TileSourceRuntime | null};
×
128

129
    if (!tileSource) {
×
130
      return null;
×
131
    }
132

133
    if (this.sourceSupportsMVTLayer()) {
×
134
      // TODO - Currently only TileSource that supports CRS override is TableTileSourceLoader
135
      tileSource.options.table = tileSource.options.table || {};
×
136
      tileSource.options.table.coordinates = 'local';
×
137
      return this.renderMVTLayer();
×
138
    }
139

140
    // TODO - Currently only TileSource that supports CRS override is TableTileSourceLoader
141
    tileSource.options.table = tileSource.options.table || {};
×
142
    tileSource.options.table.coordinates = 'wgs84';
×
143
    return this.renderTileLayer();
×
144
  }
145

146
  /** Check if the current source supports MVT layer rendering with local coordinates. */
147
  sourceSupportsMVTLayer(): boolean {
148
    const {data} = this.props;
×
149
    return data.mimeType === 'application/vnd.mapbox-vector-tile' && Boolean(data.localCoordinates);
×
150
  }
151

152
  /** Render vector tiles through `MVTLayer` when local coordinate support is required. */
153
  renderMVTLayer() {
154
    const {data, showTileBorders, metadata, onTilesLoad, onTileError} = this.props;
×
155
    const minZoom = metadata?.minZoom || 0;
×
156
    const maxZoom = metadata?.maxZoom || 30;
×
157
    const devicePixelRatio = this.context.device.getCanvasContext().getDevicePixelRatio();
×
158

159
    return [
×
160
      new MVTSourceLoaderLayer({
161
        id: String(data.url),
162
        data: data as any,
163
        getLineColor: [0, 0, 0],
164
        getLineWidth: 1,
165
        getFillColor: [100, 120, 140],
166
        lineWidthUnits: 'pixels',
167
        pickable: true,
168
        autoHighlight: true,
169
        onViewportLoad: onTilesLoad,
170
        onTileError,
171
        minZoom,
172
        maxZoom,
173
        tileSize: 256,
174
        zoomOffset: devicePixelRatio === 1 ? -1 : 0,
×
175
        showTileBorders
176
      } as any)
177
    ];
178
  }
179

180
  /** Render image tiles and non-local vector tiles through `TileLayer`. */
181
  renderTileLayer() {
182
    const {data, showTileBorders, metadata, onTilesLoad, onTileError} = this.props;
×
183
    const minZoom = metadata?.minZoom || 0;
×
184
    const maxZoom = metadata?.maxZoom || 30;
×
185
    const devicePixelRatio = this.context.device.getCanvasContext().getDevicePixelRatio();
×
186

187
    return [
×
188
      new TileLayer({
189
        id: String(data.url),
190
        getTileData: async (parameters: GetTileDataParameters) => {
191
          try {
×
192
            return await data.getTileData(parameters);
×
193
          } catch (error) {
194
            onTileError?.(error, parameters);
×
195
            return null;
×
196
          }
197
        },
198
        maxRequests: 20,
199
        pickable: true,
200
        autoHighlight: showTileBorders,
201
        onViewportLoad: onTilesLoad,
202
        minZoom,
203
        maxZoom,
204
        tileSize: 256,
205
        zoomOffset: devicePixelRatio === 1 ? -1 : 0,
×
206
        renderSubLayers: renderSubLayers as any,
207
        tileSource: data as any,
208
        showTileBorders
209
      } as any)
210
    ];
211
  }
212
}
213

214
/**
215
 * Sublayer render callback for the top-level `TileLayer`.
216
 *
217
 * Renders vector tiles with `GeoJsonLayer`, image tiles with `BitmapLayer`, and
218
 * optional debug tile borders with `PathLayer`.
219
 *
220
 * @param props - Tile sublayer props supplied by deck.gl.
221
 * @returns A list of sublayers for the resolved tile payload.
222
 */
223
function renderSubLayers(props: any) {
224
  const {
225
    tileSource,
226
    showTileBorders,
227
    minZoom,
228
    maxZoom,
229
    tile: {
230
      index: {z: zoom},
231
      bbox: {west, south, east, north}
232
    }
233
  } = props;
×
234

235
  const layers: any[] = [];
×
236
  const resolvedMinZoom = minZoom ?? 0;
×
237
  const resolvedMaxZoom = maxZoom ?? 30;
×
238
  const borderColor =
239
    zoom <= resolvedMinZoom || zoom >= resolvedMaxZoom ? [255, 0, 0, 255] : [0, 0, 255, 255];
×
240

241
  switch (tileSource.mimeType) {
×
242
    case 'application/vnd.mapbox-vector-tile':
243
    case 'application/vnd.maplibre-tile':
244
      layers.push(
×
245
        new GeoJsonLayer({
246
          id: `${props.id}-geojson`,
247
          data: props.data as any,
248
          pickable: true,
249
          autoHighlight: true,
250
          lineWidthScale: 500,
251
          lineWidthMinPixels: 0.5,
252
          getFillColor: [100, 120, 140, 255],
253
          highlightColor: [0, 0, 200, 255]
254
        })
255
      );
256
      break;
×
257

258
    case 'image/png':
259
    case 'image/jpeg':
260
    case 'image/webp':
261
    case 'image/avif':
262
      layers.push(
×
263
        new BitmapLayer(props, {
264
          data: null as any,
265
          image: props.data,
266
          bounds: [west, south, east, north],
267
          pickable: true
268
        } as any)
269
      );
270
      break;
×
271

272
    default:
273
      console.error('Unknown tile mimeType', tileSource?.mimeType);
×
274
  }
275

276
  if (showTileBorders) {
×
277
    layers.push(
×
278
      new PathLayer({
279
        id: `${props.id}-border`,
280
        data: [
281
          [
282
            [west, north],
283
            [west, south],
284
            [east, south],
285
            [east, north],
286
            [west, north]
287
          ]
288
        ],
289
        getPath: data => data,
×
290
        getColor: borderColor as any,
291
        widthMinPixels: 4
292
      })
293
    );
294
  }
295

296
  return layers;
×
297
}
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