kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
225 lines (208 loc) • 6.05 kB
JavaScript
// Copyright (c) 2020 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 geoViewport from '@mapbox/geo-viewport';
/**
* Updaters for `mapState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.
* Read more about [Using updaters](../advanced-usage/using-updaters.md)
* @public
* @example
*
* import keplerGlReducer, {mapStateUpdaters} from 'kepler.gl/reducers';
* // Root Reducer
* const reducers = combineReducers({
* keplerGl: keplerGlReducer,
* app: appReducer
* });
*
* const composedReducer = (state, action) => {
* switch (action.type) {
* // click button to close side panel
* case 'CLICK_BUTTON':
* return {
* ...state,
* keplerGl: {
* ...state.keplerGl,
* foo: {
* ...state.keplerGl.foo,
* mapState: mapStateUpdaters.fitBoundsUpdater(
* mapState, {payload: [127.34, 31.09, 127.56, 31.59]]}
* )
* }
* }
* };
* }
* return reducers(state, action);
* };
*
* export default composedReducer;
*/
/* eslint-disable no-unused-vars */
const mapStateUpdaters = null;
/* eslint-enable no-unused-vars */
/**
* Default initial `mapState`
* @memberof mapStateUpdaters
* @constant
* @property {number} pitch Default: `0`
* @property {number} bearing Default: `0`
* @property {number} latitude Default: `37.75043`
* @property {number} longitude Default: `-122.34679`
* @property {number} zoom Default: `9`
* @property {boolean} dragRotate Default: `false`
* @property {number} width Default: `800`
* @property {number} height Default: `800`
* @property {boolean} isSplit Default: `false`
* @public
*/
export const INITIAL_MAP_STATE = {
pitch: 0,
bearing: 0,
latitude: 37.75043,
longitude: -122.34679,
zoom: 9,
dragRotate: false,
width: 800,
height: 800,
isSplit: false
};
/* Updaters */
/**
* Update map viewport
* @memberof mapStateUpdaters
* @param {Object} state
* @param {Object} action
* @param {Object} action.payload - viewport
* @returns {Object} nextState
* @public
*/
export const updateMapUpdater = (state, action) => ({
...state,
...(action.payload || {})
});
/**
* Fit map viewport to bounds
* @memberof mapStateUpdaters
* @param {Object} state
* @param {Object} action
* @param {number[]} action.payload - bounds as `[lngMin, latMin, lngMax, latMax]`
* @returns {Object} nextState
* @public
*/
export const fitBoundsUpdater = (state, action) => {
const bounds = action.payload;
const {center, zoom} = geoViewport.viewport(bounds, [state.width, state.height]);
return {
...state,
latitude: center[1],
longitude: center[0],
zoom
};
};
/**
* Toggle between 3d and 2d map.
* @memberof mapStateUpdaters
* @param {Object} state
* @returns {Object} nextState
* @public
*/
export const togglePerspectiveUpdater = state => ({
...state,
...{
pitch: state.dragRotate ? 0 : 50,
bearing: state.dragRotate ? 0 : 24
},
dragRotate: !state.dragRotate
});
/**
* reset mapState to initial State
* @memberof mapStateUpdaters
* @param {Object} state `mapState`
* @returns {Object} nextState
* @public
*/
export const resetMapConfigUpdater = state => ({
...INITIAL_MAP_STATE,
...state.initialState,
initialState: state.initialState
});
// consider case where you have a split map and user wants to reset
/**
* Update `mapState` to propagate a new config
* @memberof mapStateUpdaters
* @param {Object} state
* @param {Object} action
* @param {Object} action.payload - saved map config
* @returns {Object} nextState
* @public
*/
export const receiveMapConfigUpdater = (
state,
{payload: {config = {}, options = {}, bounds = null}}
) => {
const {mapState} = config || {};
// merged received mapstate with previous state
let mergedState = {...state, ...mapState};
// if center map
// center map will override mapState config
if (options.centerMap && bounds) {
mergedState = fitBoundsUpdater(mergedState, {
payload: bounds
});
}
return {
...mergedState,
// update width if `isSplit` has changed
...getMapDimForSplitMap(mergedState.isSplit, state)
};
};
/**
* Toggle between one or split maps
* @memberof mapStateUpdaters
* @param {Object} state
* @returns {Object} nextState
* @public
*/
export const toggleSplitMapUpdater = state => ({
...state,
isSplit: !state.isSplit,
...getMapDimForSplitMap(!state.isSplit, state)
});
// Helpers
export function getMapDimForSplitMap(isSplit, state) {
// cases:
// 1. state split: true - isSplit: true
// do nothing
// 2. state split: false - isSplit: false
// do nothing
if (state.isSplit === isSplit) {
return {};
}
const width =
state.isSplit && !isSplit
? // 3. state split: true - isSplit: false
// double width
state.width * 2
: // 4. state split: false - isSplit: true
// split width
state.width / 2;
return {
width
};
}