mapboxgl - 用webgl展示海量数据,是否可行?


下面的代码可以在我的Github中找到:https://github.com/QingyaFan/data-visualization

先说结论:不可行。具体原因如下。

以后的文章我们将不仅仅局限于Openlayers,适合业务的技术才是好的技术,有的功能其他的库实现的比Openlayers好,我们也会聊,比如这次的mapboxgl。这个也源于Openlayers对于WebGL支持的缓慢进度:Openlayers憋了好久,终于憋出了WebGL的渲染器,然而只支持点要素类型,要是线和面类型的要素,数据量级一上去,就没法用了。我们看看隔壁的Mapboxgl,诞生之日起就是WebGL渲染,而且openlayers新出的WebGLPoint Layer使用的WebGL渲染样式规则也借鉴了Mapboxgl的表达式(Expression)。所以,为什么不用mapboxgl呢?当我想到这个想法的时候,真香定理仿佛在向我招手。于是我掏出压箱底的321万的线要素数据 ⚡️ 来一场硬核的测试。

线要素321万

1、初始化一幅地图

Mapbox的一些理念和Openlayers不太一样,在使用之前,我们要看有哪些异同。

Mapbox中数据源(Source)不从属于图层(Layer),而是直接由Map对象管理
Layer可以选择要渲染的Source,所以我们很自然想到,是否可以多个Layer使用一个Source呢?当然可以!Openlayers中也可以,只不过我们之前没那么做过
Mapbox中样式是Paint表示,Openlayers中是Style
在用321万的数据之前,我们先来一波常规操作,用19万的全球城市点数据熟悉一下mapbox的流程:

let url = 'http://localhost:8000/world-cities.geojson';
this.map.addSource('cities-population', { type: 'geojson', data: url });
this.map.addLayer({
    'id': 'cities-population',
    'type': 'circle',
    'source': 'cities-population',
    'paint': {
        'circle-radius': [
            "interpolate",
            ["linear"],
            ["get", "population"],
            40000, 2,
            2000000, 12
        ],
        'circle-color': [
            "interpolate",
            ["linear"],
            ["get", "population"],
            40000, 'DarkSlateGrey',
            2000000, 'DarkSlateGray'
        ],
        'circle-opacity': 0.6
    }
    
});
this.map.addLayer({
    'id': 'cities-population-heatmap',
    'type': 'heatmap',
    'source': 'cities-population',
    'paint': {
        'heatmap-radius': [
            "interpolate",
            ["linear"],
            ["get", "population"],
            40000, 2,
            2000000, 12
        ],
        'heatmap-intensity': 0.5
    }
    
});

我们用世界城市人口数据渲染了两个图层,一个普通的根据人口数量决定点半径大小的图层,另一个是根据人口数量决定热度的热度图。效果还不错,在图上,我们可以明显看出印度的人口密度是真的大,日本也毫不逊色,西欧也想勇争第一,再看咱中国,胡焕庸线非常明显,人口密度大的城市都集中在了东部。所以这个热力图还是很有用的,不是只图个好看。

2、硬核的来了

好了,是时候祭出321万的道路数据了,我先描述一下这个数据,避免后面的结果对我们打击太大:

路

写个代码,满怀期待 :

let url = 'http://localhost:8000/roads.geojson';
this.map.addSource('roads', { type: 'geojson', data: url });
this.map.addLayer({
    'id': 'roads',
    'type': 'line',
    'source': 'roads',
    'paint': {
        'line-color': 'DarkViolet',
        'line-opacity': 0.6
    }
    
});

然而,浏览器直接崩了 ,这是因为chrome每个Tab页占用的内存是有限制的,64位的Chrome单个Tab可以占用4G内存[1],而且我们要注意到,文件占用磁盘的大小和载入内存占用内存的大小是不一样的。

mapboxgl - 用webgl展示海量数据,是否可行?_第1张图片
很显然,就算WebGL再强,浏览器本身加载数据量有限制,占用机器的内存也不会无限大。读过《深入浅出Nodejs》的可能知道,Nodejs的Javascript运行时是V8,而V8本来就用在了Chrome上,V8的内存限制导致浏览器不能加载特别大的Javascript对象,所以整体加载并不能解决大数据量的渲染问题。解决这个问题也有一些思路,比如:

  1. 对数据化简,让数据没有那么大的体积。这个思路需要注意,数据在不同的缩放级别下应该是不一样的化简程度,高缩放级别下,可多化简,反之少化简。这其实就是GIS中常说的术语:LOD(Level Of Details)细节层次模型,不同缩放级别对展现的细节程度要求是不一样的。比如,你离着一棵树500米,你可能只关注树的外形和全貌,5米时,你可能关注树有几个树杈,10cm时,你就会想看树叶的纹理;
  2. 借鉴切片的思想,对数据分块,不加载用不到的数据。这里需要注意的是不同的缩放级别切片的内容也不一样,对每个级别都需要重新切片。

两个思路各有优缺点,实际我们会把两个方案结合起来,就是现在最常用的切片方案,每个级别首先化简,然后切片。在 Openlayers图层详解 里我们在聊Openlayers的WebGLPointLayer时也提过服务器端渲染和浏览器端渲染各自的优劣势:

那么有的同学会问,我在服务器端渲染不比webgl性能高,它不香吗?香是香,但是服务器与客户端是1对多的关系,每个客户端都需要服务器渲染,并发量高了,服务器垮不垮?又有小伙伴说了,切片不就是解决这个问题的吗?对,但现在需求往往是样式随时会变,缓存了切片,样式一变,又要重新切,意义不大。矢量切片出现不就是这个问题的证据么?

所以,如果我们既能解决大数据量的问题,又能把浏览器端渲染的优势融合进来,不就是一个很理想的方案吗?Mapbox想在了我们前面,在2014年4月份就发布了他们的方案:矢量瓦片(Vector Tiles),感兴趣的童鞋们去看下他们的 initial commit。

3、我们先拉回来

聊了比较理想的方案,咱们现在还是测测WebGL的极限吧,虽然2G的GeoJSON不行,我们搞一个数据量稍微小一点的数据。这里跟童鞋们说一下:看数据量的大小,不能只看要素的数量,比如,一个100万的点要素和10万的线要素,哪个数据量大?稍微复杂一点的数据,一般一条线上会有几十甚至上百上千个点,所以要素类型也需要关注。

这里我们来一个复杂的线数据:

line

let url = 'http://localhost:8000/lines.geojson';
this.map.addSource('lines', { type: 'geojson', data: url });
this.map.addLayer({
    'id': 'lines',
    'type': 'line',
    'source': 'lines',
    'paint': {
        'line-color': 'DarkViolet',
        'line-opacity': 0.6,
        'line-width': .9
    },
    'layout': {
        'line-cap': 'round',
    }
    
});

看效果,效果还行(台湾省的数据没搞到,所以一片空白,各位同学见谅),

但是在看到这个效果之前,我等了19秒,听到了电脑呼呼叫,还看到了这样的景象:

cpu usage
面的效果怎么样呢?我试了一下19万的建筑面,发现渲染毫无压力,2秒内就可以看到效果。不过好像自带LOD,真是厉害。不过,面是建筑面,每个面大概4个左右的点,相对于上面的线来说,线的数据量要远大于面。不过这里测试面,我们渲染的是2.5维,有高度。


4、所以怎么渲染海量数据?

通过上面的测试我们也看到了,渲染比较大量的数据,在前端WebGL渲染,一有文件大小的限制,二有渲染性能的极限。在第二节我们也讨论了两个常见的解决措施,最终觉得矢量瓦片可能是我们的救星,那矢量瓦片效果咋样呀?最近在写一个Shapefile转换Vector Tile的工具,写成之日,就是321万线数据重现之时。

上面的代码可以在我的Github中找到:https://github.com/QingyaFan/data-visualization

参考

[1] https://js9.si.edu/js9/help/memory.html

你可能感兴趣的:(WebGIS)