Skip to content

Commit

Permalink
TileLayer API audit (#4246)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Feb 20, 2020
1 parent cf80e02 commit e6ce2ea
Show file tree
Hide file tree
Showing 11 changed files with 549 additions and 373 deletions.
70 changes: 35 additions & 35 deletions docs/layers/tile-layer.md
Expand Up @@ -7,7 +7,9 @@

# TileLayer

This TileLayer takes in a function `getTileData` that fetches tiles, and renders it in a GeoJsonLayer or with the layer returned in `renderSubLayers`.
The `TileLayer` is a composite layer that makes it possible to visualize very large datasets. Instead of fetching the entire dataset, it only loads and renders what's visible in the current viewport.

To use this layer, the data must be sliced into "tiles". Each tile has a pre-defined bounding box and level of detail. The layer takes in a function `getTileData` that fetches tiles when they are needed, and renders the loaded data in a GeoJsonLayer or with the layer returned in `renderSubLayers`.

```js
import DeckGL from '@deck.gl/react';
Expand Down Expand Up @@ -107,9 +109,11 @@ The `tile` argument contains the following fields:
- `x` (Number) - x index of the tile
- `y` (Number) - y index of the tile
- `z` (Number) - z index of the tile
- `bbox` (Object) - bounding box of the tile, see `tileToBoundingBox`.
- `bbox` (Object) - bounding box of the tile

When used with a geospatial view such as the [MapView](/docs/api-reference/map-view.md), x, y, and z are determined from [the OSM tile index](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames), and `bbox` is in the shape of `{west: <longitude>, north: <latitude>, east: <longitude>, south: <latitude>}`.

By default, the `TileLayer` loads tiles defined by [the OSM tile index](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames). You may override this by implementing `getTileIndices`.
When used with a non-geospatial view such as the [OrthographicView](/docs/api-reference/orthographic-view.md) or the [OrbitView](/docs/api-reference/orbit-view.md), x, y, and z are determined by dividing the view space in to 512 pixel by 512 pixel tiles. `bbox` is in the shape of `{left, top, right, bottom}`.


##### `maxZoom` (Number|Null, optional)
Expand All @@ -126,49 +130,31 @@ Hide tiles when under-zoomed.
- Default: 0


##### `maxCacheSize` (Number|Null, optional)

The maximum cache size for a tile layer. If not defined, it is calculated using the number of tiles in the current viewport times multiplied by `5`.

- Default: `null`


##### `strategy` (Enum, optional)
##### `maxCacheSize` (Number, optional)

How the tile layer determines the visibility of tiles. One of the following:
The maximum number of tiles that can be cached. The tile cache keeps loaded tiles in memory even if they are no longer visible. It reduces the need to re-download the same data over and over again when the user pan/zooms around the map, providing a smoother experience.

* `'best-available'`: If a tile in the current viewport is waiting for its data to load, use cached content from the closest zoom level to fill the empty space. This approach minimizes the visual flashing due to missing content.
* `'no-overlap'`: Avoid showing overlapping tiles when backfilling with cached content. This is usually favorable when tiles do not have opaque backgrounds.
If not supplied, the `maxCacheSize` is calculated as `5` times the number of tiles in the current viewport.

- Default: `'best-available'`


##### `tileToBoundingBox` (Function, optional)

**Advanced** Converts from `x, y, z` tile indices to a bounding box in the global coordinates. The default implementation converts an OSM tile index to `{west: <longitude>, north: <latitude>, east: <longitude>, south: <latitude>}`.

Receives arguments:
- Default: `null`

- `x` (Number)
- `y` (Number)
- `z` (Number)

The returned value will be available via `tile.bbox`.
##### `maxCacheByteSize` (Number, optional)

The maximum memory used for caching tiles. If this limit is supplied, `getTileData` must return an object that contains a `byteLength` field.

##### `getTileIndices` (Function, optional)
- Default: `null`

**Advanced** This function converts a given viewport to the indices needed to fetch tiles contained in the viewport. The default implementation returns visible tiles defined by [the OSM tile index](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).

Receives arguments:
##### `refinementStrategy` (Enum, optional)

- `viewport` (Viewport)
- `minZoom` (Number) The minimum zoom level
- `maxZoom` (Number) The maximum zoom level
How the tile layer refines the visibility of tiles. One of the following:

Returns:
* `'best-available'`: If a tile in the current viewport is waiting for its data to load, use cached content from the closest zoom level to fill the empty space. This approach minimizes the visual flashing due to missing content.
* `'no-overlap'`: Avoid showing overlapping tiles when backfilling with cached content. This is usually favorable when tiles do not have opaque backgrounds.
* `'never'`: Do not display any tile that is not selected.

An array of objects in the shape of `{x, y, z}`.
- Default: `'best-available'`


### Render Options
Expand All @@ -193,13 +179,27 @@ Renders one or an array of Layer instances with all the `TileLayer` props and th
- Default: `data => null`


##### `onTileLoad` (Function, optional)

`onTileLoad` called when a tile successfully loads.

- Default: `() => {}`

Receives arguments:

- `tile` (Object) - the tile that has been loaded.

##### `onTileError` (Function, optional)

`onTileError` called when a tile failed to load.

- Default: `console.error`

Receives arguments:

- `error` (`Error`)


# Source
## Source

[modules/geo-layers/src/tile-layer](https://github.com/uber/deck.gl/tree/master/modules/geo-layers/src/tile-layer)
@@ -1,10 +1,11 @@
import {log} from '@deck.gl/core';

export default class Tile2DHeader {
constructor({getTileData, x, y, z, onTileLoad, onTileError, tileToBoundingBox}) {
constructor({getTileData, x, y, z, onTileLoad, onTileError}) {
this.x = x;
this.y = y;
this.z = z;
this.bbox = tileToBoundingBox(this.x, this.y, this.z);
this.selected = true;
this.isVisible = false;
this.parent = null;
this.children = [];

Expand All @@ -17,13 +18,21 @@ export default class Tile2DHeader {
}

get data() {
return this.content || this._loader;
return this._isLoaded ? this.content : this._loader;
}

get isLoaded() {
return this._isLoaded;
}

get byteLength() {
const result = this.content ? this.content.byteLength : 0;
if (!Number.isFinite(result)) {
log.error('byteLength not defined in tile data')();
}
return result;
}

_loadData(getTileData) {
const {x, y, z, bbox} = this;
if (!getTileData) {
Expand Down
46 changes: 20 additions & 26 deletions modules/geo-layers/src/tile-layer/tile-layer.js
@@ -1,23 +1,21 @@
import {CompositeLayer} from '@deck.gl/core';
import {GeoJsonLayer} from '@deck.gl/layers';

import Tileset2D, {STRATEGY_DEFAULT} from './utils/tileset-2d';
import {tileToBoundingBox as defaultTile2Bbox} from './utils/tile-util';
import {getTileIndices as defaultGetIndices} from './utils/viewport-util';
import Tileset2D, {STRATEGY_DEFAULT} from './tileset-2d';

const defaultProps = {
renderSubLayers: {type: 'function', value: props => new GeoJsonLayer(props), compare: false},
getTileData: {type: 'function', value: ({x, y, z}) => null, compare: false},
tileToBoundingBox: {type: 'function', value: defaultTile2Bbox, compare: false},
getTileIndices: {type: 'function', value: defaultGetIndices, compare: false},
// TODO - change to onViewportLoad to align with Tile3DLayer
onViewportLoad: {type: 'function', optional: true, value: null, compare: false},
onTileLoad: {type: 'function', value: tile => {}, compare: false},
// eslint-disable-next-line
onTileError: {type: 'function', value: err => console.error(err), compare: false},
maxZoom: null,
minZoom: 0,
maxCacheSize: null,
strategy: STRATEGY_DEFAULT
maxCacheByteSize: null,
refinementStrategy: STRATEGY_DEFAULT
};

export default class TileLayer extends CompositeLayer {
Expand Down Expand Up @@ -45,31 +43,20 @@ export default class TileLayer extends CompositeLayer {
(changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getTileData));

if (createTileCache) {
const {
getTileData,
maxZoom,
minZoom,
maxCacheSize,
strategy,
getTileIndices,
tileToBoundingBox
} = props;
if (tileset) {
tileset.finalize();
}
const {maxZoom, minZoom, maxCacheSize, maxCacheByteSize, refinementStrategy} = props;
tileset = new Tileset2D({
getTileData,
getTileIndices,
tileToBoundingBox,
maxSize: maxCacheSize,
getTileData: props.getTileData,
maxCacheSize,
maxCacheByteSize,
maxZoom,
minZoom,
strategy,
onTileLoad: this._updateTileset.bind(this),
refinementStrategy,
onTileLoad: this._onTileLoad.bind(this),
onTileError: this._onTileError.bind(this)
});
this.setState({tileset});
} else if (changeFlags.propsChanged) {
tileset.setOptions(props);
// if any props changed, delete the cached layers
this.state.tileset.tiles.forEach(tile => {
tile.layer = null;
Expand Down Expand Up @@ -102,10 +89,17 @@ export default class TileLayer extends CompositeLayer {
this.state.isLoaded = isLoaded;
}

_onTileLoad(tile) {
const layer = this.getCurrentLayer();
layer.props.onTileLoad(tile);
layer._updateTileset();
}

_onTileError(error) {
this.props.onTileError(error);
const layer = this.getCurrentLayer();
layer.props.onTileError(error);
// errorred tiles should not block rendering, are considered "loaded" with empty data
this._updateTileset();
layer._updateTileset();
}

getPickingInfo({info, sourceLayer}) {
Expand Down

0 comments on commit e6ce2ea

Please sign in to comment.