发布时间:2024-02-08 18:30
ThingJS 隶属于北京优锘科技有限公司,是优锘科技旗下物联网三维可视化开发平台。ThingJS采用JavaScript开发语言,主要面向前端程序员和实施人员。ThingJS平台让传统企业无需组建3D可视化开发团队,也能开发3D可视化应用。
ThreeJS
封装的一个 3d 可视化开发平台。echarts
, 阿里的AntV
,开源大热的D3JS
,但是这些框架本质上说都是 2d 的图表展示效果,当然,它们也提供了一些三维图表,但是总体上说,echarts
等平台提供的三维图表不满足很多复杂的带有一些特定交互场景的业务,所以在 web 开发方面,尤其是当下智慧城市
方向特别火热时,我们有必要寻求一款可以在 web 端进行较复杂 3d 可视化场景开发的平台。那么在这一方向,目前的技术/框架有:WebGL, ThreeJS. 那它们的关系如何呢:WebGL可以处理3D图像,听起来是非常高兴的一件事,但是WebGL实在是太底层了,WebGl解决是如何再画布上画图的问题,怎么画点,线,面,怎么上色,怎么贴图,怎么处理光线,视角转动之后怎么换算绘制等等。这些对于一个做3D应用的开发者来说要学的东西太多了。
Threejs库的出现解决了底层的渲染细节和复杂的数据结构,终于将复杂的底层细节抽象出来,使得大家开发3d应用更容易了一些。和很多开发者交流threejs都是他们首次接触的WebGL 3D库,并能很容易的就能开始做一些实验。
但是使用Threejs开发应用还是门槛很高,单就一个加载模型,调光,选择模型弹框的功能,就能干出Threejs上百行代码。同时还有很多复杂的3D概念需要理解。这时就需要ThingJS了。
简单的说,WebGL 是需要从零开发 3d 场景的基础知识,而 ThreeJS 封装了 WebGL 的很多底层细节,用 ThreeJS 来做 3d 开发更简便,而 ThingJS 在 ThreeJS 的基础上又做了一层封装,进一步简化非专业人员开发 3d 场景的门槛。
笔者以公司目前需要开发的一个业务场景为例:笔者所在公司需要开发一个 3d 可视化的智慧乡村场景,在这个 3d 场景上加上一些 echarts 大屏的图表,同时还会有一些交互。大致场景如下:
该项目的地图背景是一个倾斜摄影模型,当然倾斜摄影模型只是一部分,在其外围是一个普通的,由 ThingJS 官方提供的一个三维底图。我们需要在这个模型基础上加入一些图表的展示,同时在此模型上加入大量的地图点位,每个地图点位都是一个摄像头,可以通过点击地图点位播放实时监控视频。
开发流程
来自 thingjs 官网,可以说是价格不菲了,入手需谨慎。
其次,导入倾斜摄影模型,倾斜摄影模型的源数据来自无人机航拍。这里也是 ThingJS 提供的付费服务,我们只需付钱,将源数据交给官方人员,平台帮我们生成倾斜摄影模型。
到此开始正式开发,我们的思路是将项目分成两部分,即地图模型端
和图表端
。地图模型端主要负责处理地图模型的一些交互,如切换地图底图;从后端获取地图点位数据并渲染到地图上;点击地图点位显示点位信息,并支持视频播放。最终项目在运行时,地图模型端是以一个 iframe 的形式嵌入到图表端的。
这里有一点值得注意的是如果要实现最终的效果,不把项目分成两部分也是可以的,即把 echarts 图表部分也写在地图模型端,这样做的好处时之后不需要考虑 iframe 交互的问题。官方也提供了一个类似的例子。于是第一个坑点出现了,thingjs 是不支持目前主流的 JS 模块化方案的(ESM,UMD, CMD……),引入模块化文件需要靠官方提供的一个 api
const loadViedeJs = async () => {
return new Promise(resolve => {
THING.Utils.dynamicLoad([
"https://www.thingjs.com/uploads/wechat/xxxx/static/video-js.min.css",
"https://www.thingjs.com/uploads/wechat/xxxx/static/video.min.js"],
function (result) {
resolve(true)
},
true, // 选填 是否带时间戳
true, // 选填 是否按顺序下载
false //选填 文件是否包含加密文件
)
})
}
类似如上的写法,而如果要实现一个较为庞大复杂的业务场景时,就不太方便了,所以项目一开始便是分为地图模型端
和图表端
两部分的。
在开发流程上笔者先开发了项目的图表端的主要内容,之后再开发地图端部分。因为相比之下图表端的部分以 echarts
和传统 html 网页为主,开发起来较为简单。先易后难的开发流程较好。
<!DOCTYPE html>
<html>
<head>
<title>HTML</title>
</head>
<body>
<div style="display: inline-block">
<div style="
padding: 2px 0 4px 29px;
background: url(./assets/images/common/title-bg.png) no-repeat;
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;
">
农村基层党建
</div>
<div style="
margin-top: 4px;
padding: 35px 20px;
padding-top: 0;
background: url(./assets/images/common/data-bg.png) no-repeat;
background-size: cover;
overflow: hidden;
">
<div style="
margin: 21px auto;
width: 130px;
text-align: center;
background: url(./assets/images/common/blue.png) no-repeat;
background-size: cover;
">
<div style="
font-family: DINAlternate-Bold;
font-size: 32px;
color: #409aff;
text-shadow: 0 0 20px #19acb5;
">
500
</div>
<div style="
margin-top: 34px;
font-family: PingFangSC-Regular;
font-size: 18px;
color: #a8dedd;
letter-spacing: 0;
text-shadow: 0 1px 2px rgba(35, 26, 124, 0.61);
">
基层党支部数量
</div>
</div>
<div>
<div style="
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;
">
<img src="./assets/images/manage/decoration-1.png" style="margin-right: 7px" />
党员类型
</div>
<div style="height: 132px">
<div style="
background: url(./assets/images/common/circle.png) no-repeat;
">
<span>4,961</span>
</div>
<div>
<div><span>预备党员</span> 2,845 <span>44%</span></div>
<div><span>预备党员</span> 2,845 <span>44%</span></div>
</div>
</div>
</div>
<div>
<div style="
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;
">
<img src="./assets/images/manage/decoration-1.png" style="margin-right: 7px" />党员年龄分布情况
</div>
<div style="
padding: 25px 0;
margin-left: 46px;
width: 314px;
background: url(./assets/images/common/Academic-degree.png)
no-repeat;
background-size: cover;
">
<div style="
text-align: center;
font-family: DINAlternate-Bold;
font-size: 32px;
color: #ffffff;
letter-spacing: 0;
text-shadow: 0 0 16px #91ffff;
">
200
</div>
<div style="
text-align: center;
font-family: PingFangSC-Regular;
font-size: 18px;
color: #a8dedd;
letter-spacing: 0;
text-shadow: 0 1px 2px rgba(35, 26, 124, 0.61);
">
大专以以上学历
</div>
</div>
<div style="height: 224px">柱状图预留</div>
</div>
<div>
<div style="
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;
">
<img src="./assets/images/manage/decoration-1.png" style="margin-right: 7px" />男女占比
</div>
<div style="
display: flex;
justify-content: center;
align-items: center;
margin-top: 22px;
">
<div>
<div style="
font-family: PingFangSC-Medium;
font-size: 16px;
color: #e0f7ff;
">
男生
</div>
<div style="
font-family: DINAlternate-Bold;
font-size: 16px;
color: #44dbda;
">
567
</div>
</div>
<div style="margin: 0 22px">
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
<img src="./assets/images/man.png" />
</div>
<div>
<div style="
font-family: PingFangSC-Medium;
font-size: 16px;
color: #e0f7ff;
">
女生
</div>
<div style="
font-family: DINAlternate-Bold;
font-size: 16px;
color: #44dbda;
">
567
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
注意这里笔者没有把样式代码写在 style
标签中,之前是考虑到最终 html 代码会以模板的形式加入到 JS 代码中去,感觉如果将 CSS 代码写在 style 标签会不好插入到模板中。但是后面笔者发现其实将 CSS 代码写在模板中也未尝不可,但是需要注意的时类名的命名上尽量不要重复,因为一旦类名重复可能会影响到其他组件的样式(推荐使用 BEM 方法进行命名)。
this.clearDom() //在每次进入创建DOM之前必须清除之前的DOM
let dom = this.getDom() //获取DOM节点
let jq = this.getJquery() //获取Jquery
let data = this.getComponentData() //获取组件数据
let topTitle = data.topTitle //标签数据
let title = data.title //标题数据
let content = data.content //内容数据
const aa = `<div class="civilization" style="display: inline-block">
<div
style="padding: 2px 0 4px 29px;
background: url(/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/common/title-bg.png) no-repeat;
font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;">
农村基层党建
</div>
<div
style="margin-top: 4px;
padding: 35px 20px;
padding-top: 0;
background: url(/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/common/data-bg.png) no-repeat;
background-size: cover;
overflow: hidden;">
<div
style="margin: 21px auto;
width: 140px;
text-align: center;
background: url(/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/common/blue.png) no-repeat;
background-size: cover;">
<div
style="font-family: DINAlternate-Bold;
text-align: center;
font-size: 32px;
color: #409aff;
text-shadow: 0 0 20px #19acb5;">
500
</div>
<div
style="margin-top: 34px;
text-align: center;
font-family: PingFangSC-Regular;
font-size: 18px;
color: #a8dedd;
letter-spacing: 0;
text-shadow: 0 1px 2px rgba(35, 26, 124, 0.61);">
基层党支部数量
</div>
</div>
<div>
<div
style="font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;">
<img
src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/manage/decoration-1.png"
style="margin-right: 7px"
/>
党员类型
</div>
<div style="height: 110px">
<div
style="background: url(/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/common/circle.png)
no-repeat;">
<span>4,961</span>
</div>
<div>
<div><span>预备党员</span> 2,845 <span>44%</span></div>
<div><span>预备党员</span> 2,845 <span>44%</span></div>
</div>
</div>
</div>
<div>
<div
style="font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;">
<img
src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/manage/decoration-1.png"
style="margin-right: 7px"
/>党员年龄分布情况
</div>
<div
style="padding: 25px 0;
margin-left: 46px;
width: 314px;
background: url(/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/common/Academic-degree.png)
no-repeat;
background-size: cover;">
<div
style="text-align: center;
font-family: DINAlternate-Bold;
font-size: 32px;
color: #ffffff;
letter-spacing: 0;
text-shadow: 0 0 16px #91ffff;">
200
</div>
<div
style="text-align: center;
font-family: PingFangSC-Regular;
font-size: 18px;
color: #a8dedd;
letter-spacing: 0;
text-shadow: 0 1px 2px rgba(35, 26, 124, 0.61);">
大专以以上学历
</div>
</div>
<div style="height: 190px"></div>
</div>
<div>
<div
style="font-family: YouSheBiaoTiHei;
font-size: 20px;
color: rgba(255, 247, 224, 0.9);
letter-spacing: 0.4px;">
<img
src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/manage/decoration-1.png"
style="margin-right: 7px"
/>男女占比
</div>
<div style="display: flex;justify-content: center; align-items: center; margin-top: 22px">
<div>
<div
style="font-family: PingFangSC-Medium;
font-size: 16px;
color: #e0f7ff;">
男生
</div>
<div
style="font-family: DINAlternate-Bold;
font-size: 16px;
color: #44dbda;">
567
</div>
</div>
<div style="margin: 0 22px">
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
<img src="/uploads/wechat/oLX7p00hNvyDvp6C44l-mJtTtmAo/file/数字清水/static/images/man.png" />
</div>
<div>
<div
style="font-family: PingFangSC-Medium;
font-size: 16px;
color: #e0f7ff;">
女生
</div>
<div
style="font-family: DINAlternate-Bold;
font-size: 16px;
color: #44dbda;">
567
</div>
</div>
</div>
</div>
</div>
</div>`
jq(dom).append(aa) //创建标签、标题和内容的div
这里需要注意的是类似 this.getJquery() //获取Jquery
这样的代码,通过 this 获取 jquery 函数是由 thingsjs 平台提供的能力,作为使用开发者却又无法在全局自定义挂载一个可使用的全局工具函数。
改写完成后,将上述 JS 代码复制粘贴到 thingjs 图表端对应的空白组件代码编写区中去。
这里需要注意的一点是因为在本地调试完毕之后再复制粘贴到 thingjs 在线平台运行,一般来说是不会报什么问题的,但是因为开发环境的不同及一切其他原因,当我们粘贴代码后点击保存&运行
按钮后仍可能运行不正常,这时候只需要 f12 打开浏览器控制台慢慢排查问题即可,待问题排查解决完毕该部分的 HTML 部分功能算是暂时完成了。之后再来处理 echarts 图表部分。
echarts 图表部分的开发
在这部分的开发里,笔者发现其有些效果会触发一些 bug 导致无法实现。比如说设计稿给出的 x轴的坐标名是带有图片背景的,笔者在本地开发可以实现这样的效果,但是将代码复制到在线开发平台就不行。
接入接口数据
在将一个个模块开发完毕之后,需要接入服务端接口数据。这里笔者是通过新建一个小的空白组件的方式进行的。
这里是用 jquery 自带的 ajax 请求方法进行的请求。之后将请求到的数据通过数据总线发布订阅的方式分发到各个业务组件中。
图表端开发综述
图表端的开发难度比想象中要大不少,这种难度不仅体现在对新工具平台的使用探索上,也体现在利用发布订阅的模式总览全局上。在编码方面,由于平台提供的代码编码端体验不佳,我们不得不先在本地进行开发再将相关代码复制到在线平台,而调试修改代码也是如此繁琐,目前暂未探索出更好的方式。