前言:大数据,人工智能,工业物联网,5G 已经或者正在潜移默化地改变着我们的生活。在信息技术快速发展的时代,谁能抓住数据的核心,利用有效的方法对数据做数据挖掘和数据分析,从数据中发现趋势,谁就能做到精准控制,实时分析,有的放矢,从而获取更快速、更平稳、更长远地发展。在航空领域,机场、航班和航线信息是至关重要的数据,本文将介绍以 HT 为平台,应用 JavaScript、HTML5、GIS 等技术开发的全球航线实例。

界面预览

主界面

飞机及飞机阴影动画

代码实现

场景搭建

本实例的场景包括 3D 和 2D 场景两部分,分别是通过 HT 的 3D 和 2D 编辑器构建,该编辑工具基于 HTML5 技术开发,易于上手,而且预定义了许多图元类型,用户可以无编码快速可视化搭建各种 3D/2D 场景。3D 场景效果如下:

2D 面板部分主要包括左侧航线表格,右侧风暴实时数据表格以及底部的信息面板。左侧航线表格展示了不同大洲的航线信息,大洲可以通过底部的左侧按钮进行切换;右测风暴信息是模拟生成,实时更新;底部信息栏包括大洲按钮及航线详细信息。面板截图:

航线来源及机场位置的计算

实例的机场和航线源数据来自于开源网站 openflights.org。拿到原始数据之后,我们首先对机场和航线数据进行了初步处理将其存为 JSON 文件。处理后的机场数据格式如下,每个域对应的信息依次是纬度、经度、海拔、机场简称、大洲、国家、地区和机场名字。

[[-9.443380356,147.2200012, 146,"POM","OC","PG","PG-NCD","Port Moresby"],
[63.98500061,-22.60560036, 171,"KEF","EU","IS","IS-2","Reykjavík"],
[36.001741,117.63201,0,"CN-0083","AS","CN","CN-U-A",""],

]

处理后的航线数据片段格式如下,以第一条信息为例,航线的起始机场为 MIA,能够抵达的机场包括["3201:PUJ","24:MSY","24:MVD","24:NAS","24:ORF","24:PHL","24:PTP","24:PTY","24:RIC","24:SAL","24:SAN","24:SDQ","24:SFO","1299:AMS"]。

{"MIA":["3201:PUJ","24:MSY","24:MVD","24:NAS","24:ORF","24:PHL","24:PTP","24:PTY","24:RIC","24:SAL","24:SAN","24:SDQ","24:SFO","1299:AMS"],
"HKG":["3021:SIN","1683:MNL","2994:ICN","15999:PVG","24:JFK","24:LAX","24:NRT","24:SFO","330:YVR","218:KIX","576:KUL","1680:SGN","328:POM"],
"SJU":["3029:SXM","3029:TPA"],

}

通过对处理后的机场、航线数据分析,可以看出机场位置是生成航线的基础。在处理后的机场数据中,已经具备了机场的经纬度信息,因此问题的关键点在于如何将经纬度转换为球体坐标,转换代码如下:

// 将经纬度转换为球体位置
getSpherePos(radius, longitude, latitude) {
let ang1 = Math.PI * (longitude - 90) / 180;
let ang2 = Math.PI * latitude / 180;
let x, y, z;
let s_r = radius;
x = s_r * Math.sin(ang1) * Math.cos(ang2);
y = s_r * Math.cos(ang1) * Math.cos(ang2);
z = s_r * Math.sin(ang2);
return [x, y, z];
}

对所有机场数据循环处理,计算每个机场的球体坐标,并将坐标信息与其它既有的机场信息保存于全局数组中。

航线生成

在生成航线时,使用了 ht.Polyline 类型,该类型支持三维空间点描述,而且结合 segments 参数,实现了从二维平面曲线延伸到三维空间曲线的效果。在本实例中,根据航线的起点和终点的位置,利用向量运算构造出中间的控制点,生成贝塞尔曲线来渲染航线。航线创建并添加到 DataModel (通过 add 函数)之后, 调用 setHost(host) 函数将其吸附到地球,这样地球在移动或者旋转时,航线也会随之变化。以下为创建一条航线的代码实现:

/**
* 根据航线起点,终点位置创建航线(贝塞尔曲线)
* @param {Object} start 起点机场信息
* @param {Object} end 终点机场信息
*/
createEdge(start, end) {
let edge;
let distance = ht.Default.getDistance(start.point, end.point);
let ratio = distance / this.radius;
let v1 = new ht.Math.Vector3(start.point);
let v2 = new ht.Math.Vector3(end.point);
let v3 = v1.clone().add(v2).setLength(distance / 2);
let v4 = v3.clone().add(v2);
v3.add(v1);

edge = new ht.Polyline();

// 此处设置 edge 样式和属性的代码省略
edge.setPoints([
{ x: start.point[0], y: start.point[2], e: start.point[1] },
{ x: v3.x, y: v3.z, e: v3.y },
{ x: v4.x, y: v4.z, e: v4.y },
{ x: end.point[0], y: end.point[2], e: end.point[1] },
]);
edge.setSegments([1, 4]);
this.dm3d.add(edge);
edge.setHost(this.earth);
}

这部分的难点在于如何根据航线的起点和终点位置构造中间控制点来生成贝塞尔曲线。下面的示意图演示了代码中向量的计算及各个向量变量的变化。

对所有航线数据循环处理,调用创建航线的 createEdge(start, end) 函数,就能完成所有航线的绘制生成。如图所示:

2D/3D 互动画线

在文章的第二幅图中,有一条黄色的线。这条线的起点对应着表格中选中的航线,终点对应着 3D 空间的航线。当点击表格中某条航线时,如何生成一条线,跨越 2D 和 3D 空间呢?本实例的思路是获取 3D 空间的位置坐标 p3 后,调用 g3d.toViewPosition 获取二维屏幕坐标 p,之后通过调用 g2d.getLogicalPoint 得到 2D 坐标,这个坐标就是终点的位置。以下是获取终点位置的代码实现:

// 获取定位线的终点 -- 3D 球体中选中航线对应的位置
getLineEnd() {
let p3 = this.g3d.getLineOffset(this.selectedEdge, this.g3d.getLineLength(this.selectedEdge) * 0.5);
let p = g3d.toViewPosition([p3.point.x, p3.point.y, p3.point.z]);
p = this.g2d.getLogicalPoint(p);
this.endPoint = p;
}

线的起点位置代码如下,分别计算起点的横坐标和纵坐标。

// 获取定位线的起点 -- 航线表格对应的位置
getLineStart() {
let offset = this.table.a('ht.translateY');
let lineStartPoint = {};
let height = this.table.getHeight();
let origY = this.table.p().y - height / 2 + this.table.a('ht.headHeight') + this.table.a("ht.rowHeight") / 2;
lineStartPoint.x = this.table.p().x + this.table.getWidth() / 2;
lineStartPoint.y = origY + this.rowIndex * this.table.a("ht.rowHeight") + offset;
this.startPoint = lineStartPoint;
}

飞机,飞机阴影动画及光源移动

在表格中选中某条航线或者双击地球上某条航线时,飞机将会沿着航线飞行,飞机上方有光源移动,下方有飞机阴影移动。这部分使用了 HT 内置的 startAni 函数启动动画。在 startAni 函数中,action 函数必须提供,实现动画过程中的属性变化;finishFunc 为动画结束后调用的函数。一个简单的动画例子如下:

ht.Default.startAnim({
frames: 60,
interval: 16,
finishFunc: function() {
console.log('finish');
},
action: function(t) {
console.log(t);
}
});

以下为本 Demo 中的 action 函数,该函数完成了动画过程中飞机、光源及飞机阴影的移动,飞机姿态调整和旋转。

action: function (v, t) {
let offset = that.g3d.getLineOffset(that.selectedEdge, length * v); // 偏移量
let p1 = offset.point; // 3D 坐标
let tangent = offset.tangent; // 切线方向
let direction = new ht.Math.Vector3(tangent);
let vp1 = new ht.Math.Vector3(p1);
direction.multiplyScalar(0.1);
direction.add(vp1);
direction.setLength(direction.length() + 2);
vp1.setLength(vp1.length() + 2); that.airPlane.p3(vp1.x, vp1.y, vp1.z);
that.airPlane.setRotationMode('yxz');
that.airPlane.lookAtX([0, 0, 0], 'bottom');
that.airPlane.lookAtX([direction.x, direction.y, direction.z], 'front'); lightP = new ht.Math.Vector3(p1);
lightP.setLength(that.radius * 2);
that.spotLight.p3(lightP.x, lightP.y, lightP.z); direction.setLength(that.radius);
lightP.setLength(that.radius);
that.planeShadow.p3(lightP.x, lightP.y, lightP.z);
that.planeShadow.setRotationMode('yxz');
that.planeShadow.lookAtX([0, 0, 0], 'back');
that.planeShadow.lookAtX([direction.x, direction.y, direction.z], 'right');
}

卫星动画

实例中,卫星按照椭圆轨道围绕地球旋转,Logo 和光晕又围绕卫星旋转。椭圆轨道的计算方式采用的是参数方程。假设椭圆的半长轴和半短轴的长度分别为 a 和 b,分别以半长轴和半短轴做椭圆的内切圆和外切圆。通过下图可以看出椭圆上任意一点 A 与内切圆上的 A1 点有相同的纵坐标,与外切圆上的 A2 点有相同的横坐标,所以 A 点的坐标就可以描述为 (a * cosθ,b * sinθ),其中 θ 是椭圆内切圆或者外切圆的圆心角。

Logo 和光晕的旋转使用了 3D 旋转函数,具体使用方法可以参照  HT 3D 手册 中的 3D 旋转函数部分。卫星动画的代码实现如下所示:

// 卫星及 Logo 的旋转
startSat() {
let dm = this.dm3d;
let a = 1226; // 椭圆半长轴
let b = 698; // 椭圆半短轴 let x, y, z;
y = 0;
let sat_ang = 0; // 卫星初始角度
let logo_ang = 0; // Logo 初始角度
setInterval(() => {
sat_ang = sat_ang + this.satelliteSpeed;
logo_ang = logo_ang + 0.01
x = a * Math.cos(-sat_ang); // 卫星当前 x 轴坐标
z = b * Math.sin(-sat_ang); // 卫星当前 z 轴坐标
y = x * Math.sin(Math.PI * 16 / 180); // 卫星当前 y 轴坐标
x = x * Math.cos(Math.PI * 16 / 180); // 卫星轨道面沿 z 轴旋转之后的新的 x 轴坐标 this.sat.p3(x, y, z); this.logo.setRotationY(logo_ang);
this.logo.setRotationZ(28 / 180 * Math.PI);
this.logo.setRotationMode('yzx'); this.sat_p.setRotationY(logo_ang);
this.sat_p.setRotationZ(-35 / 180 * Math.PI);
this.sat_p.setRotationMode('yzx');
}, 16.7);
}

风暴动画

风暴动画使用 setInterval() 方法重复调用风暴动画部分,模拟风暴的移动,风暴变大及变小。风暴变大及变小的实现思路是设置两个 Flag 来判断风暴变大或者变小,风暴变大时,不断加大风暴在 x,y,z 轴方向的长度,并利用 setSize3d 函数赋值;风暴变小时,不断减小风暴在 x,y,z 轴方向的长度,并利用 setSize3d 函数赋值。风暴的移动代码实现如下:

// 风暴动画
startStorm() {
let s_ang = 0;
let s_ang2 = 0;
let s_x, s_y, s_z;
let s_r = 380.07;
setInterval(() => {
s_ang = s_ang + 0.002;
s_ang2 = s_ang2 + 0.002;
s_x = s_r * Math.sin(s_ang) * Math.cos(s_ang2);
s_z = s_r * Math.cos(s_ang) * Math.cos(s_ang2);
s_y = s_r * Math.sin(s_ang2); this.storm.p3(s_x, s_y, s_z);
this.storm.lookAtX([0, 0, 0], 'bottom');
this.storm.setRotationMode('yzx');
this.storm.setRotationY(s_ang * 20);
}, 60);
}

性能优化

为带来更好的用户体验,本实例还进行了一系列的优化,使得实例的运行更加流畅,美观。

分批显示航线

在该实例中共有 2486 条航线,如果一次性显示在地球上,加上各种样式,那么不但加载速度非常缓慢,而且可能会因为内存过大而导致程序崩溃。因此,本实例采用了分批加载航线的方式,来提高系统性能。具体实现思路是在初次加载时,设置一个名称为 display_flag 的样式来控制航线的显示与否,然后每隔一定时间(本 Demo 中是每隔 30s)更新一次航线。相关代码如下:

this.maxDisplayCount = 300; // 30s 更新一次航线
this.MAX_DISPLAY_COUNT = 6;
edge.s({ // 创建航线时
'display_flag': parseInt(Math.random() * 10) % this.MAX_DISPLAY_COUNT,
}); start() {
this.edgeTimer = setInterval(() => {
this.edges.forEach((val) => {
let showFlag = this.checkStormDistance(val);
showFlag = showFlag && (val.s('display_flag') == this.displayFlag);
val.s('3d.visible', showFlag)
});
this.displayCount++;
if (this.displayCount > this.maxDisplayCount) {
this.displayFlag = (this.displayFlag + 1) % this.MAX_DISPLAY_COUNT;
this.displayCount = 0;
} }, 100); }

- Polyline resolution 动态改变

HT 通过微分段的方式实现曲线,参数 shape3d.resolution 用来控制曲线微分段数,这个参数决定 3D 图形精度,数值越大曲线越均匀,但同时会影响性能。在本 Demo 中,为防止飞机抖动 shape3d.resolution 设置为 60。但是这样设置之后,性能影响会很大,因此我们采用了动态调整 resolution 的方式,根据航线是否被选中动态调整,提高性能。代码如下。在 updateResolution 中也需要调用 g3d.invalidateCachedGeometry(data) 来重置 geometry,更新方法见 “Polyline cache 以及更新方法” 部分。

// 动态改变 resolution
updateResolution(isRestore) {
if (!this.selectedEdge) { // 没有航线被选中
return;
}
let res, thickness;
let len = this.g3d.getLineLength(this.selectedEdge);
if (isRestore) { // 需要恢复默认值
res = 30;
thickness = 0.7;
} else {
res = len / 200 * 30;
if (res < 60) {
res = 60;
}
thickness = 5;
}
this.selectedEdge.s('shape3d.resolution', res);
this.selectedEdge.setThickness(thickness);
}

- Polyline cache 以及更新方法

如前所述,本 Demo 中创建了 2486 条航线,每条航线都是一个 ht.polyLine 类型的 3D 曲线。为提高性能,在创建航线时,将其属性 geometry.cache 设置为 true。在后续 polyLine 的属性(例如 points, segments, width)发生变化时,使用 g3d.invalidateCachedGeometry(data) 来重置 geometry。

// 创建航线时设置属性
edge.s({
'geometry.cache': true
});
// this.selectedEdge 属性发生变化时,重置 geometry。
let ui = g3d.getData3dUI(this.selectedEdge);
ui.shapeModel = ui.info = null;
this.g3d.invalidateData(this.selectedEdge);

有效大洲中心添加辅助定位用的立方体

在有效的大洲中心位置添加一个辅助定位用的立方体,当点击大洲按钮时,使用 flyTo() 函数调整球体视角。

- 2D/3D 互动画线调用 setTimeout

当 2D/3D 定位线显示在面板后,用户每次移动界面,定位线都需要重新计算和绘制。考虑到移动界面触发这个事件的频率非常高,如果每次都响应,那么程序将会变得非常繁忙,出现卡顿现象;甚至可能造成事件丢失的情况,比如出现用户已经停止了移动,线却没有画到位的现象。因此使用 setTimout 保证更新的最短间隔为 50ms,去掉不必要的更新。当然这个间隔可以根据实际情况调整,以降低视觉上的迟钝感。

this.updateTimer = setTimeout(() => {
this.updateTimer = null;
if (this.selectedEdge == null) { // 没有航线被选中
return;
}
this.getLineEnd(); // 计算 2D/3D 定位线的终点
this.updateLine(true); // 绘制定位线
}, 50);

有了 2D 和 3D 场景,按照文中介绍的思路和逻辑,就可以完成动画的生成,航线数据加载,航线可视化,飞机态势可视化和风暴数据的实时显示,整个过程其乐无穷。

基于航空大数据,在本实例中提到的数据分析和可视化的基础上,还可以挖掘更多的应用场景,比如航班运行数据可视化,飞机数据实时展示,航班历史数据分析,应急航线调度等。如果想了解更多工业互联网 2D, 3D 可视化应用案例,可以到这里参考更多 http://www.hightopo.com/blog/1103.html 《分享数百个 HT 工业互联网 2D 3D 可视化应用案例之 2019 篇》

基于 HTML5 WebGL 与 GIS 的智慧机场大数据可视化分析的更多相关文章

  1. 基于 HTML5 WebGL 与 GIS 的智慧机场大数据可视化分析【转载】

    前言:大数据,人工智能,工业物联网,5G 已经或者正在潜移默化地改变着我们的生活.在信息技术快速发展的时代,谁能抓住数据的核心,利用有效的方法对数据做数据挖掘和数据分析,从数据中发现趋势,谁就能做到精 ...

  2. 基于 HTML5 WebGL 和 VR 技术的 3D 机房数据中心可视化

    前言 在 3D 机房数据中心可视化应用中,随着视频监控联网系统的不断普及和发展, 网络摄像机更多的应用于监控系统中,尤其是高清时代的来临,更加快了网络摄像机的发展和应用. 在监控摄像机数量的不断庞大的 ...

  3. 基于 HTML5 WebGL 的智慧城市(一)

    前言 中共中央.国务院在今年12月印发了<长江三角洲区域一体化发展规划纲要>(下文简称<纲要>),并发出通知,要求各地区各部门结合实际认真贯彻落实. <纲要>强调, ...

  4. 基于 HTML5 WebGL 的智慧楼宇可视化系统

    前言 可视化的智慧楼宇在 21 世纪是有急迫需求的,中国被世界称为"基建狂魔",全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少.智慧楼宇可视化系统更多突出的是管理方面的功能 ...

  5. 基于 HTML5 WebGL 的智慧楼宇三维可视化监控

    前言 可视化的智慧楼宇在 21 世纪是有急迫需求的,中国被世界称为"基建狂魔",全球高层建筑数量位居首位,所以对于楼宇的监控是必不可少.智慧楼宇可视化系统更多突出的是管理方面的功能 ...

  6. 基于 HTML5 WebGL 的地铁站 3D 可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  7. 基于 HTML5 WebGL 的计量站三维可视化监控系统 Web 组态工控应用

    得益于 HTML5 WebGL 技术的成熟,从技术上对工控管理的可视化,数据可视化变得简单易行!完成对工控设备的管理效率,资源管理,风险管理等的大幅度提高,同时也对国家工业4.0计划作出有力响应! 如 ...

  8. 基于 HTML5 WebGL 的 3D 风机 Web 组态工业互联网应用

    基于 HTML5 WebGL 的 3D 风机 Web 组态工业互联网应用 前言 在目前大数据时代背景之下,数据可视化的需求也变得越来越庞大,在数据可视化的背景之下,通过智能机器间的链接并最终将人机链接 ...

  9. 基于 HTML5 WebGL 的挖掘机 3D 可视化应用

    前言 在工业互联网以及物联网的影响下,人们对于机械的管理,机械的可视化,机械的操作可视化提出了更高的要求.如何在一个系统中完整的显示机械的运行情况,机械的运行轨迹,或者机械的机械动作显得尤为的重要,因 ...

随机推荐

  1. JS数字千分

    JS数字千分: 1.例子:1000--->1,000 2.实现如下: salesToFormat: function (num) { var num = (num || 0).toString( ...

  2. jQuery的html(),text()和val()比较

    .html()用为读取和修改元素的HTML标签: .text()用来读取或修改元素的纯文本内容: .val()用来读取或修改表单元素的value值: 一看黑体的部分,所以把text和html分为一组, ...

  3. ltp压力测试结果分析脚本

    最近工作性质发生了改变,在做操作系统方面的测试.接手的第一个任务是做ltp stress.测试内核稳定性. 做完之后会结果进行统计分析.因为统计的内容比较多,都是通过shell命令行进行操作.于是编写 ...

  4. grep显示前后几行信息

    显示foo及前5行 1 grep -B 5 foo file 显示foo及后5行 1 大专栏  grep显示前后几行信息ode"> grep -A 5 foo file 显示 file ...

  5. Git忽略规则(.gitignore配置)不生效原因和解决

    问题: .gitignore中已经标明忽略的文件目录下的文件,git push的时候还会出现在push的目录中,或者用git status查看状态,想要忽略的文件还是显示被追踪状态. 原因是因为在gi ...

  6. 阿里云ECS 实例Centos7系统磁盘扩容

    需求:一台阿里云的数据盘磁盘空间不足,需要扩容,我这里只有一个主分区,ext4文件系统. 因为磁盘扩容场景不同,阿里云的文档比较全面一些,所以先奉上阿里云的文档,下面开始我的操作步骤: 1.登录控制台 ...

  7. React Native 学习笔记--进阶(二)--动画

    React Native 进阶(二)–动画 动画 流畅.有意义的动画对于移动应用用户体验来说是非常必要的.我们可以联合使用两个互补的系统:用于全局的布局动画LayoutAnimation,和用于创建更 ...

  8. 【内推】平安产险大数据测试开发工程师,15-30k!

    平安产险技术岗内部推荐-大数据测试开发工程师等-欢迎中年人和2020应届生 上班地点:深圳福田平安金融中心 另有大量 上海 北京 成都 广州 岗位 交流qq群 828186629 微信pythonte ...

  9. swap和shm的区别

    在使用docker的过程中,发现其有很多内存相关的命令,对其中的swap(交换内存)和shm(共享内存)尤其费解.于是查阅了一些资料,弄明白了二者的基本区别. swap 是一个文件,是使用硬盘空间的一 ...

  10. Android下的定时任务

    Android中的定时任务一般有两种实现方式,一种是使用JavaAPI里的Timer类,另一种是使用android的Alarm机制. 这两种方式在多数情况下都能实现类似的效果,但Timer有一个明显的 ...