Skip to content

Commit

Permalink
Merge 1121392 into 6504586
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Jan 25, 2020
2 parents 6504586 + 1121392 commit 6aedfd1
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 5 deletions.
8 changes: 8 additions & 0 deletions examples/experimental/h3-grid/README.md
@@ -0,0 +1,8 @@
This is an example that visualizes the [H3 Geospatial Index System](https://uber.github.io/h3/#/documentation/core-library/overview).

### Usage
Copy the content of this folder to your project. Run
```
npm install
npm start
```
25 changes: 25 additions & 0 deletions examples/experimental/h3-grid/package.json
@@ -0,0 +1,25 @@
{
"name": "h3-grid",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"start-local": "webpack-dev-server --env.local --progress --hot --open",
"start": "webpack-dev-server --progress --hot --open",
"build": "webpack -p --output-path dist"
},
"dependencies": {
"deck.gl": "^8.1.0-alpha.1",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"react-map-gl": "^5.1.0"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"html-webpack-plugin": "^3.0.7",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.1"
}
}
115 changes: 115 additions & 0 deletions examples/experimental/h3-grid/src/app.js
@@ -0,0 +1,115 @@
import React, {Component, Fragment} from 'react';
import {render} from 'react-dom';
import DeckGL from '@deck.gl/react';
import {MapView} from '@deck.gl/core';

import {StaticMap} from 'react-map-gl';

import H3GridLayer from './h3-grid-layer';
import {getMinZoom} from './h3-utils';

// Set your mapbox token here
const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line

const CONTROL_PANEL_STYLE = {
position: 'fixed',
top: 20,
left: 20,
padding: 20,
fontSize: 13,
background: '#fff'
};

// `repeat` will draw multiple copies of the map at low zoom leveles
const MAP_VIEW = new MapView({repeat: true});
// hexagon per tile at minZoom
const MAX_HEX_COUNT = 1000;

export default class App extends Component {
constructor(props) {
super(props);

this.state = {
initialViewState: {
longitude: 0,
latitude: 0,
zoom: 2,
pitch: 0,
bearing: 0
},
resolution: 1
};

this._onResolutionChange = this._onResolutionChange.bind(this);
this._onViewStateChange = this._onViewStateChange.bind(this);

this._viewState = this.state.initialViewState;
}

_onViewStateChange({viewState}) {
// Just save a copy of the viewState, no need to trigger rerender
this._viewState = viewState;
}

_onResolutionChange(evt) {
const resolution = Number(evt.target.value);
const minZoom = getMinZoom(resolution, MAX_HEX_COUNT);
const initialViewState = {
...this._viewState,
zoom: Math.max(this._viewState.zoom, minZoom),
minZoom
};

this.setState({initialViewState, resolution});
}

render() {
const {resolution, initialViewState} = this.state;
const layer = new H3GridLayer({
// highPrecision: true,
resolution,
maxHexCount: MAX_HEX_COUNT,
filled: true,
extruded: false,
stroked: true,
lineWidthUnits: 'pixels',
getLineWidth: 2,
getFillColor: [0, 0, 0, 1],
pickable: true,
autoHighlight: true
});

return (
<Fragment>
<DeckGL
initialViewState={initialViewState}
controller={true}
views={MAP_VIEW}
layers={[layer]}
onViewStateChange={this._onViewStateChange}
getTooltip={info => info.object}
>
<StaticMap
mapStyle="mapbox://styles/mapbox/light-v9"
mapboxApiAccessToken={MAPBOX_TOKEN}
/>
</DeckGL>
<div style={CONTROL_PANEL_STYLE}>
<div>Resolution: {resolution}</div>
<input
type="range"
min="0"
max="15"
step="1"
value={resolution}
onInput={this._onResolutionChange}
/>
</div>
</Fragment>
);
}
}

/* global document */
document.body.style.overflow = 'hidden';
render(<App />, document.body.appendChild(document.createElement('div')));
50 changes: 50 additions & 0 deletions examples/experimental/h3-grid/src/h3-grid-layer.js
@@ -0,0 +1,50 @@
import {CompositeLayer} from '@deck.gl/core';
import {TileLayer, H3HexagonLayer} from '@deck.gl/geo-layers';

import {getHexagonsInBoundingBox, getTileInfo, getMinZoom} from './h3-utils';

const defaultProps = {
...H3HexagonLayer.defaultProps,
// H3 resolution
resolution: {type: 'number', min: 0, max: 15, value: 5},
// hexagon per tile at minZoom
maxHexCount: {type: 'number', value: 1000}
};

export default class H3GridLayer extends CompositeLayer {
renderLayers() {
const {resolution, maxHexCount} = this.props;
const minZoom = getMinZoom(resolution, maxHexCount);

return new TileLayer(this.props, {
minZoom,
maxZoom: minZoom,
getTileData: tile => getHexagonsInBoundingBox(tile.bbox, resolution),
renderSubLayers: props => {
const {tile} = props;
getTileInfo(tile, resolution);

return new H3HexagonLayer(props, {
getHexagon: d => d,
highPrecision: tile.hasMultipleFaces,
centerHexagon: tile.centerHexagon
// uncomment to debug
// getFillColor: getTileColor(tile)
});
},
updateTriggers: {
getTileData: resolution
}
});
}
}

H3GridLayer.layerName = 'H3GridLayer';
H3GridLayer.defaultProps = defaultProps;

// For debug. Generate some arbitrary color that differentiates neighboring tiles
// function getTileColor({x, y, z}) {
// const n = x + y;
// const i = (n * (n - 1)) / 2 + (n % 2 ? y : x) + n + 1;
// return [(x * 107) % 255, (y * 107) % 255, Math.sin(i) * 128 + 128, 80];
// }
60 changes: 60 additions & 0 deletions examples/experimental/h3-grid/src/h3-utils.js
@@ -0,0 +1,60 @@
import {polyfill, getRes0Indexes, h3GetFaces, geoToH3} from 'h3-js';

// Number of hexagons at resolution 10 in tile x:497 y:505 z:10
// This tile is close to the equator and includes a pentagon 8a7400000007fff
// which makes it denser than other tiles
const HEX_COUNT_ZOOM_10_RES_10 = 166283;
// size multiplier when zoom increases by 1
const ZOOM_FACTOR = 1 / 4;
// size multiplier when resolution increases by 1
// h3.numHexagons(n + 1) / h3.numHexagons(n)
const RES_FACTOR = 7;

export function getHexagonsInBoundingBox({west, north, east, south}, resolution) {
if (resolution === 0) {
return getRes0Indexes();
}
if (east - west > 180) {
// This is a known issue in h3-js: polyfill does not work correctly
// when longitude span is larger than 180 degrees.
return getHexagonsInBoundingBox({west, north, east: 0, south}, resolution).concat(
getHexagonsInBoundingBox({west: 0, north, east, south}, resolution)
);
}

return polyfill(
[[[west, north], [west, south], [east, south], [east, north], [west, north]]],
resolution,
true
);
}

export function getTileInfo(tile, resolution) {
if (!tile.centerHexagon) {
const {west, north, east, south} = tile.bbox;
const faces = [];

const NW = geoToH3(north, west, resolution);
faces.push(...h3GetFaces(NW));

const NE = geoToH3(north, east, resolution);
faces.push(...h3GetFaces(NE));

const SW = geoToH3(south, west, resolution);
faces.push(...h3GetFaces(SW));

const SE = geoToH3(south, east, resolution);
faces.push(...h3GetFaces(SE));

tile.hasMultipleFaces = new Set(faces).size > 1;
tile.centerHexagon = geoToH3((north + south) / 2, (west + east) / 2, resolution);
}

return tile;
}

export function getMinZoom(resolution, maxHexCount) {
const hexCountZoom10 = HEX_COUNT_ZOOM_10_RES_10 * Math.pow(RES_FACTOR, resolution - 10);
const maxHexCountZoom = 10 + Math.log2(maxHexCount / hexCountZoom10) / Math.log2(ZOOM_FACTOR);
return Math.max(0, Math.floor(maxHexCountZoom));
}
45 changes: 45 additions & 0 deletions examples/experimental/h3-grid/webpack.config.js
@@ -0,0 +1,45 @@
// NOTE: To use this example standalone (e.g. outside of deck.gl repo)
// delete the local development overrides at the bottom of this file

// avoid destructuring for older Node version support
const resolve = require('path').resolve;
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const CONFIG = {
mode: 'development',

entry: {
app: resolve('./src/app.js')
},

module: {
rules: [
{
// Transpile ES6 to ES5 with babel
// Remove if your app does not use JSX or you don't need to support old browsers
test: /\.js$/,
loader: 'babel-loader',
exclude: [/node_modules/],
options: {
presets: ['@babel/preset-react']
}
}
]
},

resolve: {
alias: {
// From mapbox-gl-js README. Required for non-browserify bundlers (e.g. webpack):
'mapbox-gl$': resolve('./node_modules/mapbox-gl/dist/mapbox-gl.js')
}
},

plugins: [
new webpack.EnvironmentPlugin(['MapboxAccessToken']),
new HtmlWebpackPlugin({title: 'deck.gl example'})
]
};

// This line enables bundling against src in this repo rather than installed deck.gl module
module.exports = env => (env ? require('../../webpack.config.local')(CONFIG)(env) : CONFIG);
4 changes: 3 additions & 1 deletion modules/geo-layers/src/h3-layers/h3-hexagon-layer.js
Expand Up @@ -92,6 +92,7 @@ function mergeTriggers(getHexagon, coverage) {
const defaultProps = Object.assign({}, PolygonLayer.defaultProps, {
highPrecision: false,
coverage: {type: 'number', min: 0, max: 1, value: 1},
centerHexagon: null,
getHexagon: {type: 'accessor', value: x => x.hexagon},
extruded: true
});
Expand Down Expand Up @@ -158,7 +159,8 @@ export default class H3HexagonLayer extends CompositeLayer {
if (resolution < 0) {
return;
}
const hex = geoToH3(viewport.latitude, viewport.longitude, resolution);
const hex =
this.props.centerHexagon || geoToH3(viewport.latitude, viewport.longitude, resolution);
if (centerHex === hex) {
return;
}
Expand Down
11 changes: 7 additions & 4 deletions modules/layers/src/base-tile-layer/base-tile-layer.js
Expand Up @@ -35,11 +35,11 @@ export default class BaseTileLayer extends CompositeLayer {

updateState({props, oldProps, context, changeFlags}) {
let {tileCache} = this.state;
if (
const createNewCache =
!tileCache ||
(changeFlags.updateTriggersChanged &&
(changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getTileData))
) {
(changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getTileData));
if (createNewCache) {
const {
getTileData,
maxZoom,
Expand Down Expand Up @@ -70,7 +70,10 @@ export default class BaseTileLayer extends CompositeLayer {
}

const {viewport} = context;
if (changeFlags.viewportChanged && viewport.id !== 'DEFAULT-INITIAL-VIEWPORT') {
if (
createNewCache ||
(changeFlags.viewportChanged && viewport.id !== 'DEFAULT-INITIAL-VIEWPORT')
) {
const z = this.getLayerZoomLevel();
tileCache.update(viewport);
// The tiles that should be displayed at this zoom level
Expand Down

0 comments on commit 6aedfd1

Please sign in to comment.