基于 HTML5 WebGL 的民航客机飞行监控系统
前言
前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for Web 的升级版监控系统,demo 的效果还行,发出来大家相互学习下。

demo(https://www.hightopo.com/demo/flight-monitor/)
实现过程
云中穿行效果
为了达到飞机云中穿行的效果,最开始我遇到的问题是飞机飞行的层次感,也就通常所说的透视效果,这里我采用的是云通道和云背景以不同的速度流动,制造一种飞行的透视效果。
云我采用的是贴图的方式呈现的,但是仅仅是贴图会遮挡天空和飞机,非常影响飞机飞行的观感,所以我开启了相应图元的 transparent 和 opacity ,云背景和云通道设置不同的透明度,不仅增加了层次感,还会让人产生云朵从眼前飘过的错觉。
云通道采用的是 ht.Polyline 类型,通道缩放拉大了 Y 轴的比例,使云通道有更大的纵向空间,设置 reverse.flip 背拷贝使云通道内部也显示出贴图,仿佛让飞机置身于云海中穿梭;云背景采用 ht.Node 类型,只设置一个面显示充当云背景。
整体的云流动效果采用 offset 偏移实现,改变相应图元或相应图元面的贴图偏移量来达到飞机云中穿行的效果, 代码如下:
var i = 1,
    p = 0;
setInterval(() => {
    i -= 0.1; p += 0.005;
    clouds.s('shape3d.uv.offset', [i, 0]);
    cloudBackground.s('all.uv.offset', [p, 0]);
}, 100);
升降颠簸效果
虽然达到了飞机云中穿行的效果,但是如果飞机只是直直的飞行,那也会降低飞行的实感,相信坐过飞机的朋友肯定都遇到过因气流产生的颠簸,也经常感受到飞机飞行途中的爬升和下降,这其实是因为飞机的航线并不是一直固定在一个高度上,有时会爬升有时会下降,所以我就用 ht-animation.js HT 动画扩展插件去实现飞机颠簸效果,代码如下:
dm.enableAnimation(20);
plane.setAnimation({
    back1: {
        from: 0,
        to: 160,
        easing: 'Cubic.easeInOut',
        duration: 8000,
        next: "up1",
        onUpdate: function (value) {
            value = parseInt(value);
            var p3 = this.p3();
            this.p3(value, p3[1], p3[2]);
        }
    },
    //...省略相似
    start: ["back1"]
});
球扇形视角限制
飞行效果完善之后,这时我就遇到了一个比较棘手的问题,因为实际上虽然看着飞机是在云海中穿梭,但是仅仅是在通道中飞行,背景其实也只是平面贴图,所以当视角到达某种程度的时候就会有强烈的违和感和不真实感,就需要一个视角限制,使视角的调整刚刚好在一个范围内。
视角限制的话一般是限制 g3d 的 eye 和 center ,不太了解的朋友可以去看 hightopo 官网中的 3d 手册,里面有详细的说明,这里我就不再赘述了;因为视角范围的关系,所以我决定固定 center 的位置,代码如下:
g3d.addPropertyChangeListener(e => {
    // 固定中心点
    if (e.property === 'center') {
        e.newValue[0] = center[0];
        e.newValue[1] = center[1];
        e.newValue[2] = center[2];
    }
}
然后再把 eye 限制在某一个范围内就大功告成了,然而这里却并不是那么简单,最开始我把 eye 限制在一个立方体的空间内,但交互效果很不理想,考虑到 g3d 默认交互中,鼠标拖拽平移视角变换时,实际上 eye 是在一个以 center 为球心的球面上运动的,所以我决定从这个球中挖出来一块作为 eye 的限制空间,也就是球扇形,不太理解的朋友可以参考这个图:

球扇形视角限制,一共需要三个参数,分别是中心参考轴、中心轴和外边所成角度、所在球限制半径,其中中心参考轴可根据初始 eye 和 center 的连接延长线确定,所在球限制半径又分最大限制和最小限制,代码如下:
function limitEye(g3d, eye, center, options) {
    var limitMaxL   = options.limitMaxL,
        limitMinL   = options.limitMinL,
        limitA      = options.limitA;
    g3d.addPropertyChangeListener(e => {
        // 固定中心点
        if (e.property === 'center') {
            e.newValue[0] = center[0];
            e.newValue[1] = center[1];
            e.newValue[2] = center[2];
        }
        // 限制视角
        if (e.property === 'eye') {
            var newEyeV = new ht.Math.Vector3(e.newValue),
                centerV = new ht.Math.Vector3(center),
                refEyeV = new ht.Math.Vector3(eye),
                refVector = refEyeV.clone().sub(centerV),
                newVector = newEyeV.clone().sub(centerV);
            if (centerV.distanceTo(newEyeV) > limitMaxL) {
                newVector.setLength(limitMaxL);
                e.newValue[0] = newVector.x;
                e.newValue[1] = newVector.y;
                e.newValue[2] = newVector.z;
            }
            if (centerV.distanceTo(newEyeV) < limitMinL) {
                newVector.setLength(limitMinL);
                e.newValue[0] = newVector.x;
                e.newValue[1] = newVector.y;
                e.newValue[2] = newVector.z;
            }
            if (newVector.angleTo(refVector) > limitA) {
                var oldLength = newVector.length(),
                    oldAngle  = newVector.angleTo(refVector),
                    refLength = oldLength * Math.cos(oldAngle),
                    vertVector,
                    realVector,
                    realEye;
                refVector.setLength(refLength);
                newEyeV = newVector.clone().add(centerV);
                refEyeV = refVector.clone().add(centerV);
                vertVector = newEyeV.clone().sub(refEyeV);
                vertLength = refLength * Math.tan(limitA);
                vertVector.setLength(vertLength);
                realVector = vertVector.clone().add(refEyeV).sub(centerV);
                realVector.setLength(oldLength);
                realEye = realVector.clone().add(centerV);
                // 防止移动角度大于 180 度,视角反转
                if (oldAngle > Math.PI / 2) {
                    realEye.negate();
                }
                e.newValue[0] = realEye.x;
                e.newValue[1] = realEye.y;
                e.newValue[2] = realEye.z;
            }
        }
    })
}

飞机监控系统
当然作为监控系统,自然要有监控了,增加右下角的小地图,并提供三种模式,分别是聚焦飞机,聚焦飞行轨迹和聚焦地图,并根据飞机的飞行方向控制飞行轨迹的流动效果,其中聚焦飞机会跟随飞机移动进行 fitData,使飞机一直处于小地图的中心,代码如下:
var fitFlowP = function (e) {
    if (e.property === 'position' && e.data === plane) {
        mapGV.fitData(plane, false);
    }
};
buttonP.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        map.a('fitDataTag', 'plane2D');
        mapGV.fitData(plane, false);
        mapDM.md(fitFlowP);
    }
});
buttonL.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        mapDM.umd(fitFlowP);
        map.a('fitDataTag', 'flyLine');
        mapGV.fitData(flyLine, false);
    }
});
// ...省略

增加鼠标移到飞机相应位置进行名称的提示、双击后显示飞机相应位置的信息面板并将视角聚焦到面板上、点击飞机任意地方切换回飞机飞行模式等效果。

左侧增加监控面板替代上面提到的双击相应位置这步操作直接聚焦到相应位置的信息面板上,这里按钮开启了交互并添加了相应的交互逻辑,代码如下:
button_JC.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        event.preventDefault();
        let g3d = G.g3d,
            g3dDM = G.g3d.dm();
        g3d.fireInteractorEvent({
            kind: 'doubleClickData',
            data: g3dDM.getDataByTag(data.getTag())
        })
    }
});
//...省略

天空渲染效果
既然是监控系统肯定是 24 小时无差别的监控,这就涉及到一个问题,我总不可能半夜的时候飞机也从瓦蓝瓦蓝的天空上飞过,这就很欠缺真实性了,所以要有一个天空从亮到暗再从暗到亮的过程,这个过程我暂定到 06:00-06:30 和19:00-19:30 这两个时间段。
天空采用的是 shape3d : 'sphere' 球形,包裹整个场景,然后使用 reverse.flip 背拷贝 和 blend 染色,之后天空就可以渲染成我想要的颜色,如果按照时间改变天空明暗只要改变染色值就可以了。
但是由于白天和晚上光照情况的不同,云反射光的强度也不同,就导致了白天和晚上云的差异,所以也要调整云道和云背景的贴图的 opacity 透明度,晚间更为透明度,代码如下:
if ((hour > 6 && hour < 19) || (hour == 6 && minutes >= 30)) {
    timePane && timePane.a({
        'morning.visible': false,
        'day.visible': true,
        'dusk.visible': false,
        'night.visible': false,
        'day.opacity': 1
    })
    skyBox.s({
        "shape3d.blend": 'rgb(127, 200, 240)',
    })
    cloudBackground.s({
        "back.opacity": 0.7,
    })
    clouds.s({
        "shape3d.opacity": 0.7,
    })
} else if ((hour < 6 || hour > 19) || (hour == 19 && minutes >= 30)) {
//...省略
} else if (hour == 6 && minutes < 15 ) {
//...省略
} else if (hour == 6 && minutes >= 15 && minutes < 30) {
//...省略
} else if (hour == 19 && minutes < 15) {
//...省略
} else if (hour == 19 && minutes >= 15 && minutes < 30) {
//...省略
}

这里我还增加了对右上角时间面板时间状态图标的支持,并增加了图标切换时的渐隐渐显效果,同时给时间面板状态图标位置增加了点击切换到下一时间状态的功能。
为了演示效果我增加了时间倍速按钮,下图是 500 倍时间流速下的变化情况:

总结
通过这个 demo ,我发现生活中有很多没有被人所注意到的细节都存在数据可视化的可能,在这个大数据的时代更多的可能性值得被人发掘出来,不要错个身边每一个值得数据可视化的细节,这样不仅可以更好的挖掘 HT for Web 的潜力,也可以加强自身身为一个程序员的综合素质。
基于 HTML5 WebGL 的民航客机飞行监控系统的更多相关文章
- 基于 HTML5 WebGL 的 智慧楼宇能源监控系统
		前言 21世纪,在能源危机和全球气候变暖的压力下,太阳能等可再生能源越来越受到关注,其中光伏建筑一体化逐渐成为绿色发展方式和生活方式,加强节能降耗,支持低碳产业和新能源.可再生能源发展,也已经成为国家 ... 
- 基于 HTML5 WebGL 的故宫人流量动态监控系统
		前言 在当代社会,故宫已经成为一个具有多元意义的文化符号,在历史.艺术.文化等不同领域发挥着重要的作用,在国际上也成为能够代表中国文化甚至中国形象的国际符号.近几年故宫的观众接待量逐年递增,年接待量已 ... 
- 基于 HTML5 WebGL 的 3D 棉花加工监控系统
		前言 现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加下厂的设备突然出现故障时,控制程序需要更换.这种情况下,首先需要客户向设备生产厂家请求派出技术人员进行维护,然后生产厂家才能根据情况再 ... 
- 基于 HTML5 WebGL 的加油站 3D 可视化监控
		前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ... 
- 基于 HTML5 WebGL 的 3D 工控裙房系统
		前言 工业物联网在中国的发展如火如荼,网络基础设施建设,以及工业升级的迫切需要都为工业物联网发展提供了很大的机遇.中国工业物联网企业目前呈现两种发展形式并存状况:一方面是大型通讯.IT企业的布局:一方 ... 
- 基于 HTML5 WebGL 的 3D 仪表数据监控
		工控仪表重点发展基于现场总线技术的主控系统装置及智能化仪表.特种和专用自动化仪表:全面扩大服务领域,推进仪器仪表系统的数字化.智能化.网络化,完成 自动化仪表从模拟技术向数字技术的转变:推进具有自主版 ... 
- 基于 HTML5 WebGL 的污水处理厂泵站自控系统
		前言 一道残阳铺水中,半江瑟瑟半江红.随着城市建设的迅速发展,每年都有大量新建管网水管通水运行.城市中有大量的排水设备,形成相应的城市排水系统,排水系统由检查井.排水泵站.污水处理厂.雨水口.排放口等 ... 
- 基于 HTML5 + WebGL 的宇宙(太阳系) 3D 可视化系统
		前言 近年来随着引力波的发现.黑洞照片的拍摄.火星上存在水的证据发现等科学上的突破,以及文学影视作品中诸如<三体>.<流浪地球>.<星际穿越>等的传播普及,宇宙空间 ... 
- HTML5+WebGL 的加油站 3D 可视化监控
		前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ... 
随机推荐
- 使用mingw编译完整Qt5的过程(使用了niXman的msys套装)good
			使用mingw编译完整Qt5的过程 坛子里似乎已经有人编译出Qt5了,不过大多有问题,不是缺少opengl就是缺少openssl,还有缺少webkit的,本文提供的仍然不能说是绝对完整的,不过相对以前 ... 
- 使用BCP批量导入数据
			本文原创,转载请标明出处 BCP 工具的使用 The bulk copy program utility (bcp) bulk copies data between an instance of M ... 
- 【canvas】基础练习二 文字
			demo1 fillText strokeText绘制文字 <!DOCTYPE html> <html lang="en"> <head> &l ... 
- 自己动手写jQuery插件---Tip(提示框)
			对jQuery相信很多同学和我一样平时都是拿来主义,没办法,要怪只能怪jQuery太火了,各种插件基本能满足平时的要求.但是这毕竟不是长久之道,古人云:“授之以鱼,不如授之以渔”. 为了方便之前没有接 ... 
- Spring5源码深度分析(二)之理解@Conditional,@Import注解
			代码地址: 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象2.@Import注解快速注入第三方bean对象3.@EnableXXXX 开启原理4.基于ImportBe ... 
- 《Effective Java》-——用私有构造器或者枚举类型强化Singleton属性
			Singleton指仅仅被实例化一次的类.Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统.使类成为Singleton会使它的客户端测试变得十分困难,因为无法给Si ... 
- UI-grid 表格内容可编辑(enableCellEdit可指定列编辑)
			在网上搜索了很多关于UI-Grid的问题 很遗憾好少啊啊啊 不过有API还是比较欣慰的 官方API:UI Grid 还有一位大佬的翻译的中文API:angularjs ui-grid中文api 行编辑 ... 
- 【ubuntu】软件安装与apt-get下载软件的存放位置
			系统:Ubuntu16.04 常用的软件安装方式有两种: 第一种:apt-get(安装后略类似于windows中的安装版软件): 例:apt-get install ssh 1.下载的软件存放位置 / ... 
- centos6.5虚拟机配置Nat模式连接外网
			想来在虚拟机上搭点软件,于是乎就想让虚拟机连上外网,就用到了Nat模式,自己对网络了解不是太深,以至于配置联网花了一下午.总结下联网步骤. (1)点击虚拟网络编辑器 (2)注意以下几点标红处 (3)点 ... 
- code forces 1173 C. Nauuo and Cards
			本文链接:https://www.cnblogs.com/blowhail/p/10990833.html Nauuo and Cards 原题链接:http://codeforces.com/con ... 
