modules/layers/src/geojson-layer/geojson-layer.js (272 lines of code) (raw):
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import {CompositeLayer, log} from '@deck.gl/core';
import ScatterplotLayer from '../scatterplot-layer/scatterplot-layer';
import PathLayer from '../path-layer/path-layer';
// Use primitive layer to avoid "Composite Composite" layers for now
import SolidPolygonLayer from '../solid-polygon-layer/solid-polygon-layer';
import {replaceInRange} from '../utils';
import {getGeojsonFeatures, separateGeojsonFeatures} from './geojson';
const defaultLineColor = [0, 0, 0, 255];
const defaultFillColor = [0, 0, 0, 255];
const defaultProps = {
stroked: true,
filled: true,
extruded: false,
wireframe: false,
lineWidthUnits: 'meters',
lineWidthScale: 1,
lineWidthMinPixels: 0,
lineWidthMaxPixels: Number.MAX_SAFE_INTEGER,
lineJointRounded: false,
lineMiterLimit: 4,
elevationScale: 1,
pointRadiusUnits: 'meters',
pointRadiusScale: 1,
pointRadiusMinPixels: 0, // min point radius in pixels
pointRadiusMaxPixels: Number.MAX_SAFE_INTEGER, // max point radius in pixels
// Line and polygon outline color
getLineColor: {type: 'accessor', value: defaultLineColor},
// Point and polygon fill color
getFillColor: {type: 'accessor', value: defaultFillColor},
// Point radius
getRadius: {type: 'accessor', value: 1},
// Line and polygon outline accessors
getLineWidth: {type: 'accessor', value: 1},
// Polygon extrusion accessor
getElevation: {type: 'accessor', value: 1000},
// Optional material for 'lighting' shader module
material: true
};
function getCoordinates(f) {
return f.geometry.coordinates;
}
export default class GeoJsonLayer extends CompositeLayer {
initializeState() {
this.state = {
features: {}
};
if (this.props.getLineDashArray) {
log.removed('getLineDashArray', 'PathStyleExtension')();
}
}
updateState({props, changeFlags}) {
if (!changeFlags.dataChanged) {
return;
}
const features = getGeojsonFeatures(props.data);
const wrapFeature = this.getSubLayerRow.bind(this);
if (Array.isArray(changeFlags.dataChanged)) {
const oldFeatures = this.state.features;
const newFeatures = {};
const featuresDiff = {};
for (const key in oldFeatures) {
newFeatures[key] = oldFeatures[key].slice();
featuresDiff[key] = [];
}
for (const dataRange of changeFlags.dataChanged) {
const partialFeatures = separateGeojsonFeatures(features, wrapFeature, dataRange);
for (const key in oldFeatures) {
featuresDiff[key].push(
replaceInRange({
data: newFeatures[key],
getIndex: f => f.__source.index,
dataRange,
replace: partialFeatures[key]
})
);
}
}
this.setState({features: newFeatures, featuresDiff});
} else {
this.setState({
features: separateGeojsonFeatures(features, wrapFeature),
featuresDiff: {}
});
}
}
/* eslint-disable complexity */
renderLayers() {
const {features, featuresDiff} = this.state;
const {pointFeatures, lineFeatures, polygonFeatures, polygonOutlineFeatures} = features;
// Layer composition props
const {stroked, filled, extruded, wireframe, material, transitions} = this.props;
// Rendering props underlying layer
const {
lineWidthUnits,
lineWidthScale,
lineWidthMinPixels,
lineWidthMaxPixels,
lineJointRounded,
lineMiterLimit,
pointRadiusUnits,
pointRadiusScale,
pointRadiusMinPixels,
pointRadiusMaxPixels,
elevationScale,
lineDashJustified
} = this.props;
// Accessor props for underlying layers
const {
getLineColor,
getFillColor,
getRadius,
getLineWidth,
getLineDashArray,
getElevation,
updateTriggers
} = this.props;
const PolygonFillLayer = this.getSubLayerClass('polygons-fill', SolidPolygonLayer);
const PolygonStrokeLayer = this.getSubLayerClass('polygons-stroke', PathLayer);
const LineStringsLayer = this.getSubLayerClass('line-strings', PathLayer);
const PointsLayer = this.getSubLayerClass('points', ScatterplotLayer);
// Filled Polygon Layer
const polygonFillLayer =
this.shouldRenderSubLayer('polygons-fill', polygonFeatures) &&
new PolygonFillLayer(
{
_dataDiff: featuresDiff.polygonFeatures && (() => featuresDiff.polygonFeatures),
extruded,
elevationScale,
filled,
wireframe,
material,
getElevation: this.getSubLayerAccessor(getElevation),
getFillColor: this.getSubLayerAccessor(getFillColor),
getLineColor: this.getSubLayerAccessor(getLineColor),
transitions: transitions && {
getPolygon: transitions.geometry,
getElevation: transitions.getElevation,
getFillColor: transitions.getFillColor,
getLineColor: transitions.getLineColor
}
},
this.getSubLayerProps({
id: 'polygons-fill',
updateTriggers: {
getElevation: updateTriggers.getElevation,
getFillColor: updateTriggers.getFillColor,
getLineColor: updateTriggers.getLineColor
}
}),
{
data: polygonFeatures,
getPolygon: getCoordinates
}
);
const polygonLineLayer =
!extruded &&
stroked &&
this.shouldRenderSubLayer('polygons-stroke', polygonOutlineFeatures) &&
new PolygonStrokeLayer(
{
_dataDiff:
featuresDiff.polygonOutlineFeatures && (() => featuresDiff.polygonOutlineFeatures),
widthUnits: lineWidthUnits,
widthScale: lineWidthScale,
widthMinPixels: lineWidthMinPixels,
widthMaxPixels: lineWidthMaxPixels,
rounded: lineJointRounded,
miterLimit: lineMiterLimit,
dashJustified: lineDashJustified,
getColor: this.getSubLayerAccessor(getLineColor),
getWidth: this.getSubLayerAccessor(getLineWidth),
getDashArray: this.getSubLayerAccessor(getLineDashArray),
transitions: transitions && {
getPath: transitions.geometry,
getColor: transitions.getLineColor,
getWidth: transitions.getLineWidth
}
},
this.getSubLayerProps({
id: 'polygons-stroke',
updateTriggers: {
getColor: updateTriggers.getLineColor,
getWidth: updateTriggers.getLineWidth,
getDashArray: updateTriggers.getLineDashArray
}
}),
{
data: polygonOutlineFeatures,
getPath: getCoordinates
}
);
const pathLayer =
this.shouldRenderSubLayer('linestrings', lineFeatures) &&
new LineStringsLayer(
{
_dataDiff: featuresDiff.lineFeatures && (() => featuresDiff.lineFeatures),
widthUnits: lineWidthUnits,
widthScale: lineWidthScale,
widthMinPixels: lineWidthMinPixels,
widthMaxPixels: lineWidthMaxPixels,
rounded: lineJointRounded,
miterLimit: lineMiterLimit,
dashJustified: lineDashJustified,
getColor: this.getSubLayerAccessor(getLineColor),
getWidth: this.getSubLayerAccessor(getLineWidth),
getDashArray: this.getSubLayerAccessor(getLineDashArray),
transitions: transitions && {
getPath: transitions.geometry,
getColor: transitions.getLineColor,
getWidth: transitions.getLineWidth
}
},
this.getSubLayerProps({
id: 'line-strings',
updateTriggers: {
getColor: updateTriggers.getLineColor,
getWidth: updateTriggers.getLineWidth,
getDashArray: updateTriggers.getLineDashArray
}
}),
{
data: lineFeatures,
getPath: getCoordinates
}
);
const pointLayer =
this.shouldRenderSubLayer('points', pointFeatures) &&
new PointsLayer(
{
_dataDiff: featuresDiff.pointFeatures && (() => featuresDiff.pointFeatures),
stroked,
filled,
radiusUnits: pointRadiusUnits,
radiusScale: pointRadiusScale,
radiusMinPixels: pointRadiusMinPixels,
radiusMaxPixels: pointRadiusMaxPixels,
lineWidthUnits,
lineWidthScale,
lineWidthMinPixels,
lineWidthMaxPixels,
getFillColor: this.getSubLayerAccessor(getFillColor),
getLineColor: this.getSubLayerAccessor(getLineColor),
getRadius: this.getSubLayerAccessor(getRadius),
getLineWidth: this.getSubLayerAccessor(getLineWidth),
transitions: transitions && {
getPosition: transitions.geometry,
getFillColor: transitions.getFillColor,
getLineColor: transitions.getLineColor,
getRadius: transitions.getRadius,
getLineWidth: transitions.getLineWidth
}
},
this.getSubLayerProps({
id: 'points',
updateTriggers: {
getFillColor: updateTriggers.getFillColor,
getLineColor: updateTriggers.getLineColor,
getRadius: updateTriggers.getRadius,
getLineWidth: updateTriggers.getLineWidth
}
}),
{
data: pointFeatures,
getPosition: getCoordinates,
highlightedObjectIndex: this._getHighlightedIndex(pointFeatures)
}
);
return [
// If not extruded: flat fill layer is drawn below outlines
!extruded && polygonFillLayer,
polygonLineLayer,
pathLayer,
pointLayer,
// If extruded: draw fill layer last for correct blending behavior
extruded && polygonFillLayer
];
}
/* eslint-enable complexity */
_getHighlightedIndex(data) {
const {highlightedObjectIndex} = this.props;
return Number.isFinite(highlightedObjectIndex)
? data.findIndex(d => d.__source.index === highlightedObjectIndex)
: null;
}
}
GeoJsonLayer.layerName = 'GeoJsonLayer';
GeoJsonLayer.defaultProps = defaultProps;