UNPKG

terriajs

Version:

Geospatial data visualization platform.

235 lines (204 loc) 11.3 kB
'use strict'; /*global require*/ var Cartesian3 = require('terriajs-cesium/Source/Core/Cartesian3'); var Cartographic = require('terriajs-cesium/Source/Core/Cartographic'); var CesiumMath = require('terriajs-cesium/Source/Core/Math'); var defined = require('terriajs-cesium/Source/Core/defined'); var defineProperties = require('terriajs-cesium/Source/Core/defineProperties'); var DeveloperError = require('terriajs-cesium/Source/Core/DeveloperError'); var Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid'); var HeadingPitchRange = require('terriajs-cesium/Source/Core/HeadingPitchRange'); var HeadingPitchRoll = require('terriajs-cesium/Source/Core/HeadingPitchRoll'); var Matrix3 = require('terriajs-cesium/Source/Core/Matrix3'); var Matrix4 = require('terriajs-cesium/Source/Core/Matrix4'); var Quaternion = require('terriajs-cesium/Source/Core/Quaternion'); var Rectangle = require('terriajs-cesium/Source/Core/Rectangle'); var Transforms = require('terriajs-cesium/Source/Core/Transforms'); /** * Holds a camera view parameters, expressed as a rectangular extent and/or as a camera position, direction, * and up vector. * * @alias CameraView * @constructor */ var CameraView = function(rectangle, position, direction, up) { if (!defined(rectangle)) { throw new DeveloperError('rectangle is required.'); } if (defined(position) || defined(direction) || defined(up)) { if (!defined(position) || !defined(direction) || !defined(up)) { throw new DeveloperError('If any of position, direction, or up are specified, all must be specified.'); } } this._rectangle = rectangle; this._position = position; this._direction = direction; this._up = up; }; defineProperties(CameraView.prototype, { /** * Gets the rectangular extent of the view. If {@link CameraView#position}, {@link CameraView#direction}, * and {@link CameraView#up} are specified, this property will be ignored for viewers that support those parameters * (e.g. Cesium). This property must always be supplied, however, for the benefit of viewers that do not understand * these parameters (e.g. Leaflet). * @type {Rectangle} */ rectangle: { get: function() { return this._rectangle; } }, /** * Gets the position of the camera in the Earth-centered Fixed frame. * @type {Cartesian3} */ position: { get: function() { return this._position; } }, /** * Gets the look direction of the camera in the Earth-centered Fixed frame. * @type {Cartesian3} */ direction: { get: function() { return this._direction; } }, /** * Gets the up vector direction of the camera in the Earth-centered Fixed frame. * @type {Cartesian3} */ up: { get: function() { return this._up; } } }); /** * Constructs a {@link CameraView} from json. All angles must be specified in degrees. * If neither json.lookAt nor json.positionHeadingPitchRoll is present, then json should have the keys position, direction, up, west, south, east, north. * @param {Object} json The JSON description. The JSON should be in the form of an object literal, not a string. * @param {Object} [json.lookAt] If present, must include keys targetLongitude, targetLatitude, targetHeight, heading, pitch, range. * @param {Object} [json.positionHeadingPitchRoll] If present, must include keys cameraLongitude, cameraLatitude, cameraHeight, heading, pitch, roll. * @return {CameraView} The camera view. */ CameraView.fromJson = function(json) { if (defined(json.lookAt)) { var targetPosition = Cartographic.fromDegrees(json.lookAt.targetLongitude, json.lookAt.targetLatitude, json.lookAt.targetHeight); var headingPitchRange = new HeadingPitchRange(CesiumMath.toRadians(json.lookAt.heading), CesiumMath.toRadians(json.lookAt.pitch), json.lookAt.range); return CameraView.fromLookAt(targetPosition, headingPitchRange); } else if (defined(json.positionHeadingPitchRoll)) { var cameraPosition = Cartographic.fromDegrees(json.positionHeadingPitchRoll.cameraLongitude, json.positionHeadingPitchRoll.cameraLatitude, json.positionHeadingPitchRoll.cameraHeight); return CameraView.fromPositionHeadingPitchRoll(cameraPosition, CesiumMath.toRadians(json.positionHeadingPitchRoll.heading), CesiumMath.toRadians(json.positionHeadingPitchRoll.pitch), CesiumMath.toRadians(json.positionHeadingPitchRoll.roll)); } else if (defined(json.position) && defined(json.direction) && defined(json.up)) { return new CameraView( Rectangle.fromDegrees(json.west, json.south, json.east, json.north), new Cartesian3(json.position.x, json.position.y, json.position.z), new Cartesian3(json.direction.x, json.direction.y, json.direction.z), new Cartesian3(json.up.x, json.up.y, json.up.z)); } else { return new CameraView(Rectangle.fromDegrees(json.west, json.south, json.east, json.north)); } }; var scratchPosition = new Cartesian3(); var scratchOffset = new Cartesian3(); var scratchDirection = new Cartesian3(); var scratchRight = new Cartesian3(); var scratchUp = new Cartesian3(); var scratchTarget = new Cartesian3(); var scratchMatrix4 = new Matrix4(); /** * Constructs a {@link CameraView} from a "look at" description. * @param {Cartographic} targetPosition The position to look at. * @param {HeadingPitchRange} headingPitchRange The offset of the camera from the target position. * @return {CameraView} The camera view. */ CameraView.fromLookAt = function(targetPosition, headingPitchRange) { if (!defined(targetPosition)) { throw new DeveloperError('targetPosition is required.'); } if (!defined(headingPitchRange)) { throw new DeveloperError('headingPitchRange is required.'); } var positionENU = offsetFromHeadingPitchRange(headingPitchRange.heading, -headingPitchRange.pitch, headingPitchRange.range, scratchPosition); var directionENU = Cartesian3.normalize(Cartesian3.negate(positionENU, scratchDirection), scratchDirection); var rightENU = Cartesian3.cross(directionENU, Cartesian3.UNIT_Z, scratchRight); if (Cartesian3.magnitudeSquared(rightENU) < CesiumMath.EPSILON10) { Cartesian3.clone(Cartesian3.UNIT_X, rightENU); } Cartesian3.normalize(rightENU, rightENU); var upENU = Cartesian3.cross(rightENU, directionENU, scratchUp); Cartesian3.normalize(upENU, upENU); var targetCartesian = Ellipsoid.WGS84.cartographicToCartesian(targetPosition, scratchTarget); var transform = Transforms.eastNorthUpToFixedFrame(targetCartesian, Ellipsoid.WGS84, scratchMatrix4); var offsetECF = Matrix4.multiplyByPointAsVector(transform, positionENU, scratchOffset); var position = Cartesian3.add(targetCartesian, offsetECF, new Cartesian3()); var direction = Cartesian3.normalize(Cartesian3.negate(offsetECF, new Cartesian3()), new Cartesian3()); var up = Matrix4.multiplyByPointAsVector(transform, upENU, new Cartesian3()); // Estimate a rectangle for this view. var fieldOfViewHalfAngle = CesiumMath.toRadians(30); var groundDistance = Math.tan(fieldOfViewHalfAngle) * (headingPitchRange.range + targetPosition.height); var angle = groundDistance / Ellipsoid.WGS84.minimumRadius; var extent = new Rectangle(targetPosition.longitude - angle, targetPosition.latitude - angle, targetPosition.longitude + angle, targetPosition.latitude + angle); return new CameraView(extent, position, direction, up); }; var scratchQuaternion = new Quaternion(); var scratchMatrix3 = new Matrix3(); /** * Constructs a {@link CameraView} from a camera position and heading, pitch, and roll angles for the camera. * @param {Cartographic} cameraPosition The position of the camera. * @param {Number} heading The heading of the camera in radians measured from North toward East. * @param {Number} pitch The pitch of the camera in radians measured from the local horizontal. Positive angles look up, negative angles look down. * @param {Number} roll The roll of the camera in radians counterclockwise. */ CameraView.fromPositionHeadingPitchRoll = function(cameraPosition, heading, pitch, roll) { if (!defined(cameraPosition)) { throw new DeveloperError('cameraPosition is required.'); } if (!defined(heading)) { throw new DeveloperError('heading is required.'); } if (!defined(pitch)) { throw new DeveloperError('pitch is required.'); } if (!defined(roll)) { throw new DeveloperError('roll is required.'); } var hpr = new HeadingPitchRoll(heading - CesiumMath.PI_OVER_TWO, pitch, roll); var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchQuaternion); var rotMat = Matrix3.fromQuaternion(rotQuat, scratchMatrix3); var directionENU = Matrix3.getColumn(rotMat, 0, scratchDirection); var upENU = Matrix3.getColumn(rotMat, 2, scratchUp); var positionECF = Ellipsoid.WGS84.cartographicToCartesian(cameraPosition, scratchTarget); var transform = Transforms.eastNorthUpToFixedFrame(positionECF, Ellipsoid.WGS84, scratchMatrix4); var directionECF = Matrix4.multiplyByPointAsVector(transform, directionENU, new Cartesian3()); var upECF = Matrix4.multiplyByPointAsVector(transform, upENU, new Cartesian3()); // Estimate a rectangle for this view. var fieldOfViewHalfAngle = CesiumMath.toRadians(30); var groundDistance = Math.tan(fieldOfViewHalfAngle) * cameraPosition.height; var angle = groundDistance / Ellipsoid.WGS84.minimumRadius; var extent = new Rectangle(cameraPosition.longitude - angle, cameraPosition.latitude - angle, cameraPosition.longitude + angle, cameraPosition.latitude + angle); return new CameraView(extent, positionECF, directionECF, upECF); }; var scratchLookAtHeadingPitchRangeQuaternion1 = new Quaternion(); var scratchLookAtHeadingPitchRangeQuaternion2 = new Quaternion(); var scratchHeadingPitchRangeMatrix3 = new Matrix3(); function offsetFromHeadingPitchRange(heading, pitch, range, result) { pitch = CesiumMath.clamp(pitch, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); heading = CesiumMath.zeroToTwoPi(heading) - CesiumMath.PI_OVER_TWO; var pitchQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, scratchLookAtHeadingPitchRangeQuaternion1); var headingQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, scratchLookAtHeadingPitchRangeQuaternion2); var rotQuat = Quaternion.multiply(headingQuat, pitchQuat, headingQuat); var rotMatrix = Matrix3.fromQuaternion(rotQuat, scratchHeadingPitchRangeMatrix3); var offset = Cartesian3.clone(Cartesian3.UNIT_X, result); Matrix3.multiplyByVector(rotMatrix, offset, offset); Cartesian3.negate(offset, offset); Cartesian3.multiplyByScalar(offset, range, offset); return offset; } module.exports = CameraView;