Skip to content

Commit

Permalink
TileLayer API audit (#2799)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xintong Xia committed Mar 19, 2019
1 parent d26e455 commit ffee0e8
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 80 deletions.
70 changes: 38 additions & 32 deletions docs/layers/tile-layer.md
Expand Up @@ -4,31 +4,32 @@ This TileLayer takes in a function `getTileData` that fetches tiles, and renders

```js
import DeckGL from 'deck.gl';
import {TileLayer} from '@deck.gl/experimental-layers';
import {TileLayer} from '@deck.gl/geo-layers';
import {VectorTile} from '@mapbox/vector-tile';
import Protobuf from 'pbf';

export const App = ({viewport}) => {
function getTileData({x, y, z}) {
const mapSource = `https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/${z}/${x}/${y}.vector.pbf?access_token=${MAPBOX_TOKEN}`;
return fetch(mapSource)
.then(response => response.arrayBuffer())
.then(buffer => vectorTileToGeoJSON(buffer, x, y, z));
}

function vectorTileToGeoJSON(buffer, x, y, z) {
const tile = new VectorTile(new Protobuf(buffer));
const features = [];
for (const layerName in tile.layers) {
const vectorTileLayer = tile.layers[layerName];
for (let i = 0; i < vectorTileLayer.length; i++) {
const vectorTileFeature = vectorTileLayer.feature(i);
const feature = vectorTileFeature.toGeoJSON(x, y, z);
features.push(feature);
}
function getTileData({x, y, z}) {
return fetch(`https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/${z}/${x}/${y}.vector.pbf?access_token=${MAPBOX_TOKEN}`)
.then(response => response.arrayBuffer())
.then(buffer => vectorTileToGeoJSON(buffer, x, y, z));
}

function vectorTileToGeoJSON(buffer, x, y, z) {
const tile = new VectorTile(new Protobuf(buffer));
const features = [];
for (const layerName in tile.layers) {
const vectorTileLayer = tile.layers[layerName];
for (let i = 0; i < vectorTileLayer.length; i++) {
const vectorTileFeature = vectorTileLayer.feature(i);
const feature = vectorTileFeature.toGeoJSON(x, y, z);
features.push(feature);
}
return features;
}
return features;
}

export const App = ({viewport}) => {

const layer = new TileLayer({
stroked: false,
getLineColor: [192, 192, 192],
Expand All @@ -41,48 +42,53 @@ export const App = ({viewport}) => {

## Properties

Inherits from all [Base Layer](/docs/api-reference/layer.md) properties, along with `renderSubLayer`, `getTileData`, `onGetTileDataError`, `onDataLoaded`, `maxZoom`, `minZoom` and `maxCacheSize`.
Inherits from all [Base Layer](/docs/api-reference/layer.md) properties, along with the following props.

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

The maximum zoom level of the tiles from consumers' data. If provided, and the current map zoom level is greater than `maxZoom`, we will fetch data at `maxZoom` instead of the current zoom level.

- Default: `null`

##### `minZoom` (Number)
##### `minZoom` (Number, optional)

The minimum zoom level of the tiles from consumers' data. If provided, and the current map zoom level is smaller than `minZoom`, we will fetch data at `minZoom` instead of the current zoom level.

- Default: `null`
- Default: 0

##### `maxCacheSize` (Number)
##### `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 constant 5 (5 is picked because it's a common zoom range).

- Default: `null`

### Render Options

##### `onDataLoaded` (Function)
##### `onViewportLoaded` (Function, optional)

`onDataLoaded` is a function that is called when all tiles in the current viewport are loaded. Data in the viewport is passed in as an array to this callback function.
`onViewportLoaded` is a function that is called when all tiles in the current viewport are loaded. Data in the viewport is passed in as an array to this callback function.

- Default: `onDataLoaded: (data) => null`
- Default: `onViewportLoaded: (data) => null`

##### `getTileData` (Function)
##### `getTileData` (Function, optional)

`getTileData` is a function that takes `{x, y, z}` as parameters, and returns a promise, which resolves to data in tile z-x-y.

- Default: `getTileData: ({x, y, z}) => Promise.resolve(null)`

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

`onGetTileDataError` called when a tile failed to load.
`onTileError` called when a tile failed to load.

- Default: `(err) => console.error(err)`

##### `renderSubLayers` (Function)
##### `renderSubLayers` (Function, optional))

Renders a sub-layer with the `data` prop being the resolved value of `getTileData`, and other props that are passed in the `TileLayer`

- Default: `props => new GeoJsonLayer(props)`

# Source

[modules/geo-layers/src/tile-layer](https://github.com/uber/deck.gl/tree/master/modules/geo-layers/src/tile-layer)

22 changes: 11 additions & 11 deletions modules/geo-layers/src/tile-layer/tile-layer.js
Expand Up @@ -3,22 +3,22 @@ import {GeoJsonLayer} from '@deck.gl/layers';
import TileCache from './utils/tile-cache';

const defaultProps = {
renderSubLayers: props => new GeoJsonLayer(props),
getTileData: ({x, y, z}) => Promise.resolve(null),
onDataLoaded: () => {},
renderSubLayers: {type: 'function', value: props => new GeoJsonLayer(props)},
getTileData: {type: 'function', value: ({x, y, z}) => Promise.resolve(null)},
onViewportLoaded: {type: 'function', value: () => {}},
// eslint-disable-next-line
onGetTileDataError: err => console.error(err),
onTileError: {type: 'function', value: err => console.error(err)},
maxZoom: null,
minZoom: null,
minZoom: 0,
maxCacheSize: null
};

export default class TileLayer extends CompositeLayer {
initializeState() {
const {maxZoom, minZoom, getTileData, onGetTileDataError} = this.props;
const {maxZoom, minZoom, getTileData, onTileError} = this.props;
this.state = {
tiles: [],
tileCache: new TileCache({getTileData, maxZoom, minZoom, onGetTileDataError}),
tileCache: new TileCache({getTileData, maxZoom, minZoom, onTileError}),
isLoaded: false
};
}
Expand All @@ -28,7 +28,7 @@ export default class TileLayer extends CompositeLayer {
}

updateState({props, oldProps, context, changeFlags}) {
const {onDataLoaded, onGetTileDataError} = props;
const {onViewportLoaded, onTileError} = props;
if (
changeFlags.updateTriggersChanged &&
(changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getTileData)
Expand All @@ -41,7 +41,7 @@ export default class TileLayer extends CompositeLayer {
maxSize: maxCacheSize,
maxZoom,
minZoom,
onGetTileDataError
onTileError
})
});
}
Expand All @@ -56,10 +56,10 @@ export default class TileLayer extends CompositeLayer {
if (!allCurrTilesLoaded) {
Promise.all(currTiles.map(tile => tile.data)).then(() => {
this.setState({isLoaded: true});
onDataLoaded(currTiles.filter(tile => tile._data).map(tile => tile._data));
onViewportLoaded(currTiles.filter(tile => tile._data).map(tile => tile._data));
});
} else {
onDataLoaded(currTiles.filter(tile => tile._data).map(tile => tile._data));
onViewportLoaded(currTiles.filter(tile => tile._data).map(tile => tile._data));
}
});
}
Expand Down
6 changes: 3 additions & 3 deletions modules/geo-layers/src/tile-layer/utils/tile-cache.js
Expand Up @@ -11,11 +11,11 @@ export default class TileCache {
* Takes in a function that returns tile data, a cache size, and a max and a min zoom level.
* Cache size defaults to 5 * number of tiles in the current viewport
*/
constructor({getTileData, maxSize, maxZoom, minZoom, onGetTileDataError}) {
constructor({getTileData, maxSize, maxZoom, minZoom, onTileError}) {
// TODO: Instead of hardcode size, we should calculate how much memory left
this._getTileData = getTileData;
this._maxSize = maxSize;
this.onGetTileDataError = onGetTileDataError;
this.onTileError = onTileError;

// Maps tile id in string {z}-{x}-{y} to a Tile object
this._cache = new Map();
Expand Down Expand Up @@ -67,7 +67,7 @@ export default class TileCache {
x,
y,
z,
onGetTileDataError: this.onGetTileDataError
onTileError: this.onTileError
});
}
const tileId = this._getTileId(x, y, z);
Expand Down
6 changes: 3 additions & 3 deletions modules/geo-layers/src/tile-layer/utils/tile.js
@@ -1,5 +1,5 @@
export default class Tile {
constructor({getTileData, x, y, z, onGetTileDataError}) {
constructor({getTileData, x, y, z, onTileError}) {
this.x = x;
this.y = y;
this.z = z;
Expand All @@ -8,7 +8,7 @@ export default class Tile {
this._data = null;
this._isLoaded = false;
this._loader = this._loadData();
this.onGetTileDataError = onGetTileDataError;
this.onTileError = onTileError;
}

get data() {
Expand Down Expand Up @@ -36,7 +36,7 @@ export default class Tile {
})
.catch(err => {
this._isLoaded = true;
this.onGetTileDataError(err);
this.onTileError(err);
});
}

Expand Down
85 changes: 62 additions & 23 deletions test/apps/mapbox-tile/app.js
@@ -1,9 +1,9 @@
/* global document fetch window */
/* eslint-disable no-console */
import React, {Component} from 'react';
import React, {PureComponent} from 'react';
import {render} from 'react-dom';
import DeckGL from 'deck.gl';
import {TileLayer} from '@deck.gl/experimental-layers';
import {TileLayer} from '@deck.gl/geo-layers';

import {decodeTile} from './utils/decode';

Expand Down Expand Up @@ -58,31 +58,70 @@ const MAP_LAYER_STYLES = {
opacity: 1
};

class Root extends Component {
getTileData({x, y, z}) {
const mapSource = `https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/${z}/${x}/${y}.vector.pbf?access_token=${MAPBOX_TOKEN}`;
return fetch(mapSource)
.then(response => {
return response.arrayBuffer();
})
.then(buffer => {
return decodeTile(x, y, z, buffer);
});
function getTileData({x, y, z}) {
const mapSource = `https://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/${z}/${x}/${y}.vector.pbf?access_token=${MAPBOX_TOKEN}`;
return fetch(mapSource)
.then(response => {
return response.arrayBuffer();
})
.then(buffer => {
return decodeTile(x, y, z, buffer);
});
}

class Root extends PureComponent {
constructor(props) {
super(props);
this._onClick = this._onClick.bind(this);
this.state = {
clickedItem: null
};
}

_onClick(info) {
this.setState({clickedItem: info && info.object});
}

_renderClickedItem() {
const {clickedItem} = this.state;
if (!clickedItem || !clickedItem.properties) {
return null;
}

return (
<div
style={{
position: 'fixed',
zIndex: 9,
margin: '20px',
left: 0,
bottom: 0,
whiteSpace: 'nowrap',
pointerEvents: 'none'
}}
>
{JSON.stringify(clickedItem.properties)}
</div>
);
}

render() {
return (
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
layers={[
new TileLayer({
...MAP_LAYER_STYLES,
pickable: true,
getTileData: this.getTileData
})
]}
/>
<div>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
onLayerClick={this._onClick}
layers={[
new TileLayer({
...MAP_LAYER_STYLES,
pickable: true,
getTileData
})
]}
/>
{this._renderClickedItem()}
</div>
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/apps/mapbox-tile/package.json
Expand Up @@ -5,7 +5,7 @@
},
"dependencies": {
"@mapbox/vector-tile": "^1.3.1",
"deck.gl": "^6.0.0-beta",
"deck.gl": "^7.0.0-alpha",
"pbf": "^3.1.0",
"react": "^16.0.0",
"react-dom": "^16.0.0"
Expand Down
1 change: 1 addition & 0 deletions test/modules/geo-layers/index.js
Expand Up @@ -31,4 +31,5 @@ test('Top-level imports', t => {
});

import './tile-layer/tile-cache.spec';
import './tile-layer/tile-layer.spec';
import './s2-layer.spec';

0 comments on commit ffee0e8

Please sign in to comment.