前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何1人5天开发完3D数据可视化大屏,超炫酷 【二】

如何1人5天开发完3D数据可视化大屏,超炫酷 【二】

作者头像
winty
发布2020-11-23 10:10:12
1.7K0
发布2020-11-23 10:10:12
举报
文章被收录于专栏:前端Q前端Q前端Q

1. 前言

在前第一部分的文章中,分享了大屏地球的实现。

本次将会分享剩余的实现部分,文内大量干货,内容包括:

  • 平面地图的实现
  • 柱体的实现
  • 性能优化
  • 地图相关问题

2. 平面地图

平面地图的必要性在于地球无法显示完整数据。就像太阳照射地球有了昼夜。

大屏

可以看到,平面地图这种全局的数据是地球无法完整表现的。

平面地图由地图数据地图块交互三部分组成。

其中交互使用THREE.Raycaster实现,较为简单不在赘述。

2.1 地图数据

与地球的实现方法不同,平面地图依赖geojson进行绘制。有什么样的geojson,绘制什么样的地图块。

不了解geojson的开发者请先学习相关知识:GEOJSON规范[1]。

在做地图相关工作时,很多情况是没有现成的geojson供开发者们使用的。而从哪获取地图的geojson数据是很多人都会面临的问题。

此处只提供相关资源,有兴趣可以自行深入了解:

  • datav.aliyun.com[2] 阿里平台提供的中国geojson
  • gadm.org[3] 可以下载到世界范围不同级别的geojson
  • naturalearthdata.com[4] 可以下载到世界范围不同精度的geojson
  • mapshaper.org[5] 用来查看geojson,提供对geojson进行数据合并、简化和修正的功能
  • geojson.io[6]手动修改geojson数据节点的在线网站。注:在处理MultiPolygon类型数据时有bug

*注1:gadmnaturalearthdata两个国外的平台下载到的中国领土数据都是错误的,错误的数据节点可在geojson.io自行调整。

*注2:本文中分享的资源下载到的geojson基本都是完整数据,数据体积在最小几M、最大几G 之间,可使用mapshaper.org的简化功能进行缩小(会使数据失真),失真后可使用geojson.io

*注3:要注意在拼接不同来源的geojson简化geojson后,可能会出现数据点不对齐的现象,需要人工花大量时间进行对齐。

2.2 坐标映射

在准备好geojson之后,绘制时要将经纬度与xy坐标进行映射。

这里我们直接使用了经纬度 <对应> xy坐标的关系来进行绘制。这一简化的对应关系会出现格陵兰岛澳大利亚大小相似的问题。

如果对视觉表现比较严苛,各位开发者可以使用d3-geo的投影模块来避免该问题。如Natural Earth projection(自然地球投影)[7]。

对应关系为投影作为经纬度与xy坐标中间的纽带:经纬度 <=> 自然地球投影 <=> xy坐标

*注:错误的投影可能会导致格陵兰岛非洲大小相似。

2.3 地图块

地图块的实现方式很简单,使用THREE.ExtrudeGeometr(挤压几何体)[8]配合THREE.Shape来将准备好的地图数据进行绘制即可。这里不在贴代码,开发者们可查阅文档自行开发。

挤压几何体创建Mesh时,可以传入有两个材质组成的数组。第一个材质将用于其表面;第二个材质则将用于其挤压出的侧面。

MultiPolygon

在geojson中,type为MultiPolygon的数据,对应的coordinates也会有个(Polygon数据的coordinates只有1个子数据),常见的多为存在岛屿飞地的国家。

这个时候如果直接使用Shape进行连结会出现模型间拉丝连线的现象。

如果将多个子数据分别绘制为几何体可以避免前一个问题,但是在做交互时多个几何体也会以个体的形式分别进行交互。会出现选中中国,海南省不跟着亮的问题。

尽管你也可以在交互时根据数据获取相关的其他几何体。

在这里我使用Geometry.merge[9]。

  1. 将多个ExtrudeGeometry的顶点数据merge到同一个Geometry中。
  2. 将合并好的Geometry作为几何体加入到Mesh

以上两个步骤即可。

注意:在销毁时需要将被merge的ExtrudeGeometry一同销毁。

3. 立体圆柱

立体圆柱用来表示某一区域的数据比例

立体圆柱

它的特点是会把不同颜色的数据渲染在立体圆柱上。

它由纹理THREE.CylinderGeometry实现。

纹理

与一般物体不同,圆柱的纹理需要根据数据动态计算。

利用THREE.ClampToEdgeWrapping的特性,绘制了1 x 100大小的canvas来当做纹理使用。

代码中使用了d3-scale模块中的scalePow[10]做数据比例尺。

纹理计算
function getTexture(data) {
  const canvas = document.createElement('canvas');
  canvas.height = 100;
  canvas.width = 1;
  const y = d3
    .scalePow()
    .rangeRound([0, 100])
    .domain([0, d3.sum(data.map(({ value }) => value))]);
  const ctx = canvas.getContext('2d');
  let left = 0;
  data.forEach((item) => {
    const current = y(item.value);
    ctx.moveTo(0.5, 100 - left);
    ctx.lineTo(0.5, 100 - (left + current));
    left += current;
    ctx.lineWidth = 1;
    ctx.strokeStyle = item.color;
    ctx.stroke();
    ctx.beginPath();
  });

  return canvas;
}
const canvas = getTexture([
  { name: '数据1', value: 10, color: 'red' },
  { name: '数据2', value: 5, color: '#123456' }
]);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.MeshBasicMaterial({ map: texture });
// ...
CylinderGeometry

在计算好material之后还不能直接将材质交给Mesh使用。与挤压几何体传入两个材质类似,CylinderGeometry将会接收三个材质。

分别对应圆柱侧面圆柱底面圆柱顶面

如果直接将material传入,地面和顶面也将会使用侧面的材质。

所以需要这么创建:

// ...
const { size, height } = options;
const material = new THREE.MeshBasicMaterial({ map: texture });
const materialBottom = new THREE.MeshBasicMaterial({
  color: 'red',
});
const materialTop = new THREE.MeshBasicMaterial({
  color: '#123456',
});
const geometry = new THREE.CylinderBufferGeometry(
  size, size, height, 64, 20, false,
);
const cylinder = new THREE.Mesh(geometry, [
  material,
  materialBottom,
  materialTop,
]);

4. 性能优化

前面的内容已经将具体的实现方法讲解的七七八八了。

在做的过程中也遇到性能内存的问题。

这里简单说一下遇到的问题和处理的方法。

4.1 Geometry.merge 导致大量的内存无法被释放

因项目使用Vue Router前端路由,在离开大屏页面并重新进入时会触发

离开大屏前 => 销毁大屏 => 离开大屏 => ... => 回到大屏 => 重绘大屏

这一流程。

每次的绘制都会使页面增加几十M的内存占用无法被GC回收。

经过排查发现这一部分内存都是在Geometry.merge操作时增加的。

这是因为没有注意Geometry.merge,只销毁了要合并到的Geometry对象,被合并的Geometry对象没有被销毁,导致大量的顶点信息遗留在内存中无法被GC清理。

4.2 场景背景导致的卡顿

在开发过程中,发现随着窗口分辨率的越来越大,动画也会卡顿的越来越严重。

这是随着分辨率像素点的增多造成的硬性性能门槛。

在分析了交互行为以后,我采用了以下方案规避这一问题:

  • 背景场景内容分离成两个renderer
  • 只在摄像机变化渲染背景,摄像机静止时只绘制场景内容
  • 渲染背景时进行节流,原本绘制2帧 或 绘制3帧的时间长度只绘制1帧,给渲染留出更多的时间

达到的效果:

  • 整个画面在摄像机不变化时帧率稳定
  • 在鼠标拖拽移动摄像机时不会因为mousemove的频繁触发导致渲染任务阻塞在很短的时间内

缺点:

  • 背景随着节流限制的大小会有不同程度的延迟与卡顿。

但对大屏来讲,摄像机通常都是静止不动的,只有部分业务场景需要人机交互。

5. 总结

写在文章的最后。

纸上得来终觉浅,绝知此事要躬行。

开发者们还是要多去实践,在实践中验证理论知识是最有效的提升能力的方式。

参考文献

  1. GEOJSON规范 - https://geojson.org/
  2. datav.aliyun.com - http://datav.aliyun.com/tools/atlas
  3. gadm.org - https://gadm.org/
  4. www.naturalearthdata.com - https://www.naturalearthdata.com/downloads/
  5. mapshaper.org - https://mapshaper.org/
  6. geojson.io - http://geojson.io/
  7. Natural Earth projection(自然地球投影) - http://www.shadedrelief.com/NE_proj/
  8. THREE.ExtrudeGeometr(挤压几何体) - https://threejs.org/docs/index.html#api/zh/geometries/ExtrudeGeometry
  9. Geometry.merge - https://threejs.org/docs/index.html#api/zh/core/Geometry.merge
  10. scalePow - https://github.com/d3/d3-scale/blob/v3.2.2/README.md#scalePow
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端Q 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 平面地图
    • 2.1 地图数据
      • 2.2 坐标映射
        • 2.3 地图块
          • MultiPolygon
          • 纹理
          • 纹理计算
          • CylinderGeometry
      • 3. 立体圆柱
      • 4. 性能优化
        • 4.1 Geometry.merge 导致大量的内存无法被释放
          • 4.2 场景背景导致的卡顿
          • 5. 总结
            • 参考文献
            相关产品与服务
            图数据库 KonisGraph
            图数据库 KonisGraph(TencentDB for KonisGraph)是一种云端图数据库服务,基于腾讯在海量图数据上的实践经验,提供一站式海量图数据存储、管理、实时查询、计算、可视化分析能力;KonisGraph 支持属性图模型和 TinkerPop Gremlin 查询语言,能够帮助用户快速完成对图数据的建模、查询和可视化分析。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档