diff --git a/src/map/Map.Camera.js b/src/map/Map.Camera.js index 24ec6b5c4..598ed232f 100644 --- a/src/map/Map.Camera.js +++ b/src/map/Map.Camera.js @@ -3,7 +3,7 @@ import Point from '../geo/Point'; import Coordinate from '../geo/Coordinate'; import * as mat4 from '../core/util/mat4'; import { subtract, add, scale, normalize, dot, set, distance } from '../core/util/vec3'; -import { clamp, interpolate, isNumber, isNil, wrap } from '../core/util'; +import { clamp, interpolate, isNumber, isNil, wrap, toDegree, toRadian } from '../core/util'; import { applyMatrix, matrixToQuaternion, quaternionToMatrix, lookAt, setPosition } from '../core/util/math'; import Browser from '../core/Browser'; @@ -206,6 +206,149 @@ Map.include(/** @lends Map.prototype */{ return this; }, + /** + * set camera movements + * @param {Array} frameOptions + * [{ center: [114, 32], zoom: 14, pitch: 45, bearing: 90, timestamp: 0 }] + * @param {Object} extraOptions + * { autoRotate: true } + */ + setCameraMovements(frameOptions, extraOptions) { + if (!Array.isArray(frameOptions) || !frameOptions.length) { + return this; + } + this.setView({ + center: frameOptions[0].center, + zoom: frameOptions[0].zoom, + pitch: frameOptions[0].pitch, + bearing: frameOptions[0].bearing + }); + if (frameOptions.length === 1) return this; + let index = 1; + let onFrame = frame => { + if (frame.state.playState === 'finished') { + index++; + if (index === frameOptions.length - 1) onFrame = null; + const frameOption = frameOptions[index]; + frameOption.duration = frameOption.timestamp - frameOptions[index - 1].timestamp; + if (extraOptions && extraOptions.autoRotate) { + frameOption.bearing = calculateBearing(frameOptions[index - 1].center, frameOption.center); + } + this._setCameraMovement(frameOption, onFrame); + } + }; + if (frameOptions.length === 2) onFrame = null; + const currentBearing = this.getBearing(); + this._setCameraMovement({ + bearing: currentBearing, + ...frameOptions[index], + duration: frameOptions[index].timestamp - frameOptions[index - 1].timestamp + }, onFrame); + const play = () => { + this._animPlayer.play(); + }; + const pause = () => { + this._animPlayer.pause(); + }; + const cancel = () => { + this._animPlayer.cancel(); + }; + const finish = () => { + this._animPlayer.finish(); + }; + const reverse = () => { + this._animPlayer.reverse(); + }; + return { + play, + pause, + cancel, + finish, + reverse + }; + }, + + _setCameraMovement(frameOption, frame) { + this.animateTo({ + zoom : frameOption.zoom, + center : frameOption.center, + pitch : frameOption.pitch, + bearing : frameOption.bearing + }, { + duration : frameOption.duration, + easing : 'out' + }, frame); + }, + + /** + * Set camera position + * @param {Object} params + * @property {Array} position + * @property {Number} pitch + * @property {Number} bearing + */ + setCameraPosition(params) { + const { position, pitch, bearing } = params; + + const cameraAltitude = position[2] * this._meterToGLPoint; + + const centerAltitude = this.centerAltitude || 0; + const centerPointZ = centerAltitude * this._meterToGLPoint; + + const cz = cameraAltitude - centerPointZ; + + const pitchRadian = pitch * RADIAN; + + const cameraToGroundDistance = cz / Math.cos(pitchRadian); + + const dist = Math.sin(pitchRadian) * cameraToGroundDistance; + + const cameraToCenterDistance = cameraToGroundDistance + centerPointZ; + + const zoom = this._getZoomFromCameraToCenterDistance(cameraToCenterDistance); + + const wrapBearing = wrap(bearing, -180, 180); + const bearingRadian = wrapBearing * RADIAN; + + const glRes = this.getGLRes(); + const tempCoord = new Coordinate(position[0], position[1]); + const tempPoint = this.coordToPointAtRes(tempCoord, glRes); + const point = new Point(0, 0); + point.x = tempPoint.x + dist * Math.sin(bearingRadian); + point.y = tempPoint.y + dist * Math.cos(bearingRadian); + + const prjCenter = this._pointToPrjAtRes(point, this.getGLRes()); + this._setPrjCenter(prjCenter); + + this.setView({ + bearing, + pitch, + zoom + }); + + return this; + }, + + _getZoomFromCameraToCenterDistance(distance) { + const ratio = this._getFovRatio(); + const scale = distance * ratio * 2 / (this.height || 1) * this.getGLRes(); + const resolutions = this._getResolutions(); + let z = 0; + for (z; z < resolutions.length - 1; z++) { + if (resolutions[z] === scale) { + return z; + } else if (resolutions[z + 1] === scale) { + return z + 1; + } else if (scale < resolutions[z] && scale > resolutions[z + 1]) { + z = (scale - resolutions[z]) / (resolutions[z + 1] - resolutions[z]) + z; + return z - 1; + } else { + continue; + } + } + return z; + }, + /** * Whether the map is rotating or tilting. * @return {Boolean} @@ -754,3 +897,15 @@ Map.include(/** @lends Map.prototype */{ function createMat4() { return mat4.identity(new Array(16)); } + +function calculateBearing(start, end) { + const lon1 = toRadian(start[0]); + const lon2 = toRadian(end[0]); + const lat1 = toRadian(start[1]); + const lat2 = toRadian(end[1]); + const a = Math.sin(lon2 - lon1) * Math.cos(lat2); + const b = + Math.cos(lat1) * Math.sin(lat2) - + Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); + return toDegree(Math.atan2(a, b)); +} diff --git a/test/map/MapCameraSpec.js b/test/map/MapCameraSpec.js index 2fbc53fa0..a24fec991 100644 --- a/test/map/MapCameraSpec.js +++ b/test/map/MapCameraSpec.js @@ -453,6 +453,86 @@ describe('Map.Camera', function () { }); }); + describe('Set camera position', function () { + it('pitch 0, bearing 0', function () { + map.setCameraPosition({ + position: [0, 0, 10000], + pitch: 0, + bearing: 0, + }) + expect(map.getCenter().x).to.be.eql(0); + expect(map.getCenter().y).to.be.eql(0); + expect(map.getPitch()).to.be.eql(0); + expect(map.getBearing()).to.be.eql(0); + expect(map.getZoom()).to.be.within(8, 9); + }); + + it('pitch 45, bearing 45', function () { + map.setCameraPosition({ + position: [0, 0, 10000], + pitch: 45, + bearing: 45, + }) + expect(map.getCenter().x).to.be.within(0.07, 0.08); + expect(map.getCenter().y).to.be.within(0.07, 0.08); + expect(map.getPitch()).to.be.eql(45); + expect(map.getBearing()).to.be.eql(45); + expect(map.getZoom()).to.be.within(7, 8); + }); + + it('pitch 45, bearing 135', function () { + map.setCameraPosition({ + position: [0, 0, 10000], + pitch: 45, + bearing: 135, + }) + expect(map.getCenter().x).to.be.within(0.07, 0.08); + expect(map.getCenter().y).to.be.within(-0.08, 0.07); + expect(map.getPitch()).to.be.eql(45); + expect(map.getBearing()).to.be.eql(135); + expect(map.getZoom()).to.be.within(7, 8); + }); + + it('pitch 45, bearing -45', function () { + map.setCameraPosition({ + position: [0, 0, 10000], + pitch: 45, + bearing: -45, + }) + expect(map.getCenter().x).to.be.within(-0.08, 0.07); + expect(map.getCenter().y).to.be.within(0.07, 0.08); + expect(map.getPitch()).to.be.eql(45); + expect(map.getBearing()).to.be.eql(-45); + expect(map.getZoom()).to.be.within(7, 8); + }); + + it('pitch 45, bearing -135', function () { + map.setCameraPosition({ + position: [0, 0, 10000], + pitch: 45, + bearing: -135, + }) + expect(map.getCenter().x).to.be.within(-0.08, -0.07); + expect(map.getCenter().y).to.be.within(-0.08, -0.07); + expect(map.getPitch()).to.be.eql(45); + expect(map.getBearing()).to.be.eql(-135); + expect(map.getZoom()).to.be.within(7, 8); + }); + + it('position z', function () { + map.setCameraPosition({ + position: [0, 0, 100], + pitch: 0, + bearing: 0, + }) + expect(map.getCenter().x).to.be.eql(0); + expect(map.getCenter().y).to.be.eql(0); + expect(map.getPitch()).to.be.eql(0); + expect(map.getBearing()).to.be.eql(0); + expect(map.getZoom()).to.be.within(14, 15); + }) + }); + describe('containsPoint when pitching', function () { it('linestring', function () {