三维地图Cesium,加载一个模型,模型沿着给定的一组经纬度路线移动

目录

实现效果

实现思路

功能点

选择移动路线

加载模型和移动路线

重新运行

指定位置(经纬度点)开始移动

视角切换

到站提示

运行

停止

联动接口

完整代码

html

js逻辑

trainOperation.js

sourceData.js

gitee仓库项目代码

疑问解答


实现效果

三维地图Cesium,加载一个模型,模型沿着给定的一组经纬度

实现思路

  1. 车的运行使用czml文件的特性来加载小车模型和小车运行的固定路线,同时定义它们是怎么随时间的变化而变化的。
  2. 路径长度已知,运行速度已知,结合三维cesium的时钟,就可以知道火车移动完成这段路线需要的时间。
  3. 改变运行速度或改变当前移动的位置(经纬度点)发生变化,就重新计算时间,然后重新加载czml文件。

功能点

目前已经实现的功能:

1、加载指定可自定义火车模型

2、移动的路线可以自定义修改,也可以在地图上自己选择移动路线(取路线代码已有)

3、给定指定的移动速度

4、视角切换(第一视角、自由视角、跟随视角)

5、根据指定的经纬度点为起点开始移动

6、停止移动、开始移动、到站提示、从起点重新移动、默认相机的视角

7、根据实际业务,接口获取获取的速度、移动路线、车的状态(前进、静止、后退)来控制模型的状态

选择移动路线

1、方法:at3DMapGetPointTakingRoute()

2、初始化成功后,执行at3DMapGetPointTakingRoute函数

TO.at3DMapGetPointTakingRoute()

3、 打开页面,用鼠标点击需要的路线,页面会以红点的样式进行标注,每标注一个点,控制台会以数组的形式打印所有标注的点

4、 把打印数据赋值给routePath变化,完成了路线的数据采集

// 路线
export const routePath = [[116.44411704836098,39.95279202225133],[116.44807033296253,39.956431604742875],[116.45386442263022,39.952900124343756],[116.45546340934312,39.951275636111355],[116.45567209160625,39.9494096918938],[116.4556296095857,39.94616403625391],
]

加载模型和移动路线

加载模型和路线的方式有好几种:

    1)使用实体单独加载模型和移动路线

    2)使用czml格式描述它们两个的关系

我们这里采用第二种,之前打算用第一种,加载起来是很方便,但是需要计算模型与移动路线的关系等细节,就先不打算了。。。

具体如何使用czml文件描述模型和移动路线的关系,网上有很多,可以自行查阅。

let testModel = window.location.origin + '/Apps/SampleData/models/GroundVehicle/GroundVehicle.glb';
export function modelCzml(useRoutePath,startTime,endTime){let minx = startTime+'/'+endTime;return [{"id": "document","version": "1.0"},{"id": "Vehicle","availability": minx ,"label": {"fillColor": [{"rgba": [0, 0, 0, 255]}],"font": "bold 10pt Segoe UI Semibold","horizontalOrigin": "CENTER","outlineColor": {"rgba": [0, 0, 0, 255]},"pixelOffset": {"cartesian2": [40.0, 50.0]},"scale": 1.0,"show": [{"boolean": true}],"style": "FILL","text": "测试火车移动","verticalOrigin": "CENTER"},"model": {"gltf": testModel,"minimumPixelSize": 120,"maximumScale": 50},"orientation": {"velocityReference": "#position"},"viewFrom": {// "cartesian": [300, 20, 300]"cartesian": [500, 2000, 779]},"properties": {"fuel_remaining": {"epoch": startTime,"number": [0, 22.5, 1500, 21.2]}},"path": {"material": {"solidColor": {"color": {"rgba": [255, 255, 0, 255]}}},"width": [{"number": 4.0}],"show": [{"boolean": true}]},"position": {"interpolationAlgorithm": "LAGRANGE","interpolationDegree": 1,"wrap": false,"epoch": startTime,"cartographicDegrees": useRoutePath// useRoutePath:时间、经度、纬度、高度加载显示的格式为[0, 102.23404378554466, 27.825736605050523, 2500,10, 102.23691954070244, 27.82887625908256, 2500,]}}]
}

重新运行

1、使用trainLoadRun()函数

2、该方法接收三个参数:

1)targetPoint:起点的经纬度,也就是路线起点

2)currentVelocity:移动速度

3)tractionMethod:火车移动状态(前进 后退 静止)

trainLoadRun(targetPoint,currentVelocity=this.currentVelocity,tractionMethod=this.tractionMethod){// 如果当前火车是后退,判断当前火车是否在运行路线上,如果在,则找到当前火车所在点,并反转火车路线数组let that = this;if (that.latAndLonPosition(targetPoint).exists) {let index = that.latAndLonPosition(targetPoint).indexthat.byLonAndLatUpdateTrainPosition(index, currentVelocity, tractionMethod)} else {console.error('当前经纬度不在轨迹运行路线上:',targetPoint)}}

指定位置(经纬度点)开始移动

也是用targetPoint方法,改变下targetPoint的位置

注意:给的经纬度点要是不在移动路线上是不做处理的

视角切换

1、第一视角

    1)相机一直在相对小车指定的位置

    2)该模式无法通过鼠标或者键盘操作地图视角

    3)路线方向变化,车的头部以及方向也随着变化

firstPerspectiveTow() {let that = this;try {let center = that.trainModel.position.getValue(that.viewer.clock.currentTime);let orientation = that.trainModel.orientation.getValue(that.viewer.clock.currentTime)let transform = that.Cesium.Matrix4.fromRotationTranslation(that.Cesium.Matrix3.fromQuaternion(orientation), center);// viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(-100, 0, 50))that.viewer.camera.lookAtTransform(transform, new that.Cesium.Cartesian3(-60, 0, 50))} catch (e) {console.log('err in firstPerspectiveTow function')}}

2、自由视角

    1)将viewer.trackedEntity的值为:undefined即可

viewer.trackedEntity = undefined;

3、跟随视角

viewer.trackedEntity = trainOpera.trainModel;

到站提示

根据当前已经移动的距离与总路线的距离作比较,总路线减去已经移动的路线,误差值在10米为到站,当然,这个误差值可以自己定义

arriveAtStation(clock,callBack){let that = this;let cDistance = that.walkedThrough();let diff = Math.abs(that.fullRoutePathDistance.total - cDistance);if (that.tractionMethod == '后退'){diff = that.fullRoutePathDistance.total - diff}if (diff < 9){// 可以在这里分发到站广播callBack && callBack();clock.shouldAnimate = false;that.arriveAatStation()}}

运行

停止

设置时钟的shouldAnimate为false即可

clock.shouldAnimate = false;

联动接口

或者的运行速度,运行状态(前进、静止、后退)等信息,通过接口的形式可以进行控制

接口数据更新的时间根据自己的事迹情况来定

如:获取接口数据的方法

// 通过接口实时获取火车运行的相关信息getTrainInfo() {let that = this;let res = {currentlonLat:'112.938333,40.332444',tractionMethod:'前进',speed:23}; //火车当前前进方式,3种状态:前进 后退 静止if (!res || res === '{}') return false;let currentSpeed;if (res.speed && res.speed.includes('km/h')){currentSpeed = res.speed.split('km/h')[0]  //火车运行速度}// 获取当前火车所在经纬度let targetPoint;if (res.currentlonLat){let point = res.currentlonLat.split(',');targetPoint = {lon: point[0],lat: point[1]}} else {console.log('经纬度坐标丢失');return false;}if (res.tractionMethod == '静止' || currentSpeed == '0'){that.tractionMethod = res.tractionMethod;that.viewer.clock.shouldAnimate = false;return false;}// 如果页面火车已经到了,但是接口没有返回"静止"状态,前端做停止运行动作if (window.isEmit && res.tractionMethod != '静止'){window.TO.viewer.clock.shouldAnimate = false;return false;} else {window.isEmit = false;}that.viewer.clock.shouldAnimate = true;// 运行状态不一样,要重新计算if (that.tractionMethod != res.tractionMethod){that.currentVelocity = currentSpeed;that.tractionMethod = res.tractionMethod;that.trainLoadRun(targetPoint,that.currentVelocity,that.tractionMethod)} else {// 运行的速度不变,不做处理if (that.currentVelocity == currentSpeed){return false;}let diff = Math.abs(Number(currentSpeed) - Number(that.currentVelocity));if (diff < 1 ){return false;}that.currentVelocity = currentSpeed;that.tractionMethod = res.tractionMethod;that.trainLoadRun(targetPoint,that.currentVelocity,that.tractionMethod)}}

可以在init()初始化的时候,用定时器的方式去更新数据

init(){let that = this;that.getTrainInfo();window.trainTimer = window.setInterval(function (){that.getTrainInfo && that.getTrainInfo();}, 4000);that.loadComputeData(that.routePath, that.currentVelocity, that.trainRun);that.viewer.clock.onTick.addEventListener(function (clock){that.handler(clock,that)});}

完整代码

html

<!DOCTYPE html>
<!--suppress ALL -->
<html lang="en">
<head><!-- Use correct character set. --><meta charset="utf-8"/><!-- Tell IE to use the latest, best version. --><meta content="IE=edge" http-equiv="X-UA-Compatible"/><!-- Make the application on mobile take up the full browser screen and disable user scaling. --><meta content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" name="viewport"/><title>Hello World!</title><script src="../Build/Cesium/Cesium.js"></script><style>@import url(../Build/Cesium/Widgets/widgets.css);html, body, #cesiumContainer {width: 100%;height: 100%;margin: 0;padding: 0;overflow: hidden;}.btn{position: fixed;top: 5px;left: 10px;z-index: 9999;display: flex;align-items: center;}.btn .btn-item{margin: 0 4px;}</style>
</head>
<body>
<div class="btn"><div style="display: flex;align-items: center"><span style="font-size: 14px;color: white">当前速度:</span><input value="20" placeholder="当前速度" id="currentSuDu" style="width: 100px;height: 20px"></div><button class="btn-item" id="runBtn">运行</button><button class="btn-item" id="stopBtn">停止</button><button class="btn-item" id="restart">重新开始</button><button class="btn-item" id="startAtSpecifiedLocation">指定位置开始</button><button class="btn-item" id="firstViewBtn">第一视角</button><button class="btn-item" id="freeViewBtn">自由视角</button><button class="btn-item" id="followViewBtn">跟随视角</button>
</div>
<div id="cesiumContainer"></div>
<script type="module">import { routePath } from './trainOperation/sourceData.js';import TrainOpera from './trainOperation/trainOperation.js';let Cesium = window.Cesium;Cesium.Ion.defaultAccessToken = '你自己的token'const viewer = new Cesium.Viewer("cesiumContainer", {});window.viewer = viewer;let TO = new TrainOpera(window.viewer.clock,routePath);TO.init();TO.addPolyline(TO.routePath);window.TO = TO; // 存到全局对象TO.at3DMapGetPointTakingRoute();
</script>
</body>
</html>

js逻辑

trainOperation.js
// noinspection LanguageDetectionInspection
import { modelCzml,operaDomStyle } from './sourceData.js';function operaDom(trainOpera,clock){let viewer = window.viewer;operaDomStyle()// 获取按钮元素let currentSuDu = document.getElementById('currentSuDu');let runBtn = document.getElementById('runBtn');let stopBtn = document.getElementById('stopBtn');let startAtSpecifiedLocation = document.getElementById('startAtSpecifiedLocation');let restart = document.getElementById('restart');let firstViewBtn = document.getElementById('firstViewBtn');let freeViewBtn = document.getElementById('freeViewBtn');let followViewBtn = document.getElementById('followViewBtn');// 添加点击事件处理程序currentSuDu.onblur = function (){let dom = document.getElementById('currentSuDu');if (!dom) return;trainOpera.currentVelocity = this.value;console.log('当前速度:',trainOpera.currentVelocity+'千米每小时');}runBtn.addEventListener('click', function (){console.log('运行。。。')trainOpera.trainRun()});stopBtn.addEventListener('click', function () {console.log('暂停运行。。。')clock.shouldAnimate = false;});startAtSpecifiedLocation.addEventListener('click', function () {console.log('指定位置开始');let targetPoint = {lon:116.44807033296253,lat:39.956431604742875};trainOpera.trainLoadRun(targetPoint)});restart.addEventListener('click', function () {console.log('从新头开始运行');let targetPoint = {lon:trainOpera.originalRoute[0][0],lat:trainOpera.originalRoute[0][1]};trainOpera.trainLoadRun(targetPoint)});firstViewBtn.addEventListener('click', function () {console.log('已切换为第一视角')clock.firstPerspective = true; // 控制第一视角viewer.trackedEntity = trainOpera.trainModel;});freeViewBtn.addEventListener('click', function () {console.log('已切换为自由视角')clock.firstPerspective = false; // 控制第一视角// 关闭视角跟随viewer.trackedEntity = undefined;});followViewBtn.addEventListener('click', function () {console.log('已切换为跟随视角')clock.firstPerspective = false;viewer.trackedEntity = trainOpera.trainModel;});
}export default class TrainOpera {constructor(clock,routePath) {window.isEmit = false; // 控制火车到站后,是否重新运行this.clock = clock;this.originalRoute = routePath;this.camera = window.viewer.camera;this.Cesium = window.Cesium;this.viewer = window.viewer;this.routePath = this.addInterpolation(routePath);operaDom(this,clock);clock.firstPerspective = false; // 默认不做第一视角}originalRoute = null; // 运行的路线,经纬度trainModel = null; // 实体模型startTime = '2023-10-27T12:00:00Z'; // 开始运行时间currentVelocity = 20; //当前速度53千米每小时tractionMethod = '前进'   //火车当前前进方式,3种状态:前进 后退 静止CameraPositionInfo = {}; // 当前的相机位置参数newRoutePathIndex = 0; // 记录当前从那个短路开始计算新的数据finishNeedTime = null;// 根据当前的速度运行,到终点需要多少时间wagonNumber = '测试火车移动'; // 车号discrepancyNum = 37; // 时钟秒速相差实际的37秒,不知道为什么。。。?clock.currentTime.secondsOfDay - 37才是现在时钟运行的秒速时间useRoutePath = []; // 全局的加载好的路线dataSource = null; // 当前加载的czmlonTickEvent = null;timer = nullfullRoutePathDistance = {  // 当前路程总长,点与点之间的距离多少米total: 0, // 总的距离distance:[],positionBetween:[]}// 记录火车轨迹和时间点saveLastPathAndTime = {timer:[],lanLat:[]};init(){let that = this;// that.getTrainInfo();// window.trainTimer = window.setInterval(function (){//     that.getTrainInfo && that.getTrainInfo();// }, 4000);that.loadComputeData(that.routePath, that.currentVelocity, that.trainRun);that.viewer.clock.onTick.addEventListener(function (clock){that.handler(clock,that)});}restartTrainRun(tractionMethod){// 火车到站后监听重新启动if (tractionMethod == '静止'){window.TO.viewer.clock.shouldAnimate = false;}if (!window.isEmit) return;if (tractionMethod == window.TO.tractionMethod) return;window.isEmit = false;console.log('restartTrainRun');window.TO.timer = window.setInterval(function (){window.TO && window.TO.getTrainInfo && window.TO.getTrainInfo();}, 3000);}// 火车到站arriveAatStation(){let that = this;window.isEmit = true;console.log('The train has arrived at the station');// that.viewer.clock.currentTime = new that.Cesium.JulianDate(that.viewer.clock.stopTime.dayNumber,that.viewer.clock.stopTime.secondsOfDay - that.discrepancyNum);window.clearInterval(that.timer);window.clearInterval(window.TO.timer);that.viewer.clock.shouldAnimate = false;alert('火车到站')}// 获取指定范围内的随机数getRandomInt(min,max){return Math.floor(Math.random() * (max - min +1)) + min;}// 通过接口实时获取火车运行的相关信息getTrainInfo() {return ;let that = this;let res = {currentlonLat:'112.938333,40.332444',tractionMethod:'前进',speed:23}; //火车当前前进方式,3种状态:前进 后退 静止if (!res || res === '{}') return false;let currentSpeed;if (res.speed && res.speed.includes('km/h')){currentSpeed = res.speed.split('km/h')[0]  //火车运行速度}// 获取当前火车所在经纬度let targetPoint;if (res.currentlonLat){let point = res.currentlonLat.split(',');targetPoint = {lon: point[0],lat: point[1]}} else {console.log('经纬度坐标丢失');return false;}if (res.tractionMethod == '静止' || currentSpeed == '0'){that.tractionMethod = res.tractionMethod;that.viewer.clock.shouldAnimate = false;return false;}// 如果页面火车已经到了,但是接口没有返回"静止"状态,前端做停止运行动作if (window.isEmit && res.tractionMethod != '静止'){window.TO.viewer.clock.shouldAnimate = false;return false;} else {window.isEmit = false;}that.viewer.clock.shouldAnimate = true;// 运行状态不一样,要重新计算if (that.tractionMethod != res.tractionMethod){that.currentVelocity = currentSpeed;that.tractionMethod = res.tractionMethod;that.trainLoadRun(targetPoint,that.currentVelocity,that.tractionMethod)} else {// 运行的速度不变,不做处理if (that.currentVelocity == currentSpeed){return false;}let diff = Math.abs(Number(currentSpeed) - Number(that.currentVelocity));if (diff < 1 ){return false;}that.currentVelocity = currentSpeed;that.tractionMethod = res.tractionMethod;that.trainLoadRun(targetPoint,that.currentVelocity,that.tractionMethod)}}trainLoadRun(targetPoint,currentVelocity=this.currentVelocity,tractionMethod=this.tractionMethod){// 如果当前火车是后退,判断当前火车是否在运行路线上,如果在,则找到当前火车所在点,并反转火车路线数组let that = this;if (that.latAndLonPosition(targetPoint).exists) {let index = that.latAndLonPosition(targetPoint).indexthat.byLonAndLatUpdateTrainPosition(index, currentVelocity, tractionMethod)} else {console.error('当前经纬度不在轨迹运行路线上:',targetPoint)}}byLonAndLatUpdateTrainPosition(index,velocity, method){let that = this;that.newRoutePathIndex = index;that.currentVelocity = velocity;let useRoutePath = []if (method == '前进') {useRoutePath = that.routePath.slice(index);that.loadComputeData(useRoutePath,that.currentVelocity,that.trainRun);}if (method == '后退') {useRoutePath = that.routePath.slice(0,index).reverse();that.loadComputeData(useRoutePath,that.currentVelocity,that.trainRun);}if (method == '静止')  {window.viewer.clock.shouldAnimate = false;clearInterval(that.timer)}}// 判断一个经纬度坐标点,是否在某条由经纬度组成的路线,并返回该点所在的位置latAndLonPosition(targetPoint){// targetPoint:{lon:'83.486845',lat:'42.514477'}let that = this;let ret;let tPoint = that.Cesium.Cartographic.fromDegrees(targetPoint.lon, targetPoint.lat);let minDistance = Infinity;let positionIndex = 0;if (!that.routePath.length){return { exists: false }}for (let i = 0; i < that.routePath.length; i++) {const routePoint = that.routePath[i];const pointCartographic = that.Cesium.Cartographic.fromDegrees(...routePoint);const distance = that.distance(pointCartographic, tPoint);if (distance < minDistance) {minDistance = distance;positionIndex = i;}}let routePathStart = that.Cesium.Cartographic.fromDegrees(...that.routePath[0]);let routePathEnd = that.Cesium.Cartographic.fromDegrees(...that.routePath[that.routePath.length - 1]);let routeDistance = that.distance(routePathStart, routePathEnd);if (minDistance < routeDistance) {// 目标点在路线上  positionIndex:所在位置ret = { index:positionIndex, exists: true }} else {// 目标点不在路线上ret = { exists: false }}return ret}walkedThrough(){let that = this;let setCTimer = new that.Cesium.JulianDate(that.clock.currentTime.dayNumber,that.clock.currentTime.secondsOfDay - that.discrepancyNum)let center = that.trainModel.position.getValue(setCTimer);let currentPosition = that.cartesian3_to_WGS84(center);let positionIndex = that.latAndLonPosition(currentPosition)let cDistance = 0;if (!that.fullRoutePathDistance.distance.length) return 0;for (let i = 0; i < that.fullRoutePathDistance.distance.length; i++) {if (i === positionIndex.index) break;cDistance = cDistance + that.fullRoutePathDistance.distance[i]}return cDistance // 走了多少距离}computerFullRoutePathDistance(routePath){let that = this;for (let i = 0; i < routePath.length; i++) {if (i < routePath.length-1 ){let startPosition = that.Cesium.Cartographic.fromDegrees(...routePath[i]);let endPosition = that.Cesium.Cartographic.fromDegrees(...routePath[i+1]);let towPosition = that.distance(startPosition,endPosition);that.fullRoutePathDistance.distance.push(towPosition);that.fullRoutePathDistance.positionBetween.push(routePath[i][0]+'-'+routePath[i+1][0]);that.fullRoutePathDistance.total = (that.fullRoutePathDistance.total + towPosition)}}}timeFormat(){let now = new Date();let year = now.getFullYear();let month = ("0" + (now.getMonth() + 1)).slice(-2); // 获取两位数的月份let day = ("0" + now.getDate()).slice(-2); // 获取两位数的日期let hours = ("0" + now.getHours()).slice(-2); // 获取两位数的小时let minutes = ("0" + now.getMinutes()).slice(-2); // 获取两位数的分钟let seconds = ("0" + now.getSeconds()).slice(-2); // 获取两位数的秒数// 返回格式:如2023-10-27T12:00:00Zreturn `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`}//将秒转换为分钟、小时convertSecondsToMinutesHours(seconds) {let minutes = Math.floor(seconds / 60);let hours = Math.floor(minutes / 60);return {minutes,hours}}//加载czml文件reloadCzml(czml,callBack) {let that = this;that.delModelEntity(that.dataSource); // 更新前,需删除之前的数据that.viewer.dataSources.add(that.Cesium.CzmlDataSource.load(czml)).then(function (ds){that.dataSource = dscallBack && callBack(ds)})}// 根据路程和当前速度,计数走完需要多少时间countingRate(v,s) {// 单位是米和秒// 速度(v)、距离(s)和时间(t)之间的关系可以用以下的数学公式表示:s = v × t//速度 (km/h) = 速度 (m/s) × 3.6let t1 = s / (v*1000/3600); // kmlet t2 = s / v; // mreturn t1}trainRun(fn){let clock = window.viewer.clock;// clock.firstPerspective = true;clock.shouldAnimate = true;clock.multiplier = 1;fn && fn()}// 计算带时间的路径createDynamicPositions(positions,totalTime) {let that = this;// 将秒数,平均分给每个坐标点,每个坐标点应该到的时间if (!positions || !positions.length) return;let useRoutePath = [];let avgT = Number(totalTime)/positions.length;let cur = 0;// 重新计数that.saveLastPathAndTime = { timer: [], lanLat: []};for (let i = 0; i < positions.length; i++) {cur = cur + avgT;useRoutePath.push(cur,positions[i][0],positions[i][1],0);that.saveLastPathAndTime.timer.push(cur);that.saveLastPathAndTime.lanLat.push(positions[i][0]);}useRoutePath[0] = 0;that.useRoutePath = useRoutePath;return {useRoutePath,saveLastPathAndTime:that.saveLastPathAndTime,};}// 给一个时间格式字符串和一个添加的秒数,返回添加秒数后的最终时间addSeconds(timeString, secondsToAdd) {// var time = "2012-08-04T15:00:00Z"; console.log(addSeconds(time, 5670)); 输出新的时间字符串// 将字符串转化为Date对象let date = new Date(timeString);// 在Date对象上加上秒数date.setSeconds(date.getSeconds() + secondsToAdd);return date.toISOString(); // 将新的Date对象转换回字符串}//修改当前的速度setCurrentVelocity(currentVelocity){let that = this;// 保存当前相机的位置that.CameraPositionInfo = that.getCameraPositionInfo();if (currentVelocity < 0) currentVelocity = 1;let secondEd = that.clock.currentTime.secondsOfDay - that.discrepancyNum;let distanceEd = secondEd*(that.currentVelocity*1000 / 3600);// 找到移动到的经纬度点let atBetweenIndex = 0;let totalPathEd = 0;let originalDistance = that.fullRoutePathDistance.distance;for (let i = 0; i < originalDistance.length; i++) {totalPathEd = totalPathEd + originalDistance[i];if (totalPathEd > distanceEd){atBetweenIndex = i;break}}// 已经移动到的经纬度坐标点let curMeter = 0;let currentLan = that.fullRoutePathDistance.positionBetween[atBetweenIndex];for (let i = 0; i < that.routePath.length; i++) {if (currentLan.includes(that.routePath[i][0])){curMeter = i // 记录当前从那个短路开始计算新的数据break}}that.newRoutePathIndex = that.newRoutePathIndex + curMeter;let newRoutePath = that.routePath.slice(that.newRoutePathIndex);that.loadComputeData(newRoutePath,currentVelocity,that.trainRun)}// 加载最新数据loadComputeData(routePath,Velocity = this.currentVelocity,fn){let that = this;//routePath:由经纬度组成的路程// currentVelocity = 30; // 每秒30米的速度行驶let endTime; // 运行结束时间that.currentVelocity = Velocity;let finishNeedTime; // 走完需要多少时间 单位是秒slet distanceMeter; // 起点到终点多少米远distanceMeter = that.totalJourney(routePath);finishNeedTime = that.countingRate(Velocity,distanceMeter);that.finishNeedTime = finishNeedTime;endTime = that.addSeconds(this.startTime,finishNeedTime);let ret = that.createDynamicPositions(routePath,finishNeedTime);that.saveLastPathAndTime = ret.saveLastPathAndTime; // 路程与时间that.reloadCzml(modelCzml(ret.useRoutePath,that.startTime,endTime),function (ds){let trainModel = ds.entities.getById('Vehicle');that.trainModel = trainModel;that.viewer.trackedEntity = trainModel;// 设置模型可以随路径方向转向// trainModel.orientation = new Cesium.VelocityOrientationProperty(trainModel.position);// trainModel.model.alignedAxis = new Cesium.VelocityVectorProperty(trainModel.position, true);// 更新相机位置(第一视角)// tool.firstPerspective()fn && fn()})return finishNeedTime}// 找出当前的速度改变前,火车在的位置,利用数组的下标的方式来记录,根据当前时间的秒速去找,对比,取下一个地点currentMovingLocation(arr, num){// 添加数字到数组中arr.push(num);// 排序数组arr.sort((a, b) => a - b);// 查找数字在数组中的位置(下标)let index = arr.indexOf(num);return {index: index,sortedArray: arr};}// 修改当前时钟的时间,从那个位置开始运动setClockCurrentTime(second){let that = this;that.clock.currentTime = new that.Cesium.JulianDate(that.clock.currentTime.dayNumber, second);that.clock.multiplier = 1;that.clock.shouldAnimate = true;}// 删除实体和模型delModelEntity(dataSource){let that = this;if (dataSource){that.viewer.dataSources.remove(dataSource);}}// 第一视角firstPerspective(){let that = this;//window.trainModel实体const orientation = that.trainModel.orientation;const position = that.trainModel.position;function main() {try {if (that.viewer.clock.shouldAnimate === true) {let ori = orientation.getValue(that.viewer.clock.currentTime); // 获取偏向角let center = position.getValue(that.viewer.clock.currentTime); // 获取位置// 1、由四元数计算三维旋转矩阵let mtx3 = that.Cesium.Matrix3.fromQuaternion(ori);// 2、计算四维转换矩阵:let mtx4 = that.Cesium.Matrix4.fromRotationTranslation(mtx3, center);// 3、计算角度:let hpr = that.Cesium.Transforms.fixedFrameToHeadingPitchRoll(mtx4);// 获取角度(弧度)const headingTemp = hpr.heading;const pitchTemp = hpr.pitch;// 调整角度为第一人称const heading = that.Cesium.Math.toRadians(that.Cesium.Math.toDegrees(headingTemp) + 90);const pitch = that.Cesium.Math.toRadians(that.Cesium.Math.toDegrees(pitchTemp) - 12);// 视角高度,根据模型大小调整const range = 140.0;// 动态改变模型视角that.viewer.camera.lookAt(center, new that.Cesium.HeadingPitchRange(heading, pitch, range));}} catch (e) {console.log('err in firstPerspective function')}}that.onTickEvent = that.viewer.clock.onTick.addEventListener(main);}firstPerspectiveTow() {let that = this;try {let center = that.trainModel.position.getValue(that.viewer.clock.currentTime);let orientation = that.trainModel.orientation.getValue(that.viewer.clock.currentTime)let transform = that.Cesium.Matrix4.fromRotationTranslation(that.Cesium.Matrix3.fromQuaternion(orientation), center);// viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(-100, 0, 50))that.viewer.camera.lookAtTransform(transform, new that.Cesium.Cartesian3(-60, 0, 50))} catch (e) {console.log('err in firstPerspectiveTow function')}}// 根据经纬度坐标,计算总的路程有多少米totalJourney(routePaths){let that = this;if (!routePaths || !routePaths.length) return 0;let totalJourney = 0;for (let i = 0; i < routePaths.length; i++) {if (i < routePaths.length-1 ){let startPosition = that.Cesium.Cartographic.fromDegrees(...routePaths[i]);let endPosition = that.Cesium.Cartographic.fromDegrees(...routePaths[i+1]);let towPosition = that.distance(startPosition,endPosition);totalJourney = totalJourney + towPosition}}return totalJourney}arriveAtStation(clock,callBack){let that = this;let cDistance = that.walkedThrough();let diff = Math.abs(that.fullRoutePathDistance.total - cDistance);if (that.tractionMethod == '后退'){diff = that.fullRoutePathDistance.total - diff}if (diff < 9){// 可以在这里分发到站广播callBack && callBack();clock.shouldAnimate = false;that.arriveAatStation()}}// 画一条路线addPolyline(routePath){let that = this;if (!routePath || !routePath.length) return;let positions = [];routePath.forEach( el =>{positions.push(that.Cesium.Cartesian3.fromDegrees(...el))})return that.viewer.entities.add({id: 'MY_POLYLINE',name: 'MY_POLYLINE',polyline: {positions: positions,  // 设置轨道的位置点material: that.Cesium.Color.YELLOW.withAlpha(1.0),width: 4  // 设置轨道的宽度。}});}getCameraPositionInfo(){let that = this;let position = that.viewer.camera.position;  // 获取当前相机位置let offsetHeading = that.viewer.camera.heading; // 获取当前相机偏移角度let pitch = that.viewer.camera.pitch; // 获取当前相机俯视角let roll = that.viewer.camera.roll; // 获取当前相机的滚动角// viewer.camera.setView(cameraPositionInfo); // 设置相机位置return {destination: position,// 经度,纬度,高度orientation: that.Cesium.HeadingPitchRoll.fromDegrees(offsetHeading, pitch, roll) // 偏航角,俯仰角,滚动角}}throttle(func, delay) {let lastCall = 0;return function(...args) {const now = new Date().getTime();if (now - lastCall < delay) {return;}lastCall = now;return func.apply(this, args);};}// 两个经纬度之间的插值,返回相差的经纬度坐标interpolation(startPosition,endPosition){let that = this;// 两个参数接收的类型和值:// startPosition:[83.787568,42.919772]// endPosition:[83.484661,42.515142]let nextPath = [];  // 存放两个经纬度坐标之间的差值// nextPath.push(startPosition); // 火车有时候有可能会变成闪动let sPosition = new that.Cesium.Cartesian3.fromDegrees(...startPosition);let ePosition = new that.Cesium.Cartesian3.fromDegrees(...endPosition);let differenceValue = 0;// Cesium.Cartesian3.lerp(startPosition, endPosition, differenceValue, new Cesium.Cartesian3())  // 计数两点之间的插值while (differenceValue <= 1){// 计数两个经纬度之间的差值,分成--份differenceValue = differenceValue + 0.05; // 0.05let coordinateD = that.Cesium.Cartesian3.lerp(sPosition, ePosition, differenceValue, new that.Cesium.Cartesian3());let wgs84Point = that.cartesian3_to_WGS84(coordinateD);nextPath.push([wgs84Point.lon, wgs84Point.lat])}return nextPath}// 笛卡尔坐标转化为经纬度坐标cartesian3_to_WGS84(point) {let that = this;let cartesian3 = new that.Cesium.Cartesian3(point.x, point.y, point.z);let cartographic = that.Cesium.Cartographic.fromCartesian(cartesian3);let lat = that.Cesium.Math.toDegrees(cartographic.latitude);let lon = that.Cesium.Math.toDegrees(cartographic.longitude);let alt = cartographic.height;return { lat, lon, alt };}// 添加插值addInterpolation(routePath){let that = this;if (!routePath || !routePath.length) return;/**// bd09towgs84  百度坐标转换为wgs84坐标let wgs84 = [];for (let i = 0; i < routePath.length; i++) {let a = window.coordtransform.bd09togcj02(routePath[i][0],routePath[i][1]);let b = window.coordtransform.gcj02towgs84(a[0],a[1]);wgs84.push(b)}routePath = wgs84;**/let arr = [];for (let i = 0; i < routePath.length; i++) {if (i < routePath.length-1){// 做个优化,如何两个点直接的距离小于15米不做差值计算let startPosition = that.Cesium.Cartographic.fromDegrees(...routePath[i]);let endPosition = that.Cesium.Cartographic.fromDegrees(...routePath[i+1]);let ret = that.distance(startPosition,endPosition);if (5 > ret) continue;let opera = that.interpolation(routePath[i],routePath[i+1]);arr = arr.concat(opera)}}// 原路线的起点和终点要保持和源数据一致,不能丢失if (arr[0][0] != routePath[0][0]) arr.unshift(routePath[0]);if (arr[arr.length -1][0] != routePath[routePath.length -1][0]) arr.push(routePath[routePath.length -1]);that.computerFullRoutePathDistance(arr);return arr}// 计数两个经纬度之间距离多少米distance(startPosition, endPosition) {let that = this;// let startPosition = Cesium.Cartographic.fromDegrees(117.270739, 31.84309);// let endPosition = Cesium.Cartographic.fromDegrees(117.270739, 31.85309);let geodesic = new that.Cesium.EllipsoidGeodesic();geodesic.setEndPoints(startPosition, endPosition);return geodesic.surfaceDistance; // 返回两点之间的距离}// 在三维地图上取点,地图上会打印选择的点坐标,可以用于去移动路线at3DMapGetPointTakingRoute(){window.viewer.entities.add({//点的位置position : Cesium.Cartesian3.fromDegrees(112.90365587012, 27.810901397823, 0),//点point : {pixelSize : 5,//点的大小color : Cesium.Color.RED,//点的颜色outlineColor:Cesium.Color.GREEN,//外圈颜色outlineWidth:1,//外圈大小}});let coordinate = []let str = ''let handler = new window.Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(function(event) {let cartesian = window.viewer.camera.pickEllipsoid(event.position);let cartographic = window.Cesium.Cartographic.fromCartesian(cartesian);let lng = window.Cesium.Math.toDegrees(cartographic.longitude); // 经度let lat = window.Cesium.Math.toDegrees(cartographic.latitude); // 纬度let alt = cartographic.height; // 高度,椭球面height永远等于0// 添加点window.viewer.entities.add({//点的位置position : Cesium.Cartesian3.fromDegrees(lng, lat, 0),//点point : {pixelSize : 5,//点的大小color : Cesium.Color.RED,//点的颜色outlineColor:Cesium.Color.GREEN,//外圈颜色outlineWidth:1,//外圈大小}});str += '['str += lngstr += ','str += latstr += ']'str += ','coordinate.push([lng, lat])console.log(coordinate);console.log(str);}, window.Cesium.ScreenSpaceEventType.LEFT_CLICK);}handler(clock,TO){clock.multiplier = 1;if (clock.firstPerspective){// tool.throttle(tool.firstPerspectiveTow,50) && tool.throttle(tool.firstPerspectiveTow,50)();TO.firstPerspectiveTow()}clock.shouldAnimate && TO.arriveAtStation(clock);}
}
sourceData.js
/* eslint-disable */// 路线
export const routePath = [[116.44411704836098,39.95279202225133],[116.44807033296253,39.956431604742875],[116.45386442263022,39.952900124343756],[116.45546340934312,39.951275636111355],[116.45567209160625,39.9494096918938],[116.4556296095857,39.94616403625391],
]// let testModel = window.location.origin + '/Apps/trainOperation/model/scene.gltf';
// let testModel = window.location.origin + '/Apps/SampleData/models/GroundVehicle/GroundVehicle.glb';
let testModel = window.location.origin + '/Apps/SampleData/models/GroundVehicle/GroundVehicle.glb';
export function modelCzml(useRoutePath,startTime,endTime){let minx = startTime+'/'+endTime;return [{"id": "document","version": "1.0"},{"id": "Vehicle","availability": minx ,"label": {"fillColor": [{"rgba": [0, 0, 0, 255]}],"font": "bold 10pt Segoe UI Semibold","horizontalOrigin": "CENTER","outlineColor": {"rgba": [0, 0, 0, 255]},"pixelOffset": {"cartesian2": [40.0, 50.0]},"scale": 1.0,"show": [{"boolean": true}],"style": "FILL","text": "测试火车移动","verticalOrigin": "CENTER"},"model": {"gltf": testModel,"minimumPixelSize": 120,"maximumScale": 50},"orientation": {"velocityReference": "#position"},"viewFrom": {// "cartesian": [300, 20, 300]"cartesian": [500, 2000, 779]},"properties": {"fuel_remaining": {"epoch": startTime,"number": [0, 22.5, 1500, 21.2]}},"path": {"material": {"solidColor": {"color": {"rgba": [255, 255, 0, 255]}}},"width": [{"number": 4.0}],"show": [{"boolean": true}]},"position": {"interpolationAlgorithm": "LAGRANGE","interpolationDegree": 1,"wrap": false,"epoch": startTime,"cartographicDegrees": useRoutePath// useRoutePath:时间、经度、纬度、高度加载显示的格式为[0, 102.23404378554466, 27.825736605050523, 2500,10, 102.23691954070244, 27.82887625908256, 2500,]}}]
}export function operaDomStyle(){try {document.getElementsByClassName('cesium-viewer-animationContainer')[0].style.visibility = 'hidden';document.getElementsByClassName('cesium-viewer-bottom')[0].style.visibility = 'hidden';document.getElementsByClassName('cesium-viewer-timelineContainer')[0].style.visibility = 'hidden';document.getElementsByClassName('cesium-viewer-toolbar')[0].style.visibility = 'hidden';document.getElementsByClassName('cesium-viewer-fullscreenContainer')[0].style.visibility = 'hidden';} catch (e) {}
}

gitee仓库项目代码

wdn-cesiumCarRoute: 三维地图Cesium,加载一个模型,模型沿着给定的一组经纬度路线、速度、前进状态进行移动

疑问解答

需要解答(项目的运行、代码疑问、新功能开发...),可以给我留言哟。有空随时会解答

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/25278.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CopyOnWriteArrayList详解

目录 CopyOnWriteArrayList详解1、CopyOnWriteArrayList简介2、如何理解"写时复制"3、CopyOnWriteArrayList的继承体系4、CopyOnWriteArrayList的构造函数5、CopyOnWriteArrayList的使用示例6、CopyOnWriteArrayList 的 add方法7、CopyOnWriteArrayList弱一致性的体现…

LeetCode790多米诺和托米诺平铺

题目描述 有两种形状的瓷砖&#xff1a;一种是 2 x 1 的多米诺形&#xff0c;另一种是形如 “L” 的托米诺形。两种形状都可以旋转。给定整数 n &#xff0c;返回可以平铺 2 x n 的面板的方法的数量。返回对 109 7 取模 的值。平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺…

先进封装技术的一些优缺点探讨

半导体封装技术是半导体制造过程中的关键环节&#xff0c;它不仅保护了芯片免受物理损伤&#xff0c;还提供了电气连接和散热功能。随着技术的发展&#xff0c;出现了多种先进的封装技术&#xff0c;每种技术都有其特定的应用场景和优缺点。 --> 1. 传统封装技术 【优点】&…

【SpringBoot + Vue 尚庭公寓实战】根据类型查询标签列表接口实现(五)

【SpringBoot Vue 尚庭公寓实战】根据类型查询标签列表接口实现&#xff08;五&#xff09; 文章目录 【SpringBoot Vue 尚庭公寓实战】根据类型查询标签列表接口实现&#xff08;五&#xff09;1、查看接口2、进行开发 1、查看接口 启动项目 访问&#xff1a;http://localho…

macOS优化工具CleanMyMac2024免费版电脑性能提升 存储空间释放 电脑维护 高效易用 延长电脑使用寿命

【CleanMyMac】是一款专为macOS系统设计的优化和清理软件&#xff0c;它的核心特性就是帮助我们提升电脑性能&#xff0c;释放存储空间。&#x1f680; CleanMyMac绿色免费版下载如下&#xff1a;记得保存哈&#xff0c;以防失效&#xff1a; https://pan.quark.cn/s/9b08114…

HTML标签 label for 还是 htmlFor

文章目录 问题结论更多 问题 HTML标签&#xff1a; label 的属性 for 还是 htmlFor&#xff1f; MDN文档&#xff1a;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label 结论 for 和 htmlFor 都可以使用&#xff0c;都是对的。在部分情况中&#xff0c;只能…

【React】Redux与React - 环境准备

配套工具 在React中使用redux&#xff0c;官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux 配置基础环境 使用 CRA 快速创建 React 项目 npx create-react-app react-redux安装配套工具 npm i reduxjs/toolkit react-redux启动项目 npm run start

计算机网络复习题

期末题库复习1 一. 单选题&#xff08;共32题&#xff0c;100分&#xff09; 1. (单选题) 在脉冲起始时刻&#xff0c;有无跳变来表示“0”和“1”&#xff0c;且在脉冲中间时刻始终发生跳变的编码是&#xff08; &#xff09;。 A.非归零码 B.曼彻斯特编码 C.归零码 D.差…

物联网设计竞赛_8_Jetson Orin Nano安装pytorch与torchvision

我的新板子到了&#xff0c;型号是jetson orin Nano与之前的jetson nano稍有不同我发现库又得从新下载 我的pip3的版本是3.8.10&#xff0c;jetpack版本5.1.1&#xff0c;又得重新开始下载库&#x1f62d; 安装pytorch: 得科学上网&#xff1a; PyTorch for Jetson - Jetson …

【RAG入门教程03】Langchian框架-文档加载

Langchain 使用文档加载器从各种来源获取信息并准备处理。这些加载器充当数据连接器&#xff0c;获取信息并将其转换为 Langchain 可以理解的格式。 LangChain 中有几十个文档加载器&#xff0c;可以在这查看https://python.langchain.com/v0.2/docs/integrations/document_lo…

小白都可以通过U盘重装系统,再也不用花50块钱去安装系统啦

下载Ventoy 软件 1、今天带着大家通过Ventoy 安装Windows 11 系统。 2、首先我们通过官网如下地址&#xff1a;https://www.ventoy.net/cn/&#xff0c;找到我们对应系统的Ventoy 软件安装包。 3、通过官网可以找到软件包的地址地址&#xff0c;如下图所示。 4、如下就是我下…

弘君资本股市资讯:突发!英伟达,被查!

4家科技巨擘一同被查&#xff01; 科技巨擘们&#xff0c;正迎来反独占风暴。据美媒报道&#xff0c;美国将对英伟达、微软、OpenAI翻开反独占查询&#xff0c;这标志着监管部门对人工智能工作的查看力度越来越大。 与此一同&#xff0c;苹果也面对新费事。6月6日&#xff0c…

python-微分方程计算

首先导入数据 import numpy as np from scipy.integrate import odeint from scipy.optimize import minimize import matplotlib.pyplot as pltdata np.array([[30, 4],[47.2, 6.1],[70.2, 9.8],[77.4, 35.2],[36.3, 59.4],[20.6, 41.7],[18.1, 19],[21.4, 13],[22, 8.3],[2…

java线程相关知识点

Java多线程涉及以下几个关键点 1.线程生命周期&#xff1a;理解线程从创建到销毁的各个阶段&#xff0c;包括新建、运行、阻塞、等待、计时等待和终止。 2.线程同步&#xff1a;掌握如何使用synchronized关键字和Lock接口来同步代码&#xff0c;防止数据竞争和死锁。 3.线程间通…

数据分析必备:一步步教你如何用Pandas做数据分析(21)

1、Pandas 可视化 Pandas 可视化是指使用 Pandas 库中的函数和方法来创建数据可视化图表。Pandas 提供了一些基本的绘图功能&#xff0c;例如折线图、柱状图、饼图等&#xff0c;可以通过调用相应的函数来创建这些图表。 2、基本绘图&#xff1a;绘图 Series和DataFrame上的…

预期值与实际值对比

编辑实际值和预期值变量 因为在单独的代码当中&#xff0c;我们先定义了变量str&#xff0c;所以在matcher时传入str参数&#xff0c;但当我们要把这串代码写在testrun当中&#xff0c;改下传入的参数&#xff0c;与excel表做连接 匹配的结果是excel表中的expect结果&#xf…

有序二叉树java实现

类实现&#xff1a; package 树;import java.util.LinkedList; import java.util.Queue;public class BinaryTree {public TreeNode root;//插入public void insert(int value){//插入成功之后要return结束方法TreeNode node new TreeNode(value);//如果root为空的话插入if(r…

RK3288 android7.1 实现ota升级时清除用户数据

一&#xff0c;OTA简介(整包&#xff0c;差分包) OTA全称为Over-The-Air technology(空中下载技术)&#xff0c;通过移动通信的接口实现对软件进行远程管理。 1. 用途&#xff1a; OTA两种类型最大的区别莫过于他们的”出发点“&#xff08;我们对两种不同升级包的创建&…

【马琴绿绮】马维衡古琴之马氏汉风 明代杉木制;周身髹朱红色漆

【马琴绿绮式】马维衡古琴之马氏汉风 明代杉木制&#xff1b;琴体周身髹朱红色漆&#xff0c;鹿角霜灰胎&#xff1b;形体壮硕、风格高古&#xff1b;音色松透、浑厚&#xff0c;音质纯净&#xff0c;按弹舒适&#xff0c;手感丝滑。

Effective Java 2 遇到多个构造器参数时要考虑使用构建器

第2个经验法则&#xff1a;用遇到多个构造器参数时要考虑使用构建器&#xff08;consider a builder when faced with many constructor parameters&#xff09; 上一条讨论了静态工厂相对于构造器来说有五大优势。但静态工厂和构造器有个共同的局限性:它 们都不能很好地扩展到…