草庐IT

Cesium实现铁路仿真系统

Yo_5529 2023-09-14 原文

铁路模拟仿真实现

实现效果

train

内容比较多,只讲主要部分,详细内容可以参考代码,有不懂的欢迎讨论

初始化变量

这些变量下面都会用到

// 运动车厢的速度
let velocity = 30 // 速度,根据他来计算到达各个点的时间
// 当前目标点的位置
// var currentIndex = 1

// 每节车厢相对上一节车厢延时一定时间到达同一个位置
var delayTime = 13

// 存储所有运动中的实体对象
var dynamicEntities = []

// 运动模型数量
var dynamicNum = 5

// 每节铁轨的长度,用于计算两个点之前铺设多少节铁轨
var modelLength = 170

// 初始化dynamicEntitye
for (let i = 0; i < dynamicNum ; ++i) {
    let obj = {
        entity: null, // 实体对象
        property: new Cesium.SampledPositionProperty(), // 动态位置属性
        timeAndOrientationList: [],
        startTime: 0,
        endTime: 0
    }
    dynamicEntities.push(obj)
}

加载线路并获取位置

我们需要有一系列点路径坐标(火车运行的路径)。这里我从Google Eearth中绘制了一条线,然后导出为KML数据加载进来。

Google earth

通过加载的这条路径,我们需要获取路径中每个转折点的坐标信息。通过这些转折点,我们可以完成设置铁轨位置计算出模型实体每个时间点对应点位置

加载KML


// 初始化路径 设置带时间的路径
viewer.dataSources.add(Cesium.KmlDataSource.load(routerUrl,
    {
        camera: viewer.scene.camera,
        canvas: viewer.scene.canvas,
        clampToGround: true
    })
).then(dataSource => {
    // ... 加载好后获取改路径点坐标数组
    var router = dataSource.entities.getById('0129AA13ED12D2857AD0');
    var positions = router.polyline.positions._value
    viewer.flyTo(router)
    // createDynamicPositions(positions) // 计算带时间的路径
    // createDynamicEntity() // 根据动态路径创建模型实体
})

首先我们加载好路线后,就要获取改路线的坐标数组(每个转折点或顶点的位置)。

// 获取路径对象
var router = dataSource.entities.getById('0129AA13ED12D2857AD0');
// 获取对象中的坐标数组
var positions = router.polyline.positions._value

我们可以看一下这些数组的内容

IoR5O3

在这里可以看出来,这些坐标全是笛卡尔类型。同时可以知道我们总共有13个转折点

接下来两章是重点

加载铁轨

效果展示

Kkx93r

实现上面效果,这里我们需要做下面几步。

  • 计算每段路(两个点)之间的距离S
  • 设置每个铁轨的固定长度L
  • 计算每段路可以铺设多少个模型 num = S / L
  • 通过每段路两端的点的坐标,计算出这段中每个铁轨模型的位置

// 这个是每个模型的长度,在一开始的时候就定义了
// var modelLength = 170

function repeateModel(posCart1, posCart2) {
    // 需要摆放模型的数量
    // 模型的数量 = 两个点之间的长度 / 每个模型的长度
    let modelNum = parseInt(computeDistance(posCart1, posCart2) / modelLength)
    // 根据两个点的经纬度调整每个模型的方向
    let heading = computeOrientation(posCart1, posCart2)
    // 开始计算每个模型的位置
    for (var i = 1; i < modelNum; ++i) {
        // 求第i个点的位置。下面有介绍为什么这样写
        var mid = new Cesium.Cartesian3()
        Cesium.Cartesian3.add(
            Cesium.Cartesian3.multiplyByScalar(posCart1, i / modelNum, new Cesium.Cartesian3()),
            Cesium.Cartesian3.multiplyByScalar(posCart2, (modelNum - i) / modelNum, new Cesium.Cartesian3()),
            mid
        )
        // 计算出位置后,添加铁轨模型到Viewer中。同时调整模型的方向
        viewer.entities.add({
            position: mid,
            model: {
                uri: modelRailwayUrl,
                scale: 0.025
            },
            orientation: changeOrientation(mid, heading)
        })
    }

}

两个坐标之前第i的位置如何求

先看一下下面的一道数学题

bJgWdB

通过这道题,我们就可以写出下面代码,求出第i个点的位置了

Cesium.Cartesian3.add(
    Cesium.Cartesian3.multiplyByScalar(posCart1, i / modelNum, new Cesium.Cartesian3()),
    Cesium.Cartesian3.multiplyByScalar(posCart2, (modelNum - i) / modelNum, new Cesium.Cartesian3()),
    mid
)

模型方向问题

在上面代码中。我们经常要用到一个计算模型方向和改变模型方向的函数,那么为什么要计算模型的方向呢?

我们打开铁轨模型和系统自带的一些模型。看看他们的方向

使用下面命令调出查看方向的小工具

viewer.extend(Cesium.viewerCesiumInspectorMixin);
EUBOGy

可以看到,我们的模型的方向默认位置是朝向南方(红色是东方,绿色是北方)。而官网的模型方向默认是东方。根据官方对模型的描述

By default, the model is oriented upright and facing east. Control the orientation of the model by specifying a Quaternion for the Entity.orientation property. This controls the heading, pitch, and roll of the model.

可以看到,我们的模型方向是有问题。因此需要手动纠正。查阅很多方法,无法从模型本身入手。所以只能通过代码的方式来纠正方向。大概的思路是先计算出两个点的方向,然后在向东方偏移90度左右即可。

计算方向函数

function computeOrientation(posCart1, posCart2) {
    let heading = bearing(
        Cesium.Cartographic.fromCartesian(posCart1).latitude,
        Cesium.Cartographic.fromCartesian(posCart1).longitude,
        Cesium.Cartographic.fromCartesian(posCart2).latitude,
        Cesium.Cartographic.fromCartesian(posCart2).longitude
    )
    return heading
}
        // 计算两点之间的方向
function bearing(startLat, startLng, destLat, destLng) {
    startLat = Cesium.Math.toRadians(startLat);
    startLng = Cesium.Math.toRadians(startLng);
    destLat = Cesium.Math.toRadians(destLat);
    destLng = Cesium.Math.toRadians(destLng);

    let y = Math.sin(destLng - startLng) * Math.cos(destLat);
    let x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
    let brng = Math.atan2(y, x);
    let brngDgr = Cesium.Math.toDegrees(brng);
    return (brngDgr + 360) % 360;
}

改变模型的位置

function changeOrientation(position, degree) {
    var heading = Cesium.Math.toRadians(degree);
    var pitch = Cesium.Math.toRadians(0.0);
    var roll = Cesium.Math.toRadians(0.0);
    var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, pitch, roll));
    return orientation
}

加载运动的车头和车厢

这里我们需要了解一个知识。Cesium的Property机制总结.这篇文章中,我们可以看到一个属性SampledPositionProperty,它可以使用物体的运动。

move

它的实现代码如下

var property = new Cesium.SampledPositionProperty();

property.addSample(Cesium.JulianDate.fromIso8601('2019-01-01T00:00:00.00Z'), 
    Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0));

property.addSample(Cesium.JulianDate.fromIso8601('2019-01-03T00:00:00.00Z'), 
    Cesium.Cartesian3.fromDegrees(-114.0, 45.0, 300000.0));

blueBox.position = property;

它的原理是,Entity在不同的时间运动到不同的位置。因此我们的火车运动也是一样,在不同的时候运动到不同的位置即可。那么如何实现呢?

还是通过之前获取的铁轨路径数组,再计算到达每个转折点的时间。构成一个如下图所示的数据结构。

XzFZ1f

如何让模型运动起来也可以总结为下面这张图

Eg6FPk

比如4点的时候在position1位置,4.30的时候在position2位置。4.50的时候在position3位置。

那么现在时间点应该如何计算

我们设置一个速度变量V,然后计算两点的距离S。那么到达下一个的时间就是

time = S / V

因此实现代码如下(伪代码)

// 计算到下一个坐标所花费的时间
let time2Next = computeTime(datas[index], datas[index + 1])
// 计算到达改点的时刻
let time = totalTime + time2Next
// 将时刻+位置信息写入到模型的位置变量中
dynamicEntity.property.addSample(
    Cesium.JulianDate.addSeconds(start, time, new Cesium.JulianDate()),
    position
)
// 计算总花费时间
totalTime += time2Next

这里又有新的问题。

我们需要好几节车厢一起运动,如何实现呢?

使用延时启动,就是每一个车厢到达一个转折点的时间都比上一节车厢晚一段时间。如下图所示,不同的车厢在不同的时间点的位置不一样。

Lp33qj
let time = totalTime + delayTime * i

我们看一下实现效果,车厢是一节在一节的后面出现的

train

通过代码实现

function createDynamicPositions(positions) {
    var length = positions.length
    var totalTime = 0// 跑完全部路程的时间
    // 遍历铁轨路径的每个转折点
    positions.forEach((position, index, datas) => {
        // 在每个路径转角处创建一个Point对象
        CreatePoint(position, index)
        if (index + 1 < length) {
            // 计算一个点到另一点需要到时间
            let time2Next = computeTime(datas[index], datas[index + 1])
            // 计算两个转角点的方向
            let orientation = computeOrientation(datas[index], datas[index + 1])
            // 为每个车厢模型设置 时间+位置
            dynamicEntities.forEach((dynamicEntity, i) => {
                // 这里实现了 每个模型都相对于之前都个模型延时一定时间进行启动
                let time = totalTime + delayTime * i
                dynamicEntity.property.addSample(
                    Cesium.JulianDate.addSeconds(start, time, new Cesium.JulianDate()),
                    position
                )
                // 记录每个模型分别达到一个点的时间、方向、位置
                let obj = {
                    time: totalTime, // 到达下一个点需要耗费的时间,它是一个数值,不是一个时间点
                    position: position,
                    orientation: orientation
                }

                // 计算开始时间
                if (index === 0) {
                    dynamicEntity.startTime = Cesium.JulianDate.addSeconds(start, time, new Cesium.JulianDate())
                }

                // 计算最后一个时间
                if (index + 2 === length) {
                    dynamicEntity.endTime = Cesium.JulianDate.addSeconds(start, time, new Cesium.JulianDate())
                }
                // 将计算得到的 时间+位置 属性存储到每个实体中
                dynamicEntity.timeAndOrientationList.push(obj)

            })

            totalTime += time2Next

        }
    });

这里我们发现我们也计算了每个模型的方向,为什么要计算方向呢?在上面设置铁轨的时候讲到了,因为我们的模型方向默认是有点问题的。默认朝向南方,因此需要手动调整方向,我们需要自己写一个方法,判断到了转角处进行转向。(如果是模型默认朝向东方的话,则不需要使用该方法,直接使用自带的一种方法,具体方法后面再谈)

如何实现到了转角处自动转向呢?我们在刚刚上一步的时候记录了每个模型到达某个位置的时候是在是什么时间点。因此只需要判断,当前模型运行的时间是否到了转角的时间点,到了的话就开始转向,而这个方向我们同时也在上一步的时候存储到每个实体中

let obj = {
    time: totalTime, // 到达下一个点需要耗费的时间,它是一个数值,不是一个时间点
    position: position,
    orientation: orientation
}

监听当前时间点并转向的代码如下

viewer.clock.onTick.addEventListener((clock) => {
    // 判断每个运动的模型当前是否到了转向时间
    dynamicEntities.forEach(dynamicEntity => {
        // 计算每个运动的模型与模型的开始时间差
        let timeOffset = Cesium.JulianDate.secondsDifference(clock.currentTime, dynamicEntity.startTime);
        // 判断是否达到转向的时间点
        dynamicEntity.timeAndOrientationList.forEach((obj, index, array) => {
            if (timeOffset >= obj.time && timeOffset <= array[index + 1].time) {
                // 177在第一条铁轨是一个好的角度
                dynamicEntity.entity.orientation = changeOrientation(obj.position, obj.orientation + 180)
            }
        })
    })

如果模型的方向是正确的,只需要在创建模型实体对象的时候,指定该属性即可

orientation: new Cesium.VelocityOrientationProperty(dynamicEntity.property),

目前还有下面问题暂时无法解决

  • 各个模型之间的衔接不好

经过测试如果模型的方向是正确的话,那么就可以解决这个问题。所以可以从模型入手,更改模型的默认方向,使它默认朝向东方,但是自己一直没有找到如何编辑GLB模型。所以暂时无解。

有关Cesium实现铁路仿真系统的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  7. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  10. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

随机推荐