UNPKG

terriajs

Version:

Geospatial data visualization platform.

341 lines (275 loc) 15 kB
'use strict'; const React = require('react'); const PropTypes = require('prop-types'); import createReactClass from 'create-react-class'; const CameraFlightPath = require('terriajs-cesium/Source/Scene/CameraFlightPath'); const Cartesian2 = require('terriajs-cesium/Source/Core/Cartesian2'); const Cartesian3 = require('terriajs-cesium/Source/Core/Cartesian3'); const CesiumMath = require('terriajs-cesium/Source/Core/Math'); const defined = require('terriajs-cesium/Source/Core/defined'); const Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid'); const getTimestamp = require('terriajs-cesium/Source/Core/getTimestamp'); const Matrix4 = require('terriajs-cesium/Source/Core/Matrix4'); const Ray = require('terriajs-cesium/Source/Core/Ray'); const Transforms = require('terriajs-cesium/Source/Core/Transforms'); import Styles from './compass.scss'; // the compass on map const Compass = createReactClass({ propTypes: { terria: PropTypes.object }, getInitialState() { return { orbitCursorAngle: 0, heading: 0.0, orbitCursorOpacity: 0 }; }, componentDidMount() { this._unsubscribeFromViewerChange = this.props.terria.afterViewerChanged.addEventListener(() => viewerChange(this)); viewerChange(this); }, componentWillUnmount() { document.removeEventListener('mousemove', this.orbitMouseMoveFunction, false); document.removeEventListener('mouseup', this.orbitMouseUpFunction, false); this._unsubscribeFromClockTick && this._unsubscribeFromClockTick(); this._unsubscribeFromPostRender && this._unsubscribeFromPostRender(); this._unsubscribeFromViewerChange && this._unsubscribeFromViewerChange(); }, handleMouseDown(e) { if(e.stopPropagation) e.stopPropagation(); if(e.preventDefault) e.preventDefault(); const compassElement = e.currentTarget; const compassRectangle = e.currentTarget.getBoundingClientRect(); const maxDistance = compassRectangle.width / 2.0; const center = new Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0); const clickLocation = new Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top); const vector = Cartesian2.subtract(clickLocation, center, vectorScratch); const distanceFromCenter = Cartesian2.magnitude(vector); const distanceFraction = distanceFromCenter / maxDistance; const nominalTotalRadius = 145; const norminalGyroRadius = 50; if (distanceFraction < norminalGyroRadius / nominalTotalRadius) { orbit(this, compassElement, vector); } else if (distanceFraction < 1.0) { rotate(this, compassElement, vector); } else { return true; } }, handleDoubleClick(e) { const scene = this.props.terria.cesium.scene; const camera = scene.camera; const windowPosition = windowPositionScratch; windowPosition.x = scene.canvas.clientWidth / 2; windowPosition.y = scene.canvas.clientHeight / 2; const ray = camera.getPickRay(windowPosition, pickRayScratch); const center = scene.globe.pick(ray, scene, centerScratch); if (!defined(center)) { // Globe is barely visible, so reset to home view. this.props.terria.currentViewer.zoomTo(this.props.terria.homeView, 1.5); return; } const rotateFrame = Transforms.eastNorthUpToFixedFrame(center, Ellipsoid.WGS84); const lookVector = Cartesian3.subtract(center, camera.position, new Cartesian3()); const flight = CameraFlightPath.createTween(scene, { destination: Matrix4.multiplyByPoint(rotateFrame, new Cartesian3(0.0, 0.0, Cartesian3.magnitude(lookVector)), new Cartesian3()), direction: Matrix4.multiplyByPointAsVector(rotateFrame, new Cartesian3(0.0, 0.0, -1.0), new Cartesian3()), up: Matrix4.multiplyByPointAsVector(rotateFrame, new Cartesian3(0.0, 1.0, 0.0), new Cartesian3()), duration: 1.5 }); scene.tweens.add(flight); }, resetRotater() { this.setState({ orbitCursorOpacity: 0, orbitCursorAngle: 0 }); }, render() { const rotationMarkerStyle = { transform: 'rotate(-' + this.state.orbitCursorAngle + 'rad)', WebkitTransform: 'rotate(-' + this.state.orbitCursorAngle + 'rad)', opacity: this.state.orbitCursorOpacity }; const outerCircleStyle = { transform: 'rotate(-' + this.state.heading + 'rad)', WebkitTransform: 'rotate(-' + this.state.heading + 'rad)', opacity: '' }; const description = 'Drag outer ring: rotate view.\nDrag inner gyroscope: free orbit.\nDouble-click: reset view.\nTIP: You can also free orbit by holding the CTRL key and dragging the map.'; return ( <div className={Styles.compass} title ={description} onMouseDown ={this.handleMouseDown} onDoubleClick ={this.handleDoubleClick} onMouseUp ={this.resetRotater}> <div className={Styles.outerRing} style={outerCircleStyle}></div> <div className={Styles.innerRing} title='Click and drag to rotate the camera'></div> <div className={Styles.rotationMarker} style={rotationMarkerStyle}></div> </div> ); } }); const vectorScratch = new Cartesian2(); const oldTransformScratch = new Matrix4(); const newTransformScratch = new Matrix4(); const centerScratch = new Cartesian3(); const windowPositionScratch = new Cartesian2(); const pickRayScratch = new Ray(); function rotate(viewModel, compassElement, cursorVector) { // Remove existing event handlers, if any. document.removeEventListener('mousemove', viewModel.rotateMouseMoveFunction, false); document.removeEventListener('mouseup', viewModel.rotateMouseUpFunction, false); viewModel.rotateMouseMoveFunction = undefined; viewModel.rotateMouseUpFunction = undefined; viewModel.isRotating = true; viewModel.rotateInitialCursorAngle = Math.atan2(-cursorVector.y, cursorVector.x); const scene = viewModel.props.terria.cesium.scene; let camera = scene.camera; const windowPosition = windowPositionScratch; windowPosition.x = scene.canvas.clientWidth / 2; windowPosition.y = scene.canvas.clientHeight / 2; const ray = camera.getPickRay(windowPosition, pickRayScratch); const viewCenter = scene.globe.pick(ray, scene, centerScratch); if (!defined(viewCenter)) { viewModel.rotateFrame = Transforms.eastNorthUpToFixedFrame(camera.positionWC, Ellipsoid.WGS84, newTransformScratch); viewModel.rotateIsLook = true; } else { viewModel.rotateFrame = Transforms.eastNorthUpToFixedFrame(viewCenter, Ellipsoid.WGS84, newTransformScratch); viewModel.rotateIsLook = false; } let oldTransform = Matrix4.clone(camera.transform, oldTransformScratch); camera.lookAtTransform(viewModel.rotateFrame); viewModel.rotateInitialCameraAngle = Math.atan2(camera.position.y, camera.position.x); viewModel.rotateInitialCameraDistance = Cartesian3.magnitude(new Cartesian3(camera.position.x, camera.position.y, 0.0)); camera.lookAtTransform(oldTransform); viewModel.rotateMouseMoveFunction = function(e) { const compassRectangle = compassElement.getBoundingClientRect(); const center = new Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0); const clickLocation = new Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top); const vector = Cartesian2.subtract(clickLocation, center, vectorScratch); const angle = Math.atan2(-vector.y, vector.x); const angleDifference = angle - viewModel.rotateInitialCursorAngle; const newCameraAngle = CesiumMath.zeroToTwoPi(viewModel.rotateInitialCameraAngle - angleDifference); camera = viewModel.props.terria.cesium.scene.camera; oldTransform = Matrix4.clone(camera.transform, oldTransformScratch); camera.lookAtTransform(viewModel.rotateFrame); const currentCameraAngle = Math.atan2(camera.position.y, camera.position.x); camera.rotateRight(newCameraAngle - currentCameraAngle); camera.lookAtTransform(oldTransform); viewModel.props.terria.cesium.notifyRepaintRequired(); }; viewModel.rotateMouseUpFunction = function(e) { viewModel.isRotating = false; document.removeEventListener('mousemove', viewModel.rotateMouseMoveFunction, false); document.removeEventListener('mouseup', viewModel.rotateMouseUpFunction, false); viewModel.rotateMouseMoveFunction = undefined; viewModel.rotateMouseUpFunction = undefined; }; document.addEventListener('mousemove', viewModel.rotateMouseMoveFunction, false); document.addEventListener('mouseup', viewModel.rotateMouseUpFunction, false); } function orbit(viewModel, compassElement, cursorVector) { // Remove existing event handlers, if any. document.removeEventListener('mousemove', viewModel.orbitMouseMoveFunction, false); document.removeEventListener('mouseup', viewModel.orbitMouseUpFunction, false); if (defined(viewModel.orbitTickFunction)) { viewModel.props.terria.clock.onTick.removeEventListener(viewModel.orbitTickFunction); } viewModel.orbitMouseMoveFunction = undefined; viewModel.orbitMouseUpFunction = undefined; viewModel.orbitTickFunction = undefined; viewModel.isOrbiting = true; viewModel.orbitLastTimestamp = getTimestamp(); let scene = viewModel.props.terria.cesium.scene; let camera = scene.camera; const windowPosition = windowPositionScratch; windowPosition.x = scene.canvas.clientWidth / 2; windowPosition.y = scene.canvas.clientHeight / 2; const ray = camera.getPickRay(windowPosition, pickRayScratch); let center = scene.globe.pick(ray, scene, centerScratch); if (!defined(center)) { viewModel.orbitFrame = Transforms.eastNorthUpToFixedFrame(camera.positionWC, Ellipsoid.WGS84, newTransformScratch); viewModel.orbitIsLook = true; } else { viewModel.orbitFrame = Transforms.eastNorthUpToFixedFrame(center, Ellipsoid.WGS84, newTransformScratch); viewModel.orbitIsLook = false; } viewModel.orbitTickFunction = function(e) { const timestamp = getTimestamp(); const deltaT = timestamp - viewModel.orbitLastTimestamp; const rate = (viewModel.state.orbitCursorOpacity - 0.5) * 2.5 / 1000; const distance = deltaT * rate; const angle = viewModel.state.orbitCursorAngle + CesiumMath.PI_OVER_TWO; const x = Math.cos(angle) * distance; const y = Math.sin(angle) * distance; scene = viewModel.props.terria.cesium.scene; camera = scene.camera; const oldTransform = Matrix4.clone(camera.transform, oldTransformScratch); camera.lookAtTransform(viewModel.orbitFrame); if (viewModel.orbitIsLook) { camera.look(Cartesian3.UNIT_Z, -x); camera.look(camera.right, -y); } else { camera.rotateLeft(x); camera.rotateUp(y); } camera.lookAtTransform(oldTransform); viewModel.props.terria.cesium.notifyRepaintRequired(); viewModel.orbitLastTimestamp = timestamp; }; function updateAngleAndOpacity(vector, compassWidth) { const angle = Math.atan2(-vector.y, vector.x); viewModel.setState({ orbitCursorAngle: CesiumMath.zeroToTwoPi(angle - CesiumMath.PI_OVER_TWO) }); const distance = Cartesian2.magnitude(vector); const maxDistance = compassWidth / 2.0; const distanceFraction = Math.min(distance / maxDistance, 1.0); const easedOpacity = 0.5 * distanceFraction * distanceFraction + 0.5; viewModel.setState({ orbitCursorOpacity: easedOpacity }); viewModel.props.terria.cesium.notifyRepaintRequired(); } viewModel.orbitMouseMoveFunction = function(e) { const compassRectangle = compassElement.getBoundingClientRect(); center = new Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0); const clickLocation = new Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top); const vector = Cartesian2.subtract(clickLocation, center, vectorScratch); updateAngleAndOpacity(vector, compassRectangle.width); }; viewModel.orbitMouseUpFunction = function(e) { // TODO: if mouse didn't move, reset view to looking down, north is up? viewModel.isOrbiting = false; document.removeEventListener('mousemove', viewModel.orbitMouseMoveFunction, false); document.removeEventListener('mouseup', viewModel.orbitMouseUpFunction, false); if (defined(viewModel.orbitTickFunction)) { viewModel.props.terria.clock.onTick.removeEventListener(viewModel.orbitTickFunction); } viewModel.orbitMouseMoveFunction = undefined; viewModel.orbitMouseUpFunction = undefined; viewModel.orbitTickFunction = undefined; }; document.addEventListener('mousemove', viewModel.orbitMouseMoveFunction, false); document.addEventListener('mouseup', viewModel.orbitMouseUpFunction, false); viewModel._unsubscribeFromClockTick = viewModel.props.terria.clock.onTick.addEventListener(viewModel.orbitTickFunction); updateAngleAndOpacity(cursorVector, compassElement.getBoundingClientRect().width); } function viewerChange(viewModel) { if (defined(viewModel.props.terria.cesium)) { if (viewModel._unsubscribeFromPostRender) { viewModel._unsubscribeFromPostRender(); viewModel._unsubscribeFromPostRender = undefined; } viewModel._unsubscribeFromPostRender = viewModel.props.terria.cesium.scene.postRender.addEventListener(function() { viewModel.setState({ heading: viewModel.props.terria.cesium.scene.camera.heading }); }); } else { if (viewModel._unsubscribeFromPostRender) { viewModel._unsubscribeFromPostRender(); viewModel._unsubscribeFromPostRender = undefined; } viewModel.showCompass = false; } } module.exports = Compass;