Mapbox公司于2010年6月01日在美国成立。http://Mapbox.com 是一个很棒的地图制作及分享网站,用户可以使用Mapbox Studio创建一个自定义、交互式的地图,然后可以将这些自定义的地图和数据服务你自己的网站(Web)或移动应用程序(Mobile Web/Android/IOS)上。
在技术和体验上,这家公司对传统的地图GIS系统的冲击简直都是颠覆性的。值得一提的是它为开源社区贡献了mapbox-gl-native /mapbox-gl-js / node-sqlite3等许许多多开源项目,因此有必要对它的一些核心开源项目和矢量瓦片技术进行研究。
由2010年成立至今,Mapbox在业界的影响力可也说是蒸蒸日上了,笔者以为,为原本沉闷、技术陈旧的GIS界,注入了强大的活力,为移动互联网时代带来了一个崭新的地图交互方式。下面,先列出一些在Mapbox发展历程中经历的一些比较重要的大事件:
笔者对Mapbox的一些开源项目、原理进行研究,下面将一一介绍这些笔者看过的项目并给出一些原理解读。
MBTiles瓦片存储规范
MBTiles瓦片存储规范的制定主要是为了解决、优化传统瓦片的存储方案存在的两个问题:
参见文档,Mbtiles其实本质是一个SQLite3文件,大家知道,SQLite有它天然的可移植特性(整个数据库就是一个sqlite3文件,当然可移植性够好)。这个解决了1的问题。
下面简单解读一下规范,该规范描述了这个sqlite3文件的表必须符合以下规定:
它可能会有一个索引:
CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);
这个表主要存了x/y/z和对应的瓦片数据(BLOB),标准还提到了TMS tiling scheme,有 兴趣可以详细看下。
这边给出一个MBTiles文件的实例:
其中,map表存的是zoom_level/tile_column/tile_row/tile_id,其中tile_id是一个哈希值,
images表存的是tile_data/tile_id,其中tile_data是一个BLOB,
tiles表是基于map表和images表的一个视图
对于不同zoom_level/tile_column/tile_row三元组,在map表中对应的tile_id有可能是相同的,这样最终通过tiles视图在images表中对应的tile_data就是同一个,这样做于可以减少冗余瓦片。地图中像海洋或空旷的土地等区域包含有成千上万重复而冗余的纯色瓦片,例如太平洋中蔚蓝色的瓦片。在小比例尺中,它可能只有几张,但在大比例尺如1:10000的地图中,就会存在上百万这种单一颜色的蓝色瓦片。MBTiles 通过拆分瓦片索引和瓦片原始图像的存储,使用视图的方式来关联二者,这样成千上万的瓦片索引就可以指向同一个瓦片图像,从而大大减少纯色瓦片的冗余存储,提升磁盘利用率以及瓦片检索效率。
TileMill这个项目到现在已经没在维护了(但是还是可以下载使用),但是在当时这个项目引入了CartoCSS,利用Mapnik渲染瓦片图片,还是有一定的先进性的。看了下是基于Node 0.10.x开发的,看版本号就知道有点久远了,直接下载release版本,结果软件一直在loading状态所以放弃了,后来发现了这个fork,下载安装,其中有个mime包依赖版本有break change,手动装会1.x版本才可以成功跑起来。
下图是TileMill自带的一个华盛顿特区的地图的示例,左侧是地图区域,右侧是CartoCSS的样式编辑区域
当然右侧编辑区域用符合CartoCSS规范的的样式描述语言去描述“图层(layer)”的样式(当然可以控制在图层下面更加细节的feature)的时候,左侧的地图样式也会随之改变。 CartoCSS的用法在这里不详细展开了,在需要用到时可以自行查看,总之是非常类似CSS的一门标记语法。
不难发现,左侧的地图区域采用的是“栅格瓦片”,即由Mapnik将Mapnik XML描述文件渲染成的图片。它的工作原理入下图所示:
当然,在制作完地图的样式之后,TileMill支持将地图进行导出为MBTiles格式,用于离线地图。由上图可以看出,这里利用Mapnik的强大渲染能力,对数据、样式的分离有了一种很好的实践,同样的思想为日后的MVT矢量瓦片奠定了基础。不过这种由客户端改变样式描述文件,让服务器去触发渲染的模式,还是有一定的局限性,毕竟增加了前后端通信的开销,另外对于更加复杂的地图而言,Mapnik XML文件往往会变得非常巨大,Mapnik纵使渲染能力强大,也会有其性能的瓶颈,所以这种模式只适用于本地环境制作地图,对标现在的Mapbox Studio来看,是远远不够的。
在TileMill的代码中,其实就用到了Tilelive。笔者认为,Tilelive在瓦片的调度里面是一个非常核心的库,虽然代码量不大,但是它的设计和Plugin能力实在是有点“小而美”,有兴趣可以看看源码。
Tilelive is designed for streaming map tiles from sources (like custom geographic data formats) to sinks (destinations, like file systems) by providing a consistent API. This repository enables the interaction between sources and sinks and is meant to be used in tandem with at least one Tilelive plugin. Tilelive plugins (modules) follow a consistent architecture (defined in API.md) and implement the logic for generating and reading map tiles from a source or putting map tiles to a destination, or both.
如上图,Tilelive其实在整个Mapbox瓦片体系里面占着比较重要的地位。看Tilelive的实现和用法,有点像设计模式中的“适配器模式”。只要按照Tilelive的设计,实现Tilelive提供的标准函数,可以利用Tilelive的一些方法,实现各个数据源的数据互导,例如:想将别人家在MBTiles里面存的瓦片数据导入到MongoDB里面去,就可以使用Tilelive和MBTiles/MongoDB的相关“plugin”办到,甚至你也可以自己写一个Plugin。当然,利用Tilelive配合其他Node Web库(例如egg等)很容易在本地搭建出一个离线的瓦片服务器。有关Tilelive Pugin的示意图如下:
前文也提到过,矢量瓦片标准是Mapbox的一个对业界非常大的贡献,到目前为止,几乎所有主流的Web端地图渲染库,例如ol3.js/Cesium.js/Leaflet等都已经支持MVT标准,即可以使用它们在Web端渲染出矢量瓦片数据。矢量瓦片标准规定了一种节省存储空间的矢量瓦片数据编码格式。这种格式应用于客户端或服务端高效渲染或查询要素信息。
标准中规定了矢量瓦片的:
矢量数据切片为瓦片后,其坐标从地理坐标转换为屏幕坐标,以整数形式存储。整型比浮点型所需的存储空间更小,大大降低了瓦片的传输成本
实例:一个GeoJSON的格式要素如下:
{ "type": "FeatureCollection", "features": [ { "geometry": { "type": "Point", "coordinates": [ -8247861.1000836585, 4970241.327215323 ] }, "type": "Feature", "properties": { "hello": "world", "h": "world", "count": 1.23 } }, { "geometry": { "type": "Point", "coordinates": [ -8247861.1000836585, 4970241.327215323 ] }, "type": "Feature", "properties": { "hello": "again", "count": 2 } } ] }
会被结构化为:
layers { version: 2 name: "points" features: { id: 1 tags: 0 tags: 0 tags: 1 tags: 0 tags: 2 tags: 1 type: Point geometry: 9 geometry: 2410 geometry: 3080 } features { id: 1 tags: 0 tags: 2 tags: 2 tags: 3 type: Point geometry: 9 geometry: 2410 geometry: 3080 } keys: "hello" keys: "h" keys: "count" values: { string_value: "world" } values: { double_value: 1.23 } values: { string_value: "again" } values: { int_value: 2 } extent: 4096 }
有了矢量瓦片标准,对于在Web里面来说,利用WebGL的moveTo/lineTo之类的API,可以将矢量瓦片绘制出来。
Mapbox-GL.js是一个在Web浏览器里面解析矢量瓦片规范并且封装了WebGL,可以在Canvas上对矢量瓦片进行绘制的地图库,其中也用到了一些硬件加速的东西(着色器)等。官方提供的示例也非常多非常棒,参考这些示例去开发一些基于地理位置的应用还是不错的。
Mapbox Studio Classic
Mapbox Studio Classic是Tilemill的升级版,目前也已经不维护了。
与Tilemill的界面类似,也是左侧是地图,右侧是CartoCSS的样式编辑区域,它们的主要区别在于Mapbox Studio Classic使用了矢量瓦片进行地图样式的制作。同时,由于是矢量瓦片,字体之类的东西就没法直接在矢量瓦片里面存储,所以多了字体的功能,支持自己上传字体等功能。它的原理基本如下图:
和Tilemill相比,当改变样式文件的时候,不用再到服务器去使用Mapnik重新把数据渲染成栅格瓦片再返回前端了,现在有Mapbox-gl.js可以在前端直接利用矢量瓦片和样式文件渲染出地图来。
Mapbox Studio是一个在线制图、分享平台,“平台”意思是,跟之前的Tilemill/Mapbox Stduio Classic的本地制图的模式有很大的不同。可以说,矢量瓦片的出现,给了“在线制图平台”诞生的可能性。用户在Mapbox网站上制作的地图(本身在客户端渲染瓦片的模式对服务端的开销很低了)后,可以将瓦片、样式、字体、Mark等数据直接托管在Mapbox平台上,然后用户自己的APP、网站利用自己的Accesskey 可以访问自己的制作好的瓦片数据跟样式。以下是笔者在线改变水系的颜色的制图。
这个平台肯定是不开源的,没法从源码探究它的实现,但是矢量瓦片这块的基本原理跟Mapbox Studio Classic肯定是一致的。
利用Mapbox提供的开源技术,我们完全可以在本地可以实现一个自己的简易版的Mapbox Studio。
高德最近推出的自定义地图:https://lbs.amap.com/dev/mapstyle/index
看起来也是前端做的矢量瓦片渲染,但是如果是这样也肯定是自己的矢量瓦片标准吧。
原文链接: https://zhuanlan.zhihu.com/p/45518647