热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Cesium轨迹漫游

Cesium中,轨迹漫游的核心是借助CZML格式,CZML是Cesium团队制定的一种用来描述动态场景的JSON架构语言,可以用来描述点、线、多边形、体、模型及其他图元,同时定义它

Cesium中,轨迹漫游的核心是借助CZML格式,CZML是Cesium团队制定的一种用来描述动态场景的JSON架构语言,可以用来描述点、线、多边形、体、模型及其他图元,同时定义它们是怎样随时间变化的,参考CZML Structure · AnalyticalGraphicsInc/czml-writer Wiki (github.com)

我这里放一个简单的模板吧

 

[
{
"id": "document",
"version": "1.0"
},
{
"id": "pathRoamingEntity",
"availability": "2012-08-04T10:00:00Z/2012-08-04T20:00:00Z",
"model": {
"scale": 1,
"minimumPixelSize": 100,
"maximumScale": 20
},
"path": {
"material": {"solidColor": {"color": {"rgba": [255, 255, 0, 255]}}},
"width": [{"number": 5.0}],
"show": [{"boolean": false}],
"resolution": 5.0
},
"orientation": {
"velocityReference": "#position"
},
"viewFrom": {
"cartesian": [-2080, -1715, 779]
},
"position": {
"interpolationAlgorithm": "LINEAR",
"epoch": "2012-08-04T10:00:00Z",
"forwardExtrapolationType": "HOLD",
"cartographicDegrees": []
}
}
]

 

CZML 不过多赘述,我们先说思路,其实思路不难,配置好 CZML,加入到 dataSoures 中,开启动画即可

我们先定义一段路径:

let ps = [
[
119.44037341293323, 35.34197106899855, 5.872732096309598],
[
119.44252948098223, 35.34223901339689, 6.31711015359973],
[
119.4560550425358, 35.34202148007459, 22.906707659456394],
[
119.45610614546445, 35.32762691608659, 3.0852594116911622],
]

将线添加到场景中,注意 地理坐标 转 投影坐标

let car3 = formCartographicArrS(ps)
// 添加线
let pathEntity = new Cesium.Entity({
polyline: {
// 注意,此处position为笛卡尔坐标系
positions: car3,
width:
4,
clampToGround:
true,
arcType: Cesium.ArcType.RHUMB,
}
})
viewer.entities.add(pathEntity)

此时,若我们的场景中有地形,则要修改一下高度,比如我们要模型离地100米飞行

// 清洗height,改为相对高度,配合 heightReference
let height = 0
ps.forEach((v, k ,arr)
=>{
arr[k][
2] = 0 + height
})

在上述的CZML中,有很多必要属性我们没有添加,比如模型,比如路径

此处主要干了三个事:

1.添加模型信息

2.添加路径信息

3.计算速度,修改时间

//添加模型
czml[1].model.gltf = "./CesiumMilkTruck/CesiumMilkTruck.glb"
czml[
1].model.scale = 0.01
// CZML中通过时间来控制速度,我们先定义一个起始时间并复制一份当前时间,并记录一份结束时间
const startTime = Cesium.JulianDate.fromIso8601('2012-08-04T10:00:00Z');
let currentTime
= startTime.clone();
let lastPosition
= null;
// 为了让模型保持匀速运动,我们需要手动计算时间,此处借助 turfJS 库
let speed = 50
ps.forEach(v
=> {
if(lastPosition){
// 对于起始点,我们直接传入起始时间即可,此处为非起始点逻辑
let from = turf.point(lastPosition);
let to
= turf.point(v);
// 计算两点间长度
let distance = turf.distance(from, to, {units: 'meters'});
// 计算新时间
currentTime = Cesium.JulianDate.addSeconds(currentTime, Math.ceil(distance / speed), currentTime);
}
// 添加路径,注意坐标为经纬度,且格式为 [时间节点,经度, 维度, 高],此处时间节点就是用来计算速度的,每一个线段起始时间节点与终止时间节点定义了当前线段的速度
czml[1].position.cartographicDegrees.push(Cesium.JulianDate.toIso8601(currentTime))
czml[
1].position.cartographicDegrees.push(v[0])
czml[
1].position.cartographicDegrees.push(v[1])
czml[
1].position.cartographicDegrees.push(v[2])
lastPosition
= v
})
// 根据上述计算的时间修改 availability
czml[1].availability = `${startTime}/${currentTime}`

添加 dataSourecs,是一个异步Promise,回调参数为我们传入的dataSource

同时注意参数,该方法允许我们传入 Promise,所以对于下述静态方法定义的 CZML 数据源无需回调,直接将 Promise 作为参数传入即可

// 添加 CZML
viewer.dataSources.add(
Cesium.CzmlDataSource.load(czml)
).then(c
=>{
...
})

到此时其实已经完成大部分了,但此时我们的模型可能还没有动起来(受 clock 影响),所以在回调中我们要做一些工作

// c为回调参数
//
获取Entity
let e = c.entities.getById("pathRoamingEntity")
// 设置高度为贴地相对高度
e.model.heightReference = Cesium.HeightReference.RELATIVE_TO_GROUND
viewer.clock.multiplier
= 1
// 让时间动起来
viewer.clock.shouldAnimate = true;

接下来我们希望视角随着模型移动,视角随着模型移动有两种方法

1.使用 trackedEntity

在 viewer 中,提供了一个非常便捷的方法,有一个属性 trackedEntity,可以使当前的相机锁定一个Entity

配合 CZML 中的 viewFrom,允许我们设置一个相对的投影坐标(笛卡尔)作为初始视角,是以当前 CZML 做一个偏移

即可配置初始相机方向同时跟踪

 

 

viewer.trackedEntity = c.entities.getById("pathRoamingEntity")

2.上述方法虽然完成了跟踪,但是实际我们并没有能够使相机随着模型运动的方向随时改变,所以第二种方法是使用 addEventListener

如下所示,其实实现方法有很多种,大同小异,此处采用的是 Camera 中的 lookAt 方法

我们先看一下lookAt,lookAt 要求我们提供两个参数,目标位置 和 距离目标的偏移,目标位置我们可以直接记录,目标偏移量就需要我们手动算一下了,

大致思路就是,根据前一个点和后一个点算出 heading 朝向,因为 heading 代表 Z 轴旋转,所以比较重要,pitch 代表 Y 轴朝向,我们可以自己选择一个合适的角度,注意,俯角是负数

// 前一个点
let prePoint = null
viewer.scene.postRender.addEventListener(()
=> {
if (e && viewer.clock.shouldAnimate) {
// 获取当前时间的位置
let curPoint = e.position.getValue(viewer.clock.currentTime)
if(prePoint){
// 计算 heading
let heading = getHeading(prePoint, curPoint)
// 计算 pitch
let pitch = Cesium.Math.toRadians(-30.0);
let range
= 100;
viewer.camera.lookAt (
curPoint,
new Cesium.HeadingPitchRange(heading, pitch, range)
);
}
// 当前点在下一次渲染时为前一个点
prePoint = Cesium.Cartesian3.clone(curPoint)
}
});

function getHeading(pointA, pointB){
//建立以点A为原点,X轴为east,Y轴为north,Z轴朝上的坐标系
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
//向量AB
const positiOnvector= Cesium.Cartesian3.subtract(pointB, pointA, new Cesium.Cartesian3());
//因transform是将A为原点的eastNorthUp坐标系中的点转换到世界坐标系的矩阵
//AB为世界坐标中的向量
//因此将AB向量转换为A原点坐标系中的向量,需乘以transform的逆矩阵。
const vector = Cesium.Matrix4.multiplyByPointAsVector(Cesium.Matrix4.inverse(transform, new Cesium.Matrix4()), positionvector, new Cesium.Cartesian3());
//归一化
const direction = Cesium.Cartesian3.normalize(vector, new Cesium.Cartesian3());
//heading
const heading = Math.atan2(direction.y, direction.x) - Cesium.Math.PI_OVER_TWO;
return Cesium.Math.TWO_PI - Cesium.Math.zeroToTwoPi(heading);
}

注意:视角追踪有一个问题,当地形起伏过大时,相机可能飞入地形下面!

到此,轨迹漫游算是结束了!后续我还会写一些之前做 SDK 时的一些功能,小弟目前在读GIS研究生一枚,代码中不足之处,欢迎各位大佬指正!



推荐阅读
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了GregorianCalendar类的基本信息,包括它是Calendar的子类,提供了世界上大多数国家使用的标准日历系统。默认情况下,它对应格里高利日历创立时的日期,但可以通过调用setGregorianChange()方法来更改起始日期。同时,文中还提到了GregorianCalendar类为每个日历字段使用的默认值。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
author-avatar
yangjiao1985
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有