kepler.gl.geoiq
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
1,004 lines (914 loc) • 30.8 kB
JavaScript
// Copyright (c) 2023 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.
// libraries
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import MapboxGLMap from 'react-map-gl';
import DeckGL from 'deck.gl';
import {createSelector} from 'reselect';
import WebMercatorViewport from 'viewport-mercator-project';
import geoViewport from '@mapbox/geo-viewport';
import {
dashboardConnection,
removeDashboardConnection,
addDatasetSocket,
removeDatasetSocket,
listenDataUpdates
} from 'socket/api';
// import GL from 'luma.gl/constants';
// import {registerShaderModules, setParameters} from 'luma.gl';
// import pickingModule from 'shaderlib/picking-module';
// import brushingModule from 'shaderlib/brushing-module';
// import GeoJSON from 'geojson';
// components
import MapPopoverFactory from 'components/map/map-popover';
import MapControlFactory from 'components/map/map-control';
import {StyledMapContainer} from 'components/common/styled-components';
import Editor from './editor/editor';
// utils
import {generateMapboxLayers, updateMapboxLayers} from 'layers/mapbox-utils';
import {OVERLAY_TYPE} from 'layers/base-layer';
import {onWebGLInitialized, setLayerBlending} from 'utils/gl-utils';
import {transformRequest} from 'utils/map-style-utils/mapbox-utils';
// default-settings
import ThreeDBuildingLayer from 'deckgl-layers/3d-building-layer/3d-building-layer';
import {FILTER_TYPES} from 'utils/filter-utils';
import {LAYER_BLENDINGS} from 'constants/default-settings';
// import ThreeDBuildingLayer from '../deckgl-layers/3d-building-layer/3d-building-layer';
// import continuousColorLegend from 'react-vis/dist/legends/continuous-color-legend';
const MAP_STYLE = {
container: {
display: 'inline-block',
position: 'relative'
},
top: {
position: 'absolute',
top: '0px',
pointerEvents: 'none'
}
};
const MAPBOXGL_STYLE_UPDATE = 'style.load';
const MAPBOXGL_RENDER = 'render';
const TRANSITION_DURATION = 0;
MapContainerFactory.deps = [MapPopoverFactory, MapControlFactory];
export default function MapContainerFactory(MapPopover, MapControl) {
class MapContainer extends Component {
static propTypes = {
// required
datasets: PropTypes.object,
interactionConfig: PropTypes.object.isRequired,
layerBlending: PropTypes.string.isRequired,
layerOrder: PropTypes.arrayOf(PropTypes.any).isRequired,
layerData: PropTypes.arrayOf(PropTypes.any).isRequired,
layers: PropTypes.arrayOf(PropTypes.any).isRequired,
filters: PropTypes.arrayOf(PropTypes.any).isRequired,
mapState: PropTypes.object.isRequired,
uiState: PropTypes.object.isRequired,
mapStyle: PropTypes.object.isRequired,
mousePos: PropTypes.object.isRequired,
mapboxApiAccessToken: PropTypes.string.isRequired,
mapboxApiUrl: PropTypes.string,
visStateActions: PropTypes.object.isRequired,
mapStateActions: PropTypes.object.isRequired,
uiStateActions: PropTypes.object.isRequired,
// optional
readOnly: PropTypes.bool,
isExport: PropTypes.bool,
clicked: PropTypes.object,
hoverInfo: PropTypes.object,
mapLayers: PropTypes.object,
onMapToggleLayer: PropTypes.func,
onMapStyleLoaded: PropTypes.func,
onMapRender: PropTypes.func,
getMapboxRef: PropTypes.func,
index: PropTypes.number
};
static defaultProps = {
MapComponent: MapboxGLMap,
deckGlProps: {}
};
constructor(props) {
super(props);
this.state = {
nextMapState: null,
listenToSocket: false
};
this.previousLayers = {
// [layers.id]: mapboxLayerConfig
};
// dashboardConnection();
}
inDebounce = 0;
// componentDidMount() {
// }
componentWillUnmount() {
// unbind mapboxgl event listener
if (this._map) {
this._map.off(MAPBOXGL_STYLE_UPDATE);
this._map.off(MAPBOXGL_RENDER);
}
removeDashboardConnection(this.props);
}
componentWillReceiveProps(nextProps) {
const {
mapState,
layers,
visStateActions,
datasets,
auth,
project
} = nextProps;
let {listenToSocket} = this.state;
const datasetKeys = Object.keys(datasets);
const currentDatasetKeys = Object.keys(this.props.datasets);
if (currentDatasetKeys.length === 0 && datasetKeys.length > 0) {
listenToSocket = true;
}
if (currentDatasetKeys.length < datasetKeys.length) {
const filteredDatasetKeys = datasetKeys.filter(
dk => currentDatasetKeys.findIndex(cd => cd === dk) === -1
);
const datasetIdsToAdd = filteredDatasetKeys.reduce((accu, dk) => {
if (datasets[dk].isLiveDataset === true) {
accu.push(datasets[dk].datasetId);
}
return accu;
}, []);
addDatasetSocket(
{datasetIdsToAdd, listenToSocket, ...nextProps},
this.updateSocketLayer
);
// this.setState({listenToSocket: false});
// listenDataUpdates(this.updateSocketLayer);
} else if (currentDatasetKeys.length > datasetKeys.length) {
const filteredDatasetKeys = currentDatasetKeys.filter(
cd => datasetKeys.findIndex(dk => dk === cd) === -1
);
const datasetIdsToRemove = filteredDatasetKeys.reduce((accu, dk) => {
if (this.props.datasets[dk].isLiveDataset === true) {
accu.push(this.props.datasets[dk].datasetId);
}
return accu;
}, []);
removeDatasetSocket(datasetIdsToRemove, nextProps.auth);
}
// }
// console.log(
// 'before condition inside componentWillRecieveProps',
// updatingDataset
// );
// if (datasetKeys.length && updatingDataset === false) {
// // helps in updating datasets insi de a dashboad without checking updateTime
// datasetKeys.map(dk => {
// if (datasets[dk].isLiveDataset === true) {
// this.setState({updatingDataset: true});
// dashboardConnection(nextProps);
// }
// });
// }
// if (updatingDataset === true) {
// dashboardConnection(nextProps);
// this.setState({updatingDataset: false});
// }
if (mapState) {
this.setState({nextMapState: nextProps.mapState});
}
layers.map(layer => {
const {config} = layer;
const {
apiCallRequest,
legendApiCallRequest,
colorField,
dataId
} = config;
const {filters} = nextProps;
const currentFilters = this.props.filters;
if (currentFilters.length > filters.length) {
if (filters.length === 0) {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
filters.map(filter => {
if (filter.dataId.includes(dataId)) {
if (
layer.type === 'backendPoint' ||
layer.type === 'backendGeojson'
) {
visStateActions.layerConfigChange(layer, {
legendApiCallRequest: true
});
} else {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
}
});
}
if (filters.length && currentFilters.length) {
filters.map((f, i) => {
if (f.dataId.includes(dataId)) {
if (
(currentFilters[i] &&
JSON.stringify(f.value) !=
JSON.stringify(currentFilters[i].value)) ||
(currentFilters[i] &&
currentFilters[i].dataId.length !== f.dataId.length)
) {
if (
layer.type === 'backendPoint' ||
layer.type === 'backendGeojson'
) {
visStateActions.layerConfigChange(layer, {
legendApiCallRequest: true
});
} else {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
}
} else if (
currentFilters[i] &&
currentFilters[i].dataId.includes(dataId)
) {
if (
layer.type === 'backendPoint' ||
layer.type === 'backendGeojson'
) {
visStateActions.layerConfigChange(layer, {
legendApiCallRequest: true
});
} else {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
}
});
}
if (filters.length && currentFilters.length === 0) {
filters.map((f, i) => {
if (f.dataId.includes(dataId)) {
if (f.dataId.length) {
if (
layer.type === 'backendPoint' ||
layer.type === 'backendGeojson'
) {
visStateActions.layerConfigChange(layer, {
legendApiCallRequest: true
});
} else {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
}
}
});
}
if (
colorField &&
legendApiCallRequest &&
legendApiCallRequest === true
) {
const result = layer.axiosLegendAPICall(
datasets,
filters,
auth,
project
);
result.then(function(result) {
visStateActions.layerConfigChange(layer, {
legend: result,
apiCallRequest: true,
legendApiCallRequest: false
});
});
}
// if (
// layer.type === 'point' &&
// (!legendApiCallRequest || legendApiCallRequest === false)
// ) {
// nextProps.visStateActions.layerConfigChange(layer, {
// legendApiCallRequest: true
// });
// }
if (
layer.type === 'dynamic' ||
layer.type === 'geoHash' ||
layer.type === 'backendPoint' ||
layer.type === 'backendGeojson'
) {
if (
!_.isEqual(this.props.mapState, mapState)
// &&
// nextProps.layers
// nextProps.widget.config.bounds
) {
nextProps.visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
}
if (
layer &&
config &&
apiCallRequest === true
// columns[Object.keys(columns)[0]].fieldIdx !== -1
) {
visStateActions.layerConfigChange(layer, {
apiCallRequest: false,
apiCallLoader: true
});
clearTimeout(layer.inDebounce);
layer.inDebounce = setTimeout(() => {
const result = layer.axiosApiCall(
datasets,
this.getViewport(mapState),
mapState.zoom,
nextProps.filters,
auth,
project
);
if (result) {
result.then(function(result) {
visStateActions.layerConfigChange(layer, {
apiCallLoader: false
});
visStateActions.updateLayerData(layer, result);
});
}
clearTimeout(layer.inDebounce);
layer.inDebounce = 0;
}, 300);
}
});
}
updateSocketLayer = socketData => {
const {datasets, layers, widgets, visStateActions} = this.props;
const datasetKeys = Object.keys(datasets);
const updateDatasetIds = datasetKeys.filter(
d => datasets[d].datasetId === socketData.dsId
);
updateDatasetIds.map(dataId =>
visStateActions.updateDataset(dataId, socketData.updatedAt)
);
layers.map(layer => {
if (updateDatasetIds.indexOf(layer.config.dataId) > -1) {
visStateActions.layerConfigChange(layer, {
apiCallRequest: true
});
}
});
widgets.map(widget => {
if (updateDatasetIds.indexOf(widget.config.dataId) > -1) {
this.props.widgetConfigChange(widget, {
apiCallRequest: true
});
}
});
};
getViewport(mapState) {
const {
longitude,
latitude,
height,
width,
zoom,
pitch,
bearing
} = mapState;
// let boundingBox = geoViewport.bounds([longitude, latitude], zoom, [
// width,
// height
// ]);
const viewport = new WebMercatorViewport({
width,
height,
longitude,
latitude,
zoom,
pitch,
bearing
});
const cUL = viewport.unproject([0, 0]);
const cUR = viewport.unproject([width, 0]);
const cLR = viewport.unproject([width, height]);
const cLL = viewport.unproject([0, height]);
// let latRange = [...new Set([cLL[1], cUR[1], cLR[1], cUL[1]])].sort(
// (a, b) => a - b
// );
// let lngRange = [...new Set([cLL[0], cUR[0], cLR[0], cUL[0]])].sort(
// (a, b) => a - b
// );
// return [lngRange[0], lngRange[1], latRange[0], latRange[1]];
return {type: 'Polygon', coordinates: [[cUL, cUR, cLR, cLL, cUL]]};
}
layersSelector = props => props.layers;
layerDataSelector = props => props.layerData;
mapLayersSelector = props => props.mapLayers;
layerOrderSelector = props => props.layerOrder;
layersToRenderSelector = createSelector(
this.layersSelector,
this.layerDataSelector,
this.mapLayersSelector,
// {[id]: true \ false}
(layers, layerData, mapLayers) =>
layers.reduce(
(accu, layer, idx) => ({
...accu,
[layer.id]:
layer.shouldRenderLayer(layerData[idx]) &&
this._isVisibleMapLayer(layer, mapLayers)
}),
{}
)
);
filtersSelector = props => props.filters;
polygonFilters = createSelector(this.filtersSelector, filters =>
filters.filter(f => f.type === FILTER_TYPES.polygon)
);
mapboxLayersSelector = createSelector(
this.layersSelector,
this.layerDataSelector,
this.layerOrderSelector,
this.layersToRenderSelector,
generateMapboxLayers
);
/* component private functions */
_isVisibleMapLayer(layer, mapLayers) {
// if layer.id is not in mapLayers, don't render it
return !mapLayers || (mapLayers && mapLayers[layer.id]);
}
_onCloseMapPopover = () => {
this.props.visStateActions.onLayerClick(null);
};
_onLayerSetDomain = (idx, colorDomain) => {
this.props.visStateActions.layerConfigChange(this.props.layers[idx], {
colorDomain
});
};
_onWebGLInitialized = onWebGLInitialized;
_handleMapToggleLayer = layerId => {
const {index: mapIndex = 0, visStateActions} = this.props;
visStateActions.toggleLayerForMap(mapIndex, layerId);
};
_onMapboxStyleUpdate = () => {
// force refresh mapboxgl layers
this.previousLayers = {};
this._updateMapboxLayers();
if (typeof this.props.onMapStyleLoaded === 'function') {
this.props.onMapStyleLoaded(this._map);
}
};
_setMapboxMap = mapbox => {
if (!this._map && mapbox) {
this._map = mapbox.getMap();
// i noticed in certain context we don't access the actual map element
if (!this._map) {
return;
}
// bind mapboxgl event listener
this._map.on(MAPBOXGL_STYLE_UPDATE, this._onMapboxStyleUpdate);
this._map.on(MAPBOXGL_RENDER, () => {
if (typeof this.props.onMapRender === 'function') {
this.props.onMapRender(this._map);
}
});
}
if (this.props.getMapboxRef) {
// The parent component can gain access to our MapboxGlMap by
// providing this callback. Note that 'mapbox' will be null when the
// ref is unset (e.g. when a split map is closed).
this.props.getMapboxRef(mapbox, this.props.index);
}
};
_onBeforeRender = ({gl}) => {
setLayerBlending(gl, this.props.layerBlending);
};
/* component render functions */
/* eslint-disable complexity */
_renderMapPopover(layersToRender) {
// TODO: move this into reducer so it can be tested
const {
mapState,
hoverInfo,
clicked,
datasets,
interactionConfig,
layers,
mousePos: {mousePosition, coordinate, pinned},
auth,
project
} = this.props;
if (!mousePosition) {
return null;
}
// if clicked something, ignore hover behavior
const objectInfo = clicked || hoverInfo;
let layerHoverProp = null;
let position = {x: mousePosition[0], y: mousePosition[1]};
if (
interactionConfig.tooltip.enabled &&
objectInfo &&
objectInfo.picked
) {
// if anything hovered
const {object, layer: overlay} = objectInfo;
// deckgl layer to kepler-gl layer
const layer = layers[overlay.props.idx];
if (layer.getHoverData && layersToRender[layer.id]) {
// if layer is visible and have hovered data
const {
config: {dataId}
} = layer;
const {allData, fields} = datasets[dataId];
var fieldsToShow =
interactionConfig.tooltip.config.fieldsToShow[dataId];
if (layer.meta.Point === true) {
const result = layer.getHoverData(
object,
auth,
datasets,
fieldsToShow
);
result.then(re => {
layerHoverProp = {
data: re,
fields,
fieldsToShow,
layer,
datasets,
auth
};
});
} else {
var data = layer.getHoverData(object, auth, datasets, fieldsToShow);
if (layer.name === 'MVT') {
data = Object.values(object.properties);
data = [object, ...data];
fieldsToShow = Object.keys(object.properties);
}
layerHoverProp = {
data,
fields,
fieldsToShow,
layer,
datasets,
auth
};
}
}
}
if (pinned || clicked) {
// project lnglat to screen so that tooltip follows the object on zoom
const viewport = new WebMercatorViewport(mapState);
const lngLat = clicked ? clicked.lngLat : pinned.coordinate;
position = this._getHoverXY(viewport, lngLat);
}
// var data, fieldsToShow;
// const {allData, fields} = datasets[dataId];
// // const {x, y} = this._getHoverXY(viewport, lngLat) || objectInfo;
// // const {viewport} = overlay.context;
// if (layer.name == 'MVT') {
// const {
// config: {dataId}
// } = layer;
// // const {allData, fields} = datasets[dataId];
// const fieldKeys = Object.keys(object.properties);
// // let fields = [];
// // fields.push({
// // format: '',
// // id: '_geojson',
// // name: '_geojson',
// // tableFieldIndex: 1,
// // type: 'geojson'
// // });
// // fields.push({
// // format: '',
// // id: 'population',
// // name: 'Population',
// // tableFieldIndex: 2,
// // type: 'integer'
// // });
// data = Object.values(object.properties);
// data = [object, ...data];
// fieldsToShow = fieldKeys;
// // layerHoverProp = {
// // data,
// // fields,
// // fieldsToShow,
// // layer
// // };
// } else {
// data = layer.getHoverData(object, allData);
// // project lnglat to screen so that tooltip follows the object on zoom
// }
// const popoverProps = {
// data,
// fields,
// fieldsToShow: fieldsToShow
// ? fieldsToShow
// : interactionConfig.tooltip.config.fieldsToShow[dataId],
// layer,
// isVisible: true,
// x,
// y,
// freezed: Boolean(clicked),
// onClose: this._onCloseMapPopover,
// mapState
// };
// console.log('PopoverProps', popoverProps);
return (
<div>
<MapPopover
{...position}
layerHoverProp={layerHoverProp}
coordinate={
interactionConfig.coordinate.enabled &&
((pinned || {}).coordinate || coordinate)
}
freezed={Boolean(clicked || pinned)}
onClose={this._onCloseMapPopover}
mapW={mapState.width}
mapH={mapState.height}
/>
</div>
);
}
/* eslint-enable complexity */
_getHoverXY(viewport, lngLat) {
const screenCoord =
!viewport || !lngLat ? null : viewport.project(lngLat);
return screenCoord && {x: screenCoord[0], y: screenCoord[1]};
}
_renderLayer = (overlays, idx) => {
const {
layers,
layerData,
hoverInfo,
clicked,
mapState,
interactionConfig,
mousePos,
animationConfig,
visStateActions,
datasets,
filters
} = this.props;
const {nextMapState} = this.state;
const layer = layers[idx];
const data = layerData[idx];
const layerInteraction = {
mousePosition: mousePos.mousePosition,
wrapLongitude: true
};
const objectHovered = clicked || hoverInfo;
const layerCallbacks = {
onSetLayerDomain: val => this._onLayerSetDomain(idx, val)
};
// Layer is Layer class
// const layerOverlay = layer.renderLayer({
// data,
// idx,
// layerInteraction,
// objectHovered,
// mapState,
// interactionConfig,
// layerCallbacks,
// animationConfig
// });
let layerOverlay = [];
layerOverlay = layer.renderLayer({
data,
idx,
layerInteraction,
objectHovered,
mapState,
interactionConfig,
layerCallbacks,
animationConfig,
datasets,
filters,
loadEDLinkData: (data, dataId) =>
visStateActions.loadEDLinkData(data, dataId),
nextMapState
});
return overlays.concat(layerOverlay || []);
};
_renderDeckOverlay(layersToRender) {
const {
mapState,
mapStyle,
layerData,
layerOrder,
layers,
visStateActions,
mapboxApiAccessToken,
mapboxApiUrl,
uiState
} = this.props;
let deckGlLayers = [];
// wait until data is ready before render data layers
if (layerData && layerData.length) {
// last layer render first
deckGlLayers = layerOrder
.slice()
.reverse()
.filter(
idx =>
layers[idx].overlayType === OVERLAY_TYPE.deckgl &&
layersToRender[layers[idx].id]
)
.reduce(this._renderLayer, []);
}
if (mapStyle.visibleLayerGroups['3d building']) {
deckGlLayers.push(
new ThreeDBuildingLayer({
id: '_keplergl_3d-building',
mapboxApiAccessToken,
mapboxApiUrl,
threeDBuildingColor: mapStyle.threeDBuildingColor,
updateTriggers: {
getFillColor: mapStyle.threeDBuildingColor
}
})
);
}
// const isEdit = uiState.mapControls.mapDraw.active;
return (
<DeckGL
{...this.props.deckGlProps}
viewState={mapState}
id="default-deckgl-overlay"
layers={deckGlLayers}
onWebGLInitialized={this._onWebGLInitialized}
onBeforeRender={this._onBeforeRender}
onHover={visStateActions.onLayerHover}
onClick={visStateActions.onLayerClick}
// style={{zIndex: isEdit ? -1 : 0}}
/>
);
}
_updateMapboxLayers() {
const mapboxLayers = this.mapboxLayersSelector(this.props);
if (
!Object.keys(mapboxLayers).length &&
!Object.keys(this.previousLayers).length
) {
return;
}
updateMapboxLayers(this._map, mapboxLayers, this.previousLayers);
this.previousLayers = mapboxLayers;
}
_renderMapboxOverlays() {
if (this._map && this._map.isStyleLoaded()) {
this._updateMapboxLayers();
}
}
_onViewportChange = viewState => {
if (typeof this.props.onViewStateChange === 'function') {
this.props.onViewStateChange(viewState);
}
this.props.mapStateActions.updateMap(viewState);
};
render() {
const {
mapState,
mapStyle,
mapStateActions,
mapLayers,
layers,
MapComponent,
datasets,
mapboxApiAccessToken,
mapboxApiUrl,
uiState,
uiStateActions,
visStateActions,
editor,
index,
auth,
project
} = this.props;
const {longitude, latitude, height, width, zoom} = mapState;
// const boundingBox = geoViewport.bounds([longitude, latitude], zoom, [
// width,
// height
// ]);
// console.log('bounding box inside map-container', boundingBox);
const layersToRender = this.layersToRenderSelector(this.props);
if (!mapStyle.bottomMapStyle) {
// style not yet loaded
return <div />;
}
const mapProps = {
...mapState,
preserveDrawingBuffer: true,
mapboxApiAccessToken,
mapboxApiUrl,
onViewportChange: this._onViewportChange,
transformRequest
};
const isEdit = uiState.mapControls.mapDraw.active;
return (
<StyledMapContainer
sidePanel={uiState.activeSidePanel !== null}
style={MAP_STYLE.container}
>
<MapControl
datasets={datasets}
dragRotate={mapState.dragRotate}
isSplit={Boolean(mapLayers)}
isExport={this.props.isExport}
layers={layers}
layersToRender={layersToRender}
mapIndex={index}
mapControls={uiState.mapControls}
readOnly={this.props.readOnly}
scale={mapState.scale || 1}
top={0}
editor={editor}
onTogglePerspective={mapStateActions.togglePerspective}
onToggleSplitMap={mapStateActions.toggleSplitMap}
onMapToggleLayer={this._handleMapToggleLayer}
onToggleMapControl={uiStateActions.toggleMapControl}
onSetEditorMode={visStateActions.setEditorMode}
onToggleEditorVisibility={visStateActions.toggleEditorVisibility}
fitBounds={mapStateActions.fitBounds}
/>
<MapComponent
{...mapProps}
key="bottom"
ref={this._setMapboxMap}
mapStyle={mapStyle.bottomMapStyle}
getCursor={this.props.hoverInfo ? () => 'pointer' : undefined}
transitionDuration={TRANSITION_DURATION}
onMouseMove={this.props.visStateActions.onMouseMove}
attributionControl={false}
>
{this._renderDeckOverlay(layersToRender)}
{this._renderMapboxOverlays(layersToRender)}
{/*
By placing the editor in this map we have to perform fewer checks for css zIndex
and fewer updates when we switch from edit to read mode
*/}
<Editor
index={index}
datasets={datasets}
editor={editor}
filters={this.polygonFilters(this.props)}
isEnabled={isEdit}
layers={layers}
layersToRender={layersToRender}
onDeleteFeature={visStateActions.deleteFeature}
onSelect={visStateActions.setSelectedFeature}
onUpdate={visStateActions.setFeatures}
onTogglePolygonFilter={visStateActions.setPolygonFilterLayer}
style={{
pointerEvents: isEdit ? 'all' : 'none',
position: 'absolute',
display: editor.visible ? 'block' : 'none'
}}
auth={auth}
project={project}
mapState={mapState}
/>
</MapComponent>
{mapStyle.topMapStyle && (
<div style={MAP_STYLE.top}>
<MapComponent
{...mapProps}
key="top"
mapStyle={mapStyle.topMapStyle}
attributionControl={false}
/>
</div>
)}
{this._renderMapPopover(layersToRender)}
</StyledMapContainer>
);
}
}
MapContainer.displayName = 'MapContainer';
return MapContainer;
}