Press "Enter" to skip to content

地理数据可视化

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

导读

 

阅读完此文,你会了解:

 

1、常见的地理数据可视化图层及分类;

 

2、GeoJSON编码格式;

 

3、点的图层如何实现;

 

4、OD弧线图层如何实现;

 

5、热力图层如何实现;

 

数据可视化图层

 

底图 vs 数据可视化图层

 

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

地理信息地图

 

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

图层示意

 

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

 

数据可视化图层分类

 

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

点类型数据图层


线类型数据图层


面类型数据图层


热力类型图层


其他数据模型图层

样式和数据定义

 

数据格式

 

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

 

1、点:Point、MultiPoint

 

2、线:LineString、MultiLineString

 

3、面:Polygon、MultiPolygon

GeoJSON特征

 

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

 

更多规则可以参考GeoJSON规范文档: geojson.org/geojson-spe…

 

样式格式

 

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

样式结构

 

样式合并策略

 

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

样式更新逻辑

 

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

 

点图层

 

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

 

尺寸单位

 

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

 

渲染朝向

 

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

 

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

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


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


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


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

实现方案

 

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

点面计算逻辑

 

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

 

// 朝向屏幕 + 像素单位
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中,绘制一根有粗度的线,是需要计算线的面网格的。可以通过相邻的两个点,计算法线方向,沿着法线方向,做宽度的拉伸,从而实现一根“面线”:

面线计算原理

 

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

 

弧线计算

 

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

二分法插值

 

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

异常的大圆弧线

 

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

贝塞尔控制点计算

 

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

3D圆弧插值计算示意

 

应用场景

 

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

中央委员教育迁徙OD图

 

热力图层

 

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

 

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

密度图

 

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

热力网格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啦。

热度渐变纹理

热力图

 

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

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注