Reputation: 11
I am working on a React application which should be used to visualize different geographical information, mostly described in GeoJSON format. In more detail, I am currently developing a map component which should be included in the overall frontend part later on.
For this purpose I started to started evaluating Deck.gl by using their code for the GeoJSON example app (https://github.com/uber/deck.gl/tree/master/examples/website/geojson). This worked perfectly fine. So I started adding some more layers for additional information:
This also worked perfectly fine. So I wanted to encapsulate this solution properly so it could be easily used as a child component. Therefore I used create-react-app for scaffolding and started migrating the code.
The resulting code structure looks like this (only relevant parts):
src
data
index.js
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>deck.gl Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
font-family: sans-serif;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.tooltip {
pointer-events: none;
position: absolute;
z-index: 9;
font-size: 12px;
padding: 8px;
background: #000;
color: #fff;
min-width: 160px;
max-height: 240px;
overflow-y: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>
index.js:
import React from "react";
import ReactDOM from "react-dom";
import Map from "./components/Map";
ReactDOM.render(<Map />, document.querySelector("#app"));
Map.js:
import React, { Component } from "react";
import DeckGL, {
GeoJsonLayer,
TileLayer,
BitmapLayer,
IconLayer
} from "deck.gl";
import myJson from "../data/geodata.json";
import iconLocations from "../data/icons.json";
import { SelectionLayer, SELECTION_TYPE } from "nebula.gl";
const INITIAL_VIEW_STATE = {
latitude: SOME_LAT,
longitude: SOME_LONG,
zoom: 14,
maxZoom: 19,
pitch: 0,
bearing: 0
};
export default class Map extends Component {
state = {
hoveredObject: null
};
_onHover = ({ x, y, object }) => {
this.setState({ x, y, hoveredObject: object });
};
_renderLayers() {
const {
iconMapping = "../data/mapping.json",
iconAtlas = "../data/atlas.png",
viewState
} = this.props;
let data = myJson;
data.features = data.features.filter(
feature => feature.geometry.coordinates.length > 0
);
let size = viewState ? Math.min(Math.pow(1.5, viewState.zoom - 10), 1) : 1;
return [
new TileLayer({
opacity: 1,
minZoom: 0,
maxZoom: 30,
renderSubLayers: props => {
const {x, y, z, bbox} = props.tile;
const {west, south, east, north} = bbox;
const base = 1 + ((x + y) % 4);
return new BitmapLayer(props, {
image: `http://${base}.base.maps.cit.api.here.com/maptile/2.1/maptile/newest/normal.day/${z}/${x}/${y}/512/png`,
bounds: [west, south, east, north]
});
}
}),
new GeoJsonLayer({
id: "geojson",
data,
opacity: 1,
stroked: false,
lineWidthMinPixels: 1,
getLineColor: [255, 0, 0],
pickable: true,
autoHighlight: true,
highlightColor: [0, 100, 255, 80],
onHover: this._onHover
}),
new SelectionLayer({
id: "selection",
selectionType: SELECTION_TYPE.RECTANGLE,
onSelect: ({ pickingInfos }) => {
this.setState({
selectedFeatureIndexes: pickingInfos.map(pi => pi.index)
});
console.log(pickingInfos);
},
layerIds: ["geojson"],
getTentativeFillColor: () => [255, 0, 255, 100],
getTentativeLineColor: () => [0, 0, 255, 255],
getTentativeLineDashArray: () => [0, 0],
lineWidthMinPixels: 3
}),
new IconLayer({
id: "icon",
data: iconLocations,
wrapLongitude: true,
getPosition: d => d.coordinates,
iconAtlas,
iconMapping,
getIcon: d => d.type,
getSize: size,
sizeScale: 50
})
];
}
_renderTooltip = () => {
const { x, y, hoveredObject } = this.state;
return (
hoveredObject && (
// some JSX for tooltip ...
)
);
};
render() {
const { viewState, controller = true } = this.props;
return (
<DeckGL
layers={this._renderLayers()}
initialViewState={INITIAL_VIEW_STATE}
viewState={viewState}
controller={controller}
>
{this._renderTooltip}
</DeckGL>
);
}
}
The code used in Map.js is actually exactly the same as used when expanding the example code (which worked as intented), only the way it would get rendered changed a little. I would expect it to work the same way, but I get the following output: https://i.sstatic.net/TW6nm.jpg
If I remove the TileLayer + BitmapLayer, the first error will disappear and at least the GeoJSON data will be correctly displayed, just without the base map. The IconLayer does also not work, while the SelectionLayer causes no problems, just as the GeojsonLayer.
I am quite new to React and Deck.gl, so is there anything I forgot to migrate the example code properly?
UPDATE:
I refactored the code a little and got the icons working. I also got rid of the error message when using the TileLayer by removing the propagation of props to the BitmapLayer (props.data is null, which seems to be no problem when used in the example code, but somehow causes problems here), but the bitmaps are not displayed at all, even though the image link and the bounds are correct.
import React from "react";
import DeckGL from "@deck.gl/react";
import { GeoJsonLayer, IconLayer, BitmapLayer } from "@deck.gl/layers";
import { TileLayer } from "@deck.gl/geo-layers";
import { SelectionLayer, SELECTION_TYPE } from "nebula.gl";
// test data
import jsonTestFile from "../data/testJson.json";
import signLocations from "../data/signs.json";
import iconAtlas from "../data/trafficSignAtlas.png";
import iconMapping from "../data/trafficSignMapping.json";
// Initial viewport settings
const initialViewState = {
latitude: 48.872578,
longitude: 11.431032,
zoom: 14,
pitch: 0,
bearing: 0
};
const LayerEnum = Object.freeze({
GEOJSON: 1,
TILE: 2,
SELECTION: 3,
ICON: 4
});
export default class Map extends React.Component {
state = {
selectedFeatureIndexes: []
};
renderLayer = ({ layerType, options }) => {
switch (layerType) {
case LayerEnum.GEOJSON:
return new GeoJsonLayer({
id: "geojson",
opacity: 1,
stroked: false,
lineWidthMinPixels: 1,
getLineColor: [255, 0, 0],
pickable: true,
autoHighlight: true,
highlightColor: [0, 100, 255, 80],
...options
});
case LayerEnum.TILE:
return new TileLayer({
opacity: 1,
// https://wiki.openstreetmap.org/wiki/Zoom_levels
minZoom: 0,
maxZoom: 30,
renderSubLayers: ({ id, tile }) => {
const { x, y, z, bbox } = tile;
const { west, south, east, north } = bbox;
const base = 1 + ((x + y) % 4);
console.log(tile);
return new BitmapLayer({
id,
image: `http://${base}.base.maps.cit.api.here.com/maptile/2.1/maptile/newest/normal.day/${z}/${x}/${y}/512/png`,
bounds: [west, south, east, north]
});
}
});
case LayerEnum.SELECTION:
return new SelectionLayer({
id: "selection",
selectionType: SELECTION_TYPE.RECTANGLE,
onSelect: ({ pickingInfos }) => {
this.setState({
selectedFeatureIndexes: pickingInfos.map(pi => pi.index)
});
console.log(pickingInfos);
},
layerIds: ["geojson"],
getTentativeFillColor: () => [255, 0, 255, 100],
getTentativeLineColor: () => [0, 0, 255, 255],
getTentativeLineDashArray: () => [0, 0],
lineWidthMinPixels: 3,
...options
});
case LayerEnum.ICON:
return new IconLayer({
id: "icon",
wrapLongitude: true,
getPosition: d => d.coordinates,
getIcon: d => d.type,
getSize: 1,
sizeScale: 50,
...options
});
default:
console.error("Unknown errer type detected!");
return null;
}
};
renderLayers = layers => {
return layers.map(this.renderLayer);
};
render() {
// preprocess test data
let data = jsonTestFile;
data.features = data.features.filter(
feature => feature.geometry.coordinates.length > 0
);
const layers = this.renderLayers([
{
layerType: LayerEnum.GEOJSON,
options: { data }
},
{
layerType: LayerEnum.SELECTION,
options: {}
},
{
layerType: LayerEnum.ICON,
options: {
data: signLocations,
iconAtlas,
iconMapping
}
},
{
layerType: LayerEnum.TILE,
options: {}
}
]);
const { viewState, controller = true } = this.props;
return (
<DeckGL
initialViewState={initialViewState}
viewState={viewState}
controller={controller}
layers={layers}
/>
);
}
}
Upvotes: 1
Views: 643
Reputation: 11
As it turns out it seems to be a dependency version problem. The example code comes with [email protected]
and I was using [email protected]
. Starting at version 7.2.0 this code does not work anymore, 7.1.11 seems to be the latest possible version for this usecase.
Upvotes: 0