导读

阅读完此文,你会了解:1、常见的地理数据可视化图层及分类;2、GeoJSON编码格式;3、点的图层如何实现
4、OD弧线图层如何实现
5、热力图层如何实现

数据可视化图层

底图 vs 数据可视化图层

通过之前的文章,即GIS坐标体系、相机控制、数据源的金字塔构成以及二进制的解析,可以得到一个基础的矢量瓦片数据信息,包括:道路、水域、功能面、建筑、POI等信息,辅以前几篇文章(查看请访问本文末尾链接)提到的建筑的渲染、文字的渲染,再加上道路的渲染(有机会再讲)、功能面、水,基本可以构成一个较为完整的地理信息地图引擎。

iotdb可视化_图层

地理信息地图

瓦片或矢量瓦片作为地图的底图,通常只用做地理信息的表达,如果想在地理信息底图的基础上绘制,就需要疯狂输出可视化图层,这种图层通常用来快速制作出如散点、轨迹、区面、热力图等地理位置相关的可视化作品。

       

iotdb可视化_iotdb可视化_02

图层示意

地图底图和可视化图层的关系,就好比 Mapbox和Deck.gl,高德和Loca.js。

数据可视化图层分类

经过与Loca、Deck.gl的分析和收集对比,我们根据数据源类型(点、线、面)以及可视化后的呈现方式,将图层大致分为了5类:

  • 点类型数据图层

       

iotdb可视化_数据_03

    

  • 线类型数据图层

       

iotdb可视化_iotdb可视化_04

     

  • 面类型数据图层

       

iotdb可视化_图层_05

     

  • 热力类型图层

       

iotdb可视化_iotdb可视化_06

     

  • 其他数据模型图层

       

iotdb可视化_图层_07

样式和数据定义

数据格式

数据结构的定义我们采用了GeoJSON的编码格式,GeoJSON是一种对各种地理数据结构进行编码的格式,每一条数据,都叫做一个特征(Feature),特征的几何类型包括:

1、点:Point、MultiPoint

2、线:LineString、MultiLineString

3、面:Polygon、MultiPolygon

       

iotdb可视化_od找数据 遇到dll_08

GeoJSON特征

geometry属性用来描述几何属性,properties用来描述其他属性,例如:投影、bbox等。

更多规则可以参考GeoJSON规范文档:https://geojson.org/geojson-spec.html

样式格式

在样式格式上,我们借鉴了OpenLayers的样式规则,使用"image"字段来描述点的样式信息,"fill"字段描述(面)填充色样式,"stroke"字段来描述(面、线)的描边样式。

iotdb可视化_插值_09

样式结构

样式合并策略

图层中特征样式的修改和设置,有时候会需要重新生成顶点和面,例如将一个高度为0的行政区设置为有高度值的行政区多边形。如果频繁设置样式,会造成CPU的消耗。为了规避这类问题,我们对样式设置的操作设置了优先合并,延迟更新的策略。即在同一帧内的更新样式后,不立即生效,更新样式的合并,在下一次事件循环周期的渲染前将样式绑定到数据上,渲染结束后清空更新样式。



iotdb可视化_od找数据 遇到dll_10

样式更新逻辑

在实现和实践图层的时候遇到了很多坑点,每个图层都可以长篇大论一番。下面就挑一些常见的,有通识性问题的图层做一些实践上的分享。

点图层

绘制地理的点数据,是在做地理信息可视化中的基本诉求,可以用图标、图形做标注,用文字做信息描述。

尺寸单位

在二维的地图中,点数据通常都是矢量呈现方式,即点的大小为像素数,随着地图缩放,点的大小和尺寸保持不变。在三维的地图中的绘制点的诉求就会产生分歧,常见的三维场景,通常都是近大选小的,即随着地图缩放(相机远近),点会和周围物体一样有视觉上的尺寸的变化,因此在做三维场景中地图的点,需要考虑点的尺寸单位是物理单位(米),还是像素单位(px)。

渲染朝向

除了尺寸单位,还需要考虑点的朝向,二维场景中,所有的特征都是朝向屏幕的,在三维场景中,随着相机的倾斜,会有场景需要场景中的数据点随之一起倾斜,这就需要在生成点的Mesh的时候,也将朝向考虑进去。

综合尺寸单位和渲染朝向,现在就有了4种排列组合方式:

  • 朝向屏幕,以像素为单位:常规需求,常见图标打点

       

iotdb可视化_数据_11

  • 朝向天空,以像素为单位:使用场景例如路的名称

       

iotdb可视化_插值_12

        

  • 朝向天空,以米为单位:应用场景例如扫描动画,表现物理影响范围

       

iotdb可视化_数据_13

  

  • 朝向屏幕,以米为单位:适合固定规模的场景,缩小时不重叠

       

iotdb可视化_数据_14

实现方案

对于每一个点,都用4个顶点+2个三角形面来表示,其中四个顶点的坐标在CPU中计算为同一个位置,增加anchor信息辅助描述顶点的拉伸方向,增加offset信息描述点的偏移量,这样就可以在顶点着色器中,动态根据朝向和尺寸计算三角形面的具体顶点。

   

iotdb可视化_数据_15

点面计算逻辑

朝向屏幕,以像素为单位的实现方式,与常规顶点计算类似,需要在计算完矩阵投影后,做顶点拉伸和加减偏移量。

// 朝向屏幕 + 像素单位vec2 r_anchor = m_ratation * (a_anchor * a_size);vec4 projected_position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.);gl_Position = vec4(projected_position.xy / projected_position.w + (r_anchor * a_scale + offset ) / u_resolution * 2.0, 0.0,1.0);

同理的还有朝向屏幕,以米为单位的场景,需要在最后的拉伸再乘一个当前视窗像素与物理单位的比例。

// 朝向屏幕 + 物理单位float mileScale = dist * PXSCALE; vec3 newPosition = vec3(position.xy + a_offset * u_cameraScale, position.z);vec4 projected_position = projectionMatrix * modelViewMatrix * vec4(newPosition.xyz, 1.);gl_Position = vec4(projected_position.xy / projected_position.w + (r_anchor * a_scale) / mileScale / u_resolution, 0.0,1.0);

朝向天空,以米为单位,需要先算出点的拉伸和偏移,再做投影矩阵运算。

// 朝向天空 + 物理单位vec3 offsetPosition = vec3(position.xy + (r_anchor * vec2(1., -1.) * a_scale + a_offset) * u_cameraScale, position.z);gl_Position = projectionMatrix * modelViewMatrix * vec4(offsetPosition, 1.);

朝向天空,以像素为单位的类似,也需要先计算拉伸和偏移,此外需要多计算一个视窗像素与物理单位的比例。

// 朝向天空 + 像素单位float pxScale = dist * PXSCALE * u_cameraScale;vec3 offsetPosition = vec3(position.xy + (r_anchor * vec2(1., -1.) * a_scale + a_offset) * pxScale * 2.0, position.z);gl_Position = projectionMatrix * modelViewMatrix * vec4(offsetPosition, 1.);

OD弧线图层

在地图场景中,OD线通常用来绘制起点和终点之间的某种关系,例如表示人口迁徙、航班、DDOS攻击等。绘制OD线的难点,在于如何生成一根“面线”。

等宽线计算

在WebGL中,绘制一根有粗度的线,是需要计算线的面网格的。可以通过相邻的两个点,计算法线方向,沿着法线方向,做宽度的拉伸,从而实现一根“面线”。

iotdb可视化_图层_16

"面线"计算原理

如果需要朝向屏幕,按照像素单位宽,需要在着色器中动态计算线粗的拉伸值,具体的可以参考之前的文章《如何在WebGL中画一根2px的线》,此外,现在Three.js的官网扩展示例中,也提供了LineGeometry,也可以满足粗线的需求。

弧线计算

目前地理场景中弧线的计算,通常以大圆弧线居多。什么是大圆弧线呐?就是球面上两点最短的路径。在墨卡托投影完就是弧线的形状。大圆弧线路径的计算方式通常以二分法插值为主,即根据两点计算出中心点坐标,依次类推,计算出其他插值点。

iotdb可视化_图层_17

二分法插值

大圆弧线也有其对应的弊端,就是在穿过北极或南极附近的线路,会表现异常。

iotdb可视化_插值_18

异常的大圆弧线

除此以外,平面的OD弧线,我们还使用了二次贝塞尔曲线计算弧线的方法。两点的OD线,将第三个控制点选择在两点中心的垂线方向上,从而得到贝塞尔曲线的三个控制点。

       

iotdb可视化_iotdb可视化_19

贝塞尔控制点计算

三维场景的中的立体OD弧线也很常见,就是将弧度映射到海拔上(z轴),其插值点的计算方式也比较简单,将OD线的弧度均分N份,每份弧度的sin值可以计算出当前插值点的高度,根据弧度cos值可以推算当前插值点在OD线上的比例t,那么线段上任意一点,都可以通过(1-t) * a + t * b得到。

iotdb可视化_数据_20

3D圆弧插值计算示意

应用场景

最终实现效果如下图(中央委员教育迁徙),用双色表示OD方向,黄色表示起点,白色表示终点。       

iotdb可视化_od找数据 遇到dll_21

中央委员教育迁徙OD图

热力图层

热力图是以颜色来表现数据强弱大小及分布趋势,在三维地图场景中,还可以借助高度来提升立体感。

实现热力图,需要做两个阶段的渲染,第一个阶段是密度的渲染,通常会用颜色的某一个通道,颜色叠加得到密度。我们使用到了点云颜色叠加高亮,利用r通道做密度的判断。需要注意的是,如果在地图缩放的过程中,想得到动态连续的热度变化,需要在视窗变化的时候对密度图持续更新。

 

iotdb可视化_od找数据 遇到dll_22

密度图

第二阶段的渲染,其实体是一个网格平面。

iotdb可视化_od找数据 遇到dll_23

热力网格wireframe

根据密度图的r通道,对应计算热力的颜色和高度值。在计算密度时,可以利用贝塞尔曲线对r做一些处理,使得在高度和颜色的变化上没有那么陡峭。

vec2 toBezier2(float t, vec2 P0, vec2 P1, vec2 P2, vec2 P3){  float t2 = t * t;  float one_minus_t = 1.0 - t;  float one_minus_t2 = one_minus_t * one_minus_t;  return (P0 * one_minus_t2 * one_minus_t + P1 * 3.0 * t * one_minus_t2 + P2 * 3.0 * t2 * one_minus_t + P3 * t2 * t);}vec2 toBezier(float t, vec4 p){  return toBezier2(t, vec2(0.0, 0.0), vec2(p.x, p.y), vec2(p.z, p.w), vec2(1.0, 1.0));}

在热力着色时,需要两张纹理,第一张是密度图,第二章是热力渐变纹理,在片元着色器中,将密度值用做uv的s向量取色就OK啦。

       

iotdb可视化_插值_24

热度渐变纹理

       

iotdb可视化_数据_25

热力图

其它可视化图层的残坑点和代码细节未来有机会再逐一展开分享。

往期回顾

打造服务B端客户的酷炫3D地图可视化产品

数据源与存储计算

地图交互与姿态控制

地图文字渲染

地图建筑渲染

地图建筑建模制作与输出