说明
基于arcgis api for js 4.17
在arcgis api for js中实现三维飞行,同时视角要跟随飞行方向变化。实现此功能,主要使用Camera对象和goTo方法。
Camera对象主要包含四个属性:fov(视角场,默认55度);heading;tilt和position。其中heading代表偏北角或方位角,当视角朝向正北时为0度,顺时针旋转增加,在0°~360°之间。tilt代表俯仰角,为90度时平行于水平面,0度时垂直俯视,180度时垂直仰视。position代表位置,有三个属性,一般为经度、纬度、高度。其中,要实现视角的变化,需要根据起点和终点的坐标来计算heading和tilt。
View对象的goTo()方法返回一个Promise对象,一个goTo执行一个动作,在前一个动作成功完成后,在.then()中再调用goTo()执行下一个动作。通过这种Promise链式调用的方式实现连续的运动。
代码
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://js.arcgis.com/4.17/esri/themes/light/main.css"/>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#viewDiv {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="viewDiv"></div>
<script src="https://js.arcgis.com/4.17/"></script>
<script>
require(["esri/Map",
"esri/views/MapView",
"esri/views/SceneView",
"esri/Camera",
"esri/Graphic",
"esri/layers/GraphicsLayer"], function (Map, MapView, SceneView, Camera, Graphic, GraphicsLayer) {
let map = new Map({
basemap: "hybrid",
ground: "world-elevation"
})
// const paths = [
// [-0.3, 51.4879, 3000],
// [-0.4, 50.4879, 10000],
// [-0.178, 49.4879, 1000],
// [-0.5, 51.4879, 1000],
// [-0.078, 51.4879, 8000],
// [-0.178, 52.4879, 5000]
// ]
const paths = [
[120, 31, 3000],
[119, 31, 13000],
[119, 31, 40000],
[119, 32, 10000],
[121, 32, 1000],
[119, 33, 8000]
]
let view = new SceneView({
map: map,
container: "viewDiv",
viewingMode: 'global', // local, global
camera: {
position: {
x: paths[0][0],
y: paths[0][1],
z: paths[0][2],
spatialReference: { wkid: 4326 }
}
}
})
let gLayer = new GraphicsLayer()
map.add(gLayer)
// 创建图形
function createGeometry(paths) {
let polyline = {
type: 'polyline',
paths: paths
}
let polylineGraphic = new Graphic({
geometry: polyline,
symbol: {
type: 'simple-line',
color: [226, 119, 40],
width: 4
}
})
gLayer.add(polylineGraphic)
paths.forEach(item => {
let point = {
type: 'point',
x: item[0],
y: item[1],
z: item[2]
}
let pointGraphic = new Graphic({
geometry: point,
symbol: {
type: "simple-marker",
color: "blue",
size: 12,
outline: {
width: 1,
color: "white"
}
}
})
gLayer.add(pointGraphic)
})
}
// 递归函数, 实现连续飞行方法
// 每次执行两次view.goTo()方法,第一次会将视角转向,第二次转向后会前进到指定位置
function fly(i){
if(i+1 == paths.length) {
return
}
let startPoint = paths[i]
let endPoint = paths[i+1]
let heading = calcHeading(startPoint[0], startPoint[1], endPoint[0], endPoint[1])
let tilt = calcTilt(startPoint[0], startPoint[1], startPoint[2], endPoint[0], endPoint[1], endPoint[2])
console.log('拐弯', i)
console.log(startPoint, endPoint)
console.log(heading, tilt)
let cam = view.camera.clone()
cam.heading = heading
cam.tilt = tilt
cam.position = {
longitude: startPoint[0],
latitude: startPoint[1],
z: startPoint[2],
spatialReference: { wkid: 4326 }
}
view.goTo(cam, {
speedFactor: 1,
easing: 'linear'
})
.then(() => {
console.log('前进', i)
cam.position = {
longitude: endPoint[0],
latitude: endPoint[1],
z: endPoint[2],
spatialReference: { wkid: 4326 }
}
return view.goTo(cam, {
speedFactor: 0.2,
easing: "linear"
})
})
.then(() => {
setTimeout(() => {
i++
fly(i)
}, 1000)
})
}
view.when(() => {
createGeometry(paths)
view.watch('camera', e => {
// console.log(e.tilt, e.heading, e.position.z)
})
fly(0)
})
// 计算距离
function calcDistance(lon1, lat1, lon2, lat2) {
let radlat1 = lat1 * Math.PI / 180.0
let radlat2 = lat2 * Math.PI / 180.0
let a = radlat1 - radlat2
let b = lon1 * Math.PI / 180.0 - lon2 * Math.PI / 180.0
let distance = 2 * 6378137.0 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radlat2) * Math.cos(radlat2) * Math.pow(Math.sin(b / 2), 2)))
return distance
}
// 计算偏北角,0指向正北,90指向正东,顺时针旋转
function calcHeading(lon1, lat1, lon2, lat2) {
let radlat1 = lat1 * Math.PI / 180.0
let radlat2 = lat2 * Math.PI / 180.0
let radlon1 = lon1 * Math.PI / 180.0
let radlon2 = lon2 * Math.PI / 180.0
let y = Math.sin(radlon2 - radlon1) * Math.cos(radlat2)
let x = Math.cos(radlat1) * Math.sin(radlat2) - Math.sin(radlat1) * Math.cos(radlat2) * Math.cos(radlon2 - radlon1)
let bearing = Math.atan2(y, x) * 180.0 / Math.PI
return bearing < 0 ? bearing + 360.0 : bearing
}
// 计算俯仰角, 90度时平行于水平面,0度时自上向下垂直俯视, 180度时自下向上仰视
function calcTilt(lon1, lat1, alt1, lon2, lat2, alt2) {
let distance = calcDistance(lon1, lat1, lon2, lat2)
let angle = Math.atan2((alt2 -alt1), distance) * 180.0 / Math.PI
let tilt = angle + 90
return tilt
}
})
</script>
</body>
</html>