Cesium实现铁路仿真系统

铁路模拟仿真实现

实现效果

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模型。所以暂时无解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348