基于 HTML5 WebGL 的故宫人流量动态监控系统
前言
在当代社会,故宫已经成为一个具有多元意义的文化符号,在历史、艺术、文化等不同领域发挥着重要的作用,在国际上也成为能够代表中国文化甚至中国形象的国际符号。近几年故宫的观众接待量逐年递增,年接待量已突破千万,根据故宫的文物特点与开放模式,必须及时建立一套完整的集监控与防患应急于一体的现代化监控系统。
故宫人流量动态监控系统采用 Hightopo 的 HT for Web 产品来构造 故宫 3D 动态可视化场景,通过将现场部署的传感器、监控设备等装置与智能联网设备集成到互联网上,对故宫当前的人流状态、人流拥挤度进行实时监测,并生成人流量热力图直观的展示现场人流数据,以预防拥挤、踩踏等意外事故的发生。
预览地址:故宫人流量动态监控系统
整体预览图:
全景图预览:
代码实现
创建场景
项目目录结构如下:
index.js 是 src 下的入口文件,创建了一个 由 main.js 中导出的 Main 类,Main 类中创建 3D 组件和 2D 组件,利用 g2d.deserialize() 方法将 json 矢量背景图反序列化显示在 2D 组件上并利用 this.load() 方法进行 3D 场景的加载工作,在 Main 类中使用了 HT 自带的事件派发器,this.event.fire() 和 this.event.add() 分别是派发事件和订阅事件,在本示例中通过事件订阅与派发完成3D场景的切换效果,关键代码如下:
import util from '../util/util';
import forbiddenCity from './forbiddenCity.js'
import heatMap from './heatMap.js'
import loadScene from './loadScene.js'
class Main {
constructor() {
let g3d = (this.g3d = new ht.graph3d.Graph3dView());
this.g3dDm = this.g3d.dm();
let g2d = (this.g2d = new ht.graph.GraphView());
this.g2dDm = this.g2d.dm();
//将 3D 组件加入到 body 下
g3d.addToDOM();
// 将 2D 组件加入到 3D 组件的根 div 下,父子 DOM 事件会冒泡,这样不会影响 3D 场景的交互
g2d.addToDOM(g3d.getView());
// 初始化场景
this.init();
}
init() {
// 2D面板加载
this.g2d.deserialize('displays/htproject_2019_q4/故宫/首页.json', (json, dm, g2d, datas) => {
});
this.forbiddenCity = new forbiddenCity(this);
this.heatMap = new heatMap(this);
// 首页3D场景加载
this.load(this.forbiddenCity);
// 订阅事件
this.addListener(e => {
if (e.type === 'loadforbiddenCity') {
this.load(this.forbiddenCity);
} else if (e.type === 'loadheatMap') {
this.load(this.heatMap);
}
});
}
load(scene) {
let old = this.activeScene;
if (old) {
old.tearDown();
}
this.activeScene = scene;
scene.setUp();
}
fire(e) {
this.event.fire(e);
}
addListener(cb, scope) {
this.event.add(cb, scope);
} }
export default Main;
由上可以看出在 Main 类中我们通过订阅事件提供了场景切换的代码,即通过调用两个场景文件中的 setUp() 方法来完成 3D 场景的切换让我们来看下在 forbiddenCity.js 与 heatMap.js 中是如何进行场景切换的:
setUp() {
let g3d = this,
dm3d = g3d.dm();
super.setUp();
util.setSceneLevel('forbiddenCity');
// 清空数据容器
dm3d.clear();
// 反序列化 3D 图纸
g3d.deserialize('scenes/htdesign/city/故宫/故宫.json', (json, dm, g3d, datas) => { });
}
setUp() {
let g3d = this,
dm3d = g3d.dm();
super.setUp();
util.setSceneLevel('heatMap');
// 清空数据容器
dm3d.clear();
// 反序列化 3D 图纸
g3d.deserialize('scenes/htdesign/city/故宫/热力图.json', (json, dm, g3d, datas) => { });
}
以上代码可以看出我们在每次切换场景时都会调用数据容器的 clear() 方法来清空数据然后再调用 g3d.deserialize() 方法反序列化加载新场景图纸,从而完成新旧场景的加载和清空。
投影实现
为增强 3D 场景的立体感,在最新版本的 HT 核心包中新增了场景投影效果配置函数,用户通过调用 enableShadow() 和 disableShadow() 方法可以实现开启关闭 3D 投影效果,此外还可以通过设置 node.s('shadow.cast', false) 对部分不需要投影的模型进行投影关闭处理,投影关键代码:
import util from '../util/util';
const loadScene = {
shadow(g3d) {
var ssc = function(filter) {
var nodes = g3d.dm().toDatas(filter);
if (!nodes.length) {
return;
};
nodes.each(function(node) {
node.s('shadow.cast', false);
});
}
var nameFilter = function(name) {
return function(node) {
return node.getDisplayName() === name;
}
}
var typeFilter = function(type) {
return function(node) {
return node.s('shape3d') === type;
}
}
ssc(nameFilter('路线'));
ssc(nameFilter('布景'));
ssc(nameFilter('灯光'));
ssc(typeFilter('models/医疗/阴影_1.json'));
ssc(typeFilter('models/医疗/地面.json'));
ssc(typeFilter('models/htdesign/Identification/point/riangle_01.json'))
// 为了编组用的 box
ssc(typeFilter('box'));
if (util.getSceneLevel() === 'forbiddenCity') {
g3d.enableShadow({
// 投影 x 轴角度
degreeX: 55,
// 投影 z 轴角度
degreeZ: -35,
// low / medium / high / ultra / 4096数值
quality: 4096,
// 深度浮点偏差补足
bias: -0.0003,
// none / hard / soft
type: 'soft',
// type 为 hard / soft 时,补充的边缘厚度,用来提供更柔和的边缘
radius: 1.0,
// 阴影强度, 1 为黑色
intensity: 0.45
});
g3d.iv();
} }
}
export default loadScene
动画实现
飞鸟动画
飞鸟动画可以拆分为两个步骤:1.飞鸟沿固定路线环绕故宫的飞行动作以及上下位置变化动作,2.飞鸟自身的翅膀扇动动作。我们使用 HT 自带的 ht.Default.startAnim 函数让飞鸟模型沿着三维空间管道做周期运动,在动画中定义了一个变量 count 每次动画都递增,通过 Math.cos(count % 36 * 10 * Math.PI / 180) 函数使值在 1 和 -1 之间做周期变化,配合 setRotationZ() 方法改变翅膀在 3D 拓扑中沿 z 轴的旋转角度从而达到飞鸟翅膀上下扇动,关键代码如下:
// 飞鸟动画
flyerAnim(g3d) {
const dm3d = g3d.dm();
let polyline = dm3d.getDataByTag('polyline');
let flyers = dm3d.getDataByTag('flyers');
let count = 0;
let radomArr = [this.random(20, 80),
this.random(30, 100),
this.random(10, 60),
this.random(10, 50),
this.random(5, 20),
this.random(20, 70)
];
if (polyline) { let anim = {
// 动画周期毫秒数
duration: 40000,
easing: function(t) {
return t;
},
action: (v, t) => {
if (util.getSceneLevel() !== 'heatMap' && polyline) {
let length = g3d.getLineLength(polyline);
// 获取三维空间管道坐标
if (length) {
let offset = g3d.getLineOffset(polyline, length * v),
point = offset.point,
tangent = offset.tangent,
px = point.x,
py = point.y,
pz = point.z,
tx = tangent.x,
ty = tangent.y,
tz = tangent.z;
flyers.eachChild((bird, index) => {
let ty = bird.getTag().split('_')[1];
let positionZ = pz + index * 50 + radomArr[index] / 3,
positionX = px + (index - 3) * 50 + radomArr[index] / 3,
positionY = py + radomArr[index] / 5;
if (index > 2) positionZ = pz - (index - 6) * 50 + radomArr[index] / 3;
// 设置飞鸟翅膀扇动动画
const pos = count + index,
pos2 = count - index * 6;
if (pos2 > 0) {
if (!bird._posId) bird._posId = pos2;
bird._posId++;
if (bird._posId > index * 100 + 500 && bird._posId < index * 100 + 600) {
bird.eachChild((child) => {
if (child.getTag() === 'wingLeft') {
child.setRotationZ(0);
} else if (child.getTag() === 'wingRight') {
child.setRotationZ(0);
}
});
if (bird._posId === index * 100 + 599) bird._posId = 1;
} else {
bird.eachChild((child) => {
if (child.getTag() === 'wingLeft') {
child.setRotationZ(child.r3()[2] + Math.cos(bird._posId % 36 * 10 * Math.PI / 180) * 4 * 0.03);
} else if (child.getTag() === 'wingRight') {
child.setRotationZ(child.r3()[2] - Math.cos(bird._posId % 36 * 10 * Math.PI / 180) * 4 * 0.03);
}
});
}
}
// 设置飞鸟飞行轨道动画
bird.p3(positionX + radomArr[index] * v, positionY + radomArr[index] * v + Math.cos(count % 36 * 10 * Math
.PI / 180) * ty * 5, positionZ + radomArr[index] *
v);
// 设置飞鸟朝向位置
bird.lookAt([positionX + radomArr[index] * v + tx, positionY + ty + radomArr[index] * v, positionZ +
radomArr[index] * v + tz
]);
})
count++;
} }
},
finishFunc: function() {
// 继续执行飞鸟管道动画
this.birdAnim = ht.Default.startAnim(anim);
}
};
if (util.getSceneLevel() === 'forbiddenCity') {
// 执行飞鸟管道动画
this.birdAnim = ht.Default.startAnim(anim);
}
}
}
鸟瞰漫游动画
在飞鸟动画实现的前提下,接下来我们可以进一步以飞鸟模型为中心来生成鸟瞰漫游动画。首先使用 ht.Default.startAnim 函数实时调用飞鸟所在位置,通过 setEye() 和 setCenter() 方法动态设置场景的中心点和相机位置,以此达到从飞鸟的视角俯瞰整个故宫场景的动画效果。关键代码如下:
// 鸟瞰漫游动画
roamingAnim() {
const g3d = this.g3d;
let flyers = g3d.dm().getDataByTag('flyers');
let anim = {
duration: 60000, // 动画周期毫秒数
easing: function (t) {
return t * t;
},
action: function (v, t) {
let flyersP = flyers.p3();
let px = flyersP[0];
let py = flyersP[1];
let pz = flyersP[2];
g3d.setEye(px, py + 50, pz - 400);
g3d.setCenter(px, py, pz);
}
}
this.roaming = ht.Default.startAnim(anim);
}
景深动画
在 HT for Web 中为 3D 组件提供了 enablePostProcessing() 方法,使用者可以通过调用该方法手动开启 3D 场景的景深模糊效果,另外还可以通过设置 aperture 属性改变景深模糊度,在本示例中通过动态改变 aperture 属性形成淡入淡出效果以减少场景切换时的突兀感,关键代码如下:
// 景深动画
depthAnim(g3d, x = 0) {
let dof = g3d.getPostProcessingModule('Dof');
// 景深开启
g3d.enablePostProcessing('Dof', true);
return new Promise((resolve, reject) => {
let anim = {
duration: 1000,
easing: (t) => {
return t * t;
},
action: (v, t) => {
// 动态设置景深阈值
dof.aperture = x - v * 0.02
if (v == 1) resolve('end');
}
}
ht.Default.startAnim(anim);
})
}
主要功能
人流量热力图
热力图以特殊高亮的形式显示游客所在的地理区域的图示,可以非常直观的展示人流量密度信息。本示例中使用 HT 封装的 ht.thermodynamic.Thermodynamic3d() 方法动态生成热力图,关键代码如下:
createHeatMap(heatMapName, num) {
const g3d = this.g3d;
const dm3d = g3d.dm();
let room = dm3d.getDataByTag(heatMapName);
// 获取要生成热力图的矩形区域
let heatRect = room.getRect();
let Vector3 = ht.Math.Vector3;
let tall = 30
let {
x,
y,
width,
height
} = heatRect;
if (width === 0 || height === 0) return
let templateList = [];
// 在热力图区域随机生成 num 个热力点位
for (let index = 0; index < num; index++) {
templateList.push({
position: {
x: this.random(0, heatRect.width),
y: this.random(0, heatRect.height),
z: tall
},
temperature: {
value: 30 + this.random(0, 20),
radius: 90
},
})
}
// 热力图初始化
let thd = window.thd = new ht.thermodynamic.Thermodynamic3d(g3d, {
box: new Vector3(width, height, tall),
min: 15,
max: 55,
interval: 200,
remainMax: false,
opacity: 0.1,
colorStopFn: function (v, step) {
return v * step * step
},
gradient: {
0: 'rgba(0,162,255,0.14)',
0.2: 'rgba(48,255,183,0.3)',
0.4: 'rgba(255,245,48,0.5)',
0.6: 'rgba(255,73,18,0.9)',
0.8: 'rgba(217,22,0,0.95)',
1: 'rgb(179,0,0)',
}
});
thd.setData(templateList);
// 创建热力图
let node = thd.createThermodynamicNode(2, 2, 50);
node.setAnchorElevation(0);
node.setTag('test');
node.p3(room.p3());
node.s({
'3d.selectable': false,
'3d.movable': false,
'wf.visible': false,
'shape3d.transparent': true,
});
dm3d.add(node);
}
这里简单的描述下热力图生成步骤:1.首先确定热力图生成区域,在该区域内获取传感器位置和热力信息,并将这些信息存储在 templateList 数组中。2.将数组传入 Thermodynamic3d() 方法中并配置渐变颜色、透明度等相关信息生成热力图渲染数据。3.使用 createThermodynamicNode() 方法按照热力图渲染数据创建热力图。4.将热力图添加到数据容器中。
视频监控
我们通过 addInteractorListener 交互监听器为场景中摄像头模型绑定点击事件,每个摄像头都对应一个监控视频画面,通过点击弹出或关闭,并对窗口中显示的监控画面数量进行了限制,不得超过 4 个否则将不会继续弹出监控画面,避免显示多个画面造成场景遮挡,关键代码如下:
videoVisible(videoName) {
let g2d = this.g2d,
dm2d = g2d.dm();
// 当前选中监控画面
const video = dm2d.getDataByTag(videoName);
if (video) {
const videoList = video.getParent();
const videoRect = video.getRect();
const visible = g2d.isVisible(video);
if (visible) {
// 隐藏选中监控画面,并重新排列监控画面
this.hideVideo(videoList, video, videoRect);
} else {
// 显示选中监控画面,并重新排列监控画面 let showVideos = [];
videoList.eachChild(child => {
g2d.isVisible(child) && child !== video && showVideos.push(child)
})
if (showVideos.length < 5) {
video.s('2d.visible', true);
video.setY(util.getVideoListRect().y + (videoRect.height + 5) * showVideos.length);
}
}
}
} hideVideo(parent, video, videoRect) {
parent.eachChild(node => {
const nodeRect = node.getRect();
if (nodeRect.y > videoRect.y) {
node.setY(nodeRect.y - nodeRect.height)
}
})
video.s('2d.visible', false)
}
总结
现如今,伴随国民经济的持续高速增长,旅游行业迎来了健康发展的阶段,各大景区每年接待的游客人数都在不断增长,如果不对人流量进行控制的话将会出现许多隐患。本次示例效果均采用 HT 提供的 api 进行代码开发,旨在定制一套以人流量监测为中心的集监控与防患应急于一体的景点 3D 实时监控系统,也欢迎对 HT 感兴趣的伙伴给我留言,或者直接访问 官网 查询相关的资料。
基于 HTML5 WebGL 的故宫人流量动态监控系统的更多相关文章
- 基于 HTML5 WebGL 的 智慧楼宇能源监控系统
前言 21世纪,在能源危机和全球气候变暖的压力下,太阳能等可再生能源越来越受到关注,其中光伏建筑一体化逐渐成为绿色发展方式和生活方式,加强节能降耗,支持低碳产业和新能源.可再生能源发展,也已经成为国家 ...
- 基于 HTML5 WebGL 的 3D 棉花加工监控系统
前言 现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加下厂的设备突然出现故障时,控制程序需要更换.这种情况下,首先需要客户向设备生产厂家请求派出技术人员进行维护,然后生产厂家才能根据情况再 ...
- 基于 HTML5 WebGL 的民航客机飞行监控系统
前言 前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for ...
- 基于 HTML5 WebGL 的加油站 3D 可视化监控
前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ...
- 基于 HTML5 WebGL 的 3D 工控裙房系统
前言 工业物联网在中国的发展如火如荼,网络基础设施建设,以及工业升级的迫切需要都为工业物联网发展提供了很大的机遇.中国工业物联网企业目前呈现两种发展形式并存状况:一方面是大型通讯.IT企业的布局:一方 ...
- 基于 HTML5 WebGL 的 3D 仪表数据监控
工控仪表重点发展基于现场总线技术的主控系统装置及智能化仪表.特种和专用自动化仪表:全面扩大服务领域,推进仪器仪表系统的数字化.智能化.网络化,完成 自动化仪表从模拟技术向数字技术的转变:推进具有自主版 ...
- 基于 HTML5 WebGL 的污水处理厂泵站自控系统
前言 一道残阳铺水中,半江瑟瑟半江红.随着城市建设的迅速发展,每年都有大量新建管网水管通水运行.城市中有大量的排水设备,形成相应的城市排水系统,排水系统由检查井.排水泵站.污水处理厂.雨水口.排放口等 ...
- 基于 HTML5 + WebGL 的宇宙(太阳系) 3D 可视化系统
前言 近年来随着引力波的发现.黑洞照片的拍摄.火星上存在水的证据发现等科学上的突破,以及文学影视作品中诸如<三体>.<流浪地球>.<星际穿越>等的传播普及,宇宙空间 ...
- HTML5+WebGL 的加油站 3D 可视化监控
前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ...
随机推荐
- Koa 学习
中间件引擎 1234567891011121314151617181920212223242526272829303132333435363738 const Koa = require('koa') ...
- numpy.random模块用法总结
from numpy import random numpy.random.uniform(low=0.0, high=1.0, size=None) 生出size个符合均分布的浮点数,取值范围为[l ...
- Samtec大数据技术解决方案
序言:众所周知,大数据将在AI时代扮演重要角色,拥有海量数据的公司已在多个领域尝试对掌握的数据进行利用,大数据意识和能力进步飞快,体系和工具日趋成熟. Samtec和Molex 是获得许可从而提供 M ...
- Web网页布局的主要方式
一.静态布局(static layout) 即传统Web设计,网页上的所有元素的尺寸一律使用px作为单位. 1.布局特点 不管浏览器尺寸具体是多少,网页布局始终按照最初写代码时的布局来显示.常规的pc ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
- Netty学习(4):NIO网络编程
概述 在 Netty学习(3)中,我们已经学习了 Buffer 和 Channel 的概念, 接下来就让我们通过实现一个 NIO 的多人聊天服务器来深入理解 NIO 的第 3个组件:Selector. ...
- 进程,线程,Event Loop(事件循环),Web Worker
线程,是程序执行流的最小单位.线程可与同属一个进程的其他线程共享所拥有的全部资源,同一进程中的多个线程之间可以并发执行.线程有就绪,阻塞,运行三种基本状态. 阮一峰大神针对进程和线程的类比,很是形象: ...
- 最新版jdk 13环境变量配置
1.配置环境变量 右击“我的电脑”-->"属性"-->"高级系统设置"-->"高级"-->"环境变量&qu ...
- 2653 区间xor
前言 这个题目在我之前那篇c++位运算的的随笔中提到过. 有兴趣的话去看看吧! 飞机场:https://www.cnblogs.com/laoguantongxiegogofs/p/12444517. ...
- ggplot2(7) 定位
7.1 简介 位置调整:调整每个图层中出现重叠的对象的位置,对条形图和其他有组距的图形非常有用: 位置标度:控制数据到图形中位置的映射,常用的是对数变换: 分面:先将数据集划分为多个子集,然后将每个子 ...