diff --git a/src/scripts/actions/set-globe-spinning.ts b/src/scripts/actions/set-globe-spinning.ts new file mode 100644 index 000000000..502e52ace --- /dev/null +++ b/src/scripts/actions/set-globe-spinning.ts @@ -0,0 +1,13 @@ +export const SET_GLOBE_SPINNING = 'SET_GLOBE_SPINNING'; + +export interface SetGlobeSpinningAction { + type: typeof SET_GLOBE_SPINNING; + spinning: boolean; +} + +const setGlobeSpinningAction = (spinning: boolean): SetGlobeSpinningAction => ({ + type: SET_GLOBE_SPINNING, + spinning +}); + +export default setGlobeSpinningAction; diff --git a/src/scripts/components/main/globe/globe.tsx b/src/scripts/components/main/globe/globe.tsx index cdadf79c4..9a8911ad8 100644 --- a/src/scripts/components/main/globe/globe.tsx +++ b/src/scripts/components/main/globe/globe.tsx @@ -1,6 +1,15 @@ import React, {FunctionComponent, useRef, useEffect, useState} from 'react'; import 'cesium/Build/Cesium/Widgets/widgets.css'; -import {Viewer, SceneMode, Color, TileMapServiceImageryProvider} from 'cesium'; +import { + Cartesian3, + Color, + EventHelper, + SceneMode, + ScreenSpaceEventHandler, + ScreenSpaceEventType, + TileMapServiceImageryProvider, + Viewer +} from 'cesium'; import { getGlobeView, @@ -45,6 +54,7 @@ interface Props { projectionState: GlobeProjectionState; imageLayer: GlobeImageLayerData | null; basemap: BasemapId | null; + spinning: boolean; flyTo: GlobeView | null; markers?: Marker[]; backgroundColor: string; @@ -52,6 +62,7 @@ interface Props { onTouchStart: () => void; onChange: (view: GlobeView) => void; onMoveEnd: (view: GlobeView) => void; + onMouseDown: () => void; } // keep a reference to the current basemap layer @@ -67,11 +78,14 @@ function getBasemapUrl(id: BasemapId | null) { return isElectron() ? config.basemapUrlsOffline[id] : config.basemapUrls[id]; } +const spinningEventHelper = new EventHelper(); + const Globe: FunctionComponent = ({ view, projectionState, imageLayer, basemap, + spinning, active, flyTo, markers = [], @@ -79,7 +93,8 @@ const Globe: FunctionComponent = ({ onMouseEnter, onTouchStart, onChange, - onMoveEnd + onMoveEnd, + onMouseDown }) => { const [viewer, setViewer] = useState(null); const ref = useRef(null); @@ -176,6 +191,22 @@ const Globe: FunctionComponent = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [ref, onChange, onMoveEnd]); + // update mousedown handler + useEffect(() => { + if (!viewer) { + return; + } + + const handler = new ScreenSpaceEventHandler( + viewer.scene.canvas as HTMLCanvasElement + ); + handler.setInputAction(onMouseDown, ScreenSpaceEventType.LEFT_DOWN); + + return () => { + handler.destroy(); + }; + }, [viewer, onMouseDown]); + // switch projections useEffect(() => { if (!viewer) { @@ -241,6 +272,31 @@ const Globe: FunctionComponent = ({ flyToGlobeView(viewer, flyTo); }, [viewer, flyTo]); + // update spinning + useEffect(() => { + if (!viewer) { + return; + } + + if (spinning) { + setTimeout(() => { + let lastNow = Date.now(); + + const spin = () => { + const now = Date.now(); + const spinRate = 0.08; + const delta = (now - lastNow) / 1000; + lastNow = now; + viewer.scene.camera.rotate(Cartesian3.UNIT_Z, spinRate * delta); + }; + + spinningEventHelper.add(viewer.clock.onTick, spin); + }, projectionState.morphTime * 1000); + } else { + spinningEventHelper.removeAll(); + } + }, [spinning, viewer]); + useMarkers(viewer, markers); return ( diff --git a/src/scripts/components/main/globes/globes.tsx b/src/scripts/components/main/globes/globes.tsx index c48d2db2b..06af62175 100644 --- a/src/scripts/components/main/globes/globes.tsx +++ b/src/scripts/components/main/globes/globes.tsx @@ -19,11 +19,13 @@ import {getImageLayerData} from '../../../libs/get-image-layer-data'; import {State} from '../../../reducers'; import {layerDetailsSelector} from '../../../selectors/layers/layer-details'; import {selectedLayerIdsSelector} from '../../../selectors/layers/selected-ids'; +import {globeSpinningSelector} from '../../../selectors/globe/Spinning'; import {GlobeView} from '../../../types/globe-view'; import {Marker} from '../../../types/marker-type'; import styles from './globes.styl'; +import setGlobeSpinningAction from '../../../actions/set-globe-spinning'; interface Props { backgroundColor: string; @@ -35,6 +37,7 @@ const Globes: FunctionComponent = ({backgroundColor, markers = []}) => { const selectedLayerIds = useSelector(selectedLayerIdsSelector); const projectionState = useSelector(projectionSelector); const globalGlobeView = useSelector(globeViewSelector); + const globeSpinning = useSelector(globeSpinningSelector); const {mainId, compareId} = selectedLayerIds; const mainLayerDetails = useSelector((state: State) => layerDetailsSelector(state, mainId) @@ -64,6 +67,11 @@ const Globes: FunctionComponent = ({backgroundColor, markers = []}) => { [dispatch] ); + const onMouseDownHandler = useCallback( + () => globeSpinning && dispatch(setGlobeSpinningAction(false)), + [dispatch, globeSpinning] + ); + const mainImageLayer = useMemo( () => getImageLayerData(mainLayerDetails, time), [mainLayerDetails, time] @@ -104,11 +112,13 @@ const Globes: FunctionComponent = ({backgroundColor, markers = []}) => { projectionState={projectionState} imageLayer={mainImageLayer} basemap={mainLayerDetails?.basemap || null} + spinning={globeSpinning} flyTo={flyTo} onMouseEnter={() => setIsMainActive(true)} onTouchStart={() => setIsMainActive(true)} onChange={onChangeHandler} onMoveEnd={onMoveEndHandler} + onMouseDown={onMouseDownHandler} /> {compareLayer && ( @@ -119,11 +129,13 @@ const Globes: FunctionComponent = ({backgroundColor, markers = []}) => { projectionState={projectionState} imageLayer={compareImageLayer} basemap={compareLayerDetails?.basemap || null} + spinning={globeSpinning} flyTo={flyTo} onMouseEnter={() => setIsMainActive(false)} onTouchStart={() => setIsMainActive(false)} onChange={onChangeHandler} onMoveEnd={onMoveEndHandler} + onMouseDown={onMouseDownHandler} /> )} diff --git a/src/scripts/config/main.ts b/src/scripts/config/main.ts index e2c7d3687..25164ac52 100644 --- a/src/scripts/config/main.ts +++ b/src/scripts/config/main.ts @@ -19,7 +19,8 @@ const globeState: GlobeState = { pitch: -90, roll: 0 } - } + }, + spinning: true }; // @ts-ignore - injected via webpack's define plugin diff --git a/src/scripts/libs/globe-url-parameter.ts b/src/scripts/libs/globe-url-parameter.ts index df241ee5f..7f50039b5 100644 --- a/src/scripts/libs/globe-url-parameter.ts +++ b/src/scripts/libs/globe-url-parameter.ts @@ -22,7 +22,7 @@ export function parseUrl(): UrlHashState | null { const splitted = globeParam.split(char); - if (splitted.length !== 10) { + if (splitted.length !== 11) { return null; } @@ -37,14 +37,14 @@ export function parseUrl(): UrlHashState | null { } // globe view values - const values = splitted.slice(1, 8).map(str => parseFloat(str)); + const values = splitted.slice(1, 9).map(str => parseFloat(str)); if (values.some(num => isNaN(num))) { return null; } // selected main and compare layer ids - const layerIds = splitted.slice(8, 10).map(id => id || null); + const layerIds = splitted.slice(9, 11).map(id => id || null); return { globeState: { @@ -64,7 +64,8 @@ export function parseUrl(): UrlHashState | null { projection: GlobeProjection.Sphere, morphTime: 2 }, - time: values[6] + time: values[6], + spinning: Boolean(Number(values[7])) }, layerIds: { mainId: layerIds[0], @@ -78,11 +79,20 @@ export function getParamString( mainId: string | null, compareId: string | null ): string | null { - const {view, projectionState, time} = globeState; + const {view, projectionState, time, spinning} = globeState; const {position, orientation} = view; const {longitude, latitude, height} = position; const {heading, pitch, roll} = orientation; - const values = [longitude, latitude, height, heading, pitch, roll, time]; + const values = [ + longitude, + latitude, + height, + heading, + pitch, + roll, + time, + spinning ? 1 : 0 + ]; if (values.some(num => isNaN(num))) { return null; diff --git a/src/scripts/reducers/globe/index.ts b/src/scripts/reducers/globe/index.ts index 37de9b801..2203ea577 100644 --- a/src/scripts/reducers/globe/index.ts +++ b/src/scripts/reducers/globe/index.ts @@ -3,11 +3,13 @@ import {combineReducers} from 'redux'; import projectionReducer from './projection'; import viewReducer from './view'; import timeReducer from './time'; +import spinningReducer from './spinning'; const globeReducer = combineReducers({ view: viewReducer, projectionState: projectionReducer, - time: timeReducer + time: timeReducer, + spinning: spinningReducer }); export default globeReducer; diff --git a/src/scripts/reducers/globe/spinning.ts b/src/scripts/reducers/globe/spinning.ts new file mode 100644 index 000000000..d92c01389 --- /dev/null +++ b/src/scripts/reducers/globe/spinning.ts @@ -0,0 +1,39 @@ +import { + SET_GLOBE_SPINNING, + SetGlobeSpinningAction +} from '../../actions/set-globe-spinning'; +import { + SET_GLOBE_PROJECTION, + SetGlobeProjectionAction +} from '../../actions/set-globe-projection'; +import {SET_FLY_TO, SetFlyToAction} from '../../actions/set-fly-to'; + +import {GlobeProjection} from '../../types/globe-projection'; +import config from '../../config/main'; +import {parseUrl} from '../../libs/globe-url-parameter'; + +// get initial state from url or fallback to default state in config +console.log(parseUrl()); + +const globeState = parseUrl()?.globeState || config.globe; +const initialState = globeState.spinning; + +console.log(initialState); + +function spinningReducer( + state: boolean = initialState, + action: SetGlobeSpinningAction | SetGlobeProjectionAction | SetFlyToAction +): boolean { + switch (action.type) { + case SET_GLOBE_SPINNING: + return action.spinning; + case SET_GLOBE_PROJECTION: + return action.projection === GlobeProjection.Sphere; + case SET_FLY_TO: + return false; + default: + return state; + } +} + +export default spinningReducer; diff --git a/src/scripts/selectors/globe/spinning.ts b/src/scripts/selectors/globe/spinning.ts new file mode 100644 index 000000000..0f44b4c7c --- /dev/null +++ b/src/scripts/selectors/globe/spinning.ts @@ -0,0 +1,5 @@ +import {State} from '../../reducers/index'; + +export function globeSpinningSelector(state: State): boolean { + return state.globe.spinning; +}