来源  https://zhuanlan.zhihu.com/p/45518647

前言

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发展历程中经历的一些比较重要的大事件:

  • 2011.01 起草MBTiles标准,这是一个新的跨平台、支持离线的地图存储格式。
  • 2011.02-2011.06 TileMill发布,允许用户根据自定义数据来创建地图,开发了CartoCSS样式描述语言(类CSS),TileMill桌面软件是使用Mapnik和Node.js开发的。
  • 2013.05 矢量切片出现,推出了一个新的开源矢量瓦片规范,应用到Mapbox所有的Web地图中。矢量切片提供了一种超快速、高效的格式,强化了地图在交互特性、GeoJSON数据流、移动端渲染等等方面的性能。现在,设计迭代一个地图可以在几秒钟内完成,在几分钟内就可以得到一个完整的全球矢量地图。这个Mapbox Vector Tile(Mapbox 矢量瓦片)几乎是革命性的。
  • 2014.06 Mapbox GL诞生,它是一个新的可交互的,响应式地图的渲染库。Mapbox GL是一个基于OpenGL ES的,非常强大的,硬件加速的制图库,它可以完全控制每个地图样式的元素。

  • 2014.08 推出了Mapbox GL JS,它是新的一个快速而强大的Web地图系统。Mapbox GL JS是一个客户端的地图渲染器,使用JavaScript和WebGL的动态绘制数据,呈现视频游戏级别的速度和平滑。
  • 2014.09 Mapbox Studio Classic 。是开源的地图设计平台,在所有桌面平台上推出:OS X操作系统,Windows和Linux。Mapbox Studio Classic允许任何人设计完全定制的地图,很轻松地处理巨量的全球数据,在几秒钟内发布更新地图,并且它所设计的地图是与分辨率无关的,适应所有从Retina设备到高分辨率的打印。

  • 2014.12 Turf.js.Turf是,它是一个快速、紧凑且开源的JavaScript库,实现了最常见的地理空间操作功能。Turf作为新的地理空间基础设施的一部分,不同于JavaScript的ArcGIS API,由于Turf可以完全在客户端完成所有操作,Web应用程序可以脱机工作。
  • 2015.11 Mapbox Studio 发布了Mapbox Studio,它是一个用于优化移动应用和现代网页的制图系统。Mapbox Studio包含矢量渲染来制绘地图,更新和交互的速度与平滑程度和视频游戏一样。拥有的强大的数据基础架构,支持自定义数据,格式转换和处理快速而可靠。
  • 2016.08 上海办公室成立
  • 2016.10 发布Mapbox Unity SDK 将Mapbox API的全部功能带到基于位置的游戏、VR。

  • 2017.12 Mapbox GL JS中的3D功能

Mapbox开源项目介绍

笔者对Mapbox的一些开源项目、原理进行研究,下面将一一介绍这些笔者看过的项目并给出一些原理解读。

MBTiles瓦片存储规范

MBTiles瓦片存储规范的制定主要是为了解决、优化传统瓦片的存储方案存在的两个问题:

  1. 可移植性差,无法在移动端上做离线应用
  2. 存储量大,大家都知道,因为互联网上的地图都以“瓦片”的形式存在,高层级的瓦片存储数量往往是海量的。例如,对于“Web 墨卡托”投影的瓦片金字塔来说,第15层数据有 4^15 = 1073741824个瓦片。

参见文档,Mbtiles其实本质是一个SQLite3文件,大家知道,SQLite有它天然的可移植特性(整个数据库就是一个sqlite3文件,当然可移植性够好)。这个解决了1的问题。

下面简单解读一下规范,该规范描述了这个sqlite3文件的表必须符合以下规定:

  • 必须要一个名叫“metadata”的table(表)或者view(视图),这个表其实就是“元数据”表,用来描述存储的数据。这个表必须要有两列,一列是"name",一列是“value”,这两列都是text类型的。这个表必须包含一些特定的row,例如name="name",value="数据集名称";name: "format" ,value: "pbf"代表存储的瓦片格式;name: "center" ,value: -122.1906,37.7599,1代表这个数据集存储的数据中心在这个经纬度处。对于Mapbox矢量瓦片集,有特殊的json字段,用来描述矢量瓦片集。
  • 必须要有一个名字叫“tiles”的表。建表语句

    CREATE TABLE tiles (zoom_level integer, tile_column integer, tile_row integer, tile_data blob);
     

它可能会有一个索引:

 CREATE UNIQUE INDEX tile_index on tiles (zoom_level, tile_column, tile_row);
 

这个表主要存了x/y/z和对应的瓦片数据(BLOB),标准还提到了TMS tiling scheme,有 兴趣可以详细看下。

  • grids和grids_data表 可有可无,不细说了。

这边给出一个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

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来看,是远远不够的。

Tilelive

在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矢量瓦片(MVT)标准

前文也提到过,矢量瓦片标准是Mapbox的一个对业界非常大的贡献,到目前为止,几乎所有主流的Web端地图渲染库,例如ol3.js/Cesium.js/Leaflet等都已经支持MVT标准,即可以使用它们在Web端渲染出矢量瓦片数据。
矢量瓦片标准
规定了一种节省存储空间的矢量瓦片数据编码格式。这种格式应用于客户端或服务端高效渲染或查询要素信息。

标准中规定了矢量瓦片的:

  • 文件格式 Google Protocol Buffers 、文件后缀、MIME类型。
  • 投影和范围 Web Mercator是默认的投影方式,Google tile scheme是默认的瓦片编号方式。
  • 内部结构
    • 图层
    • 要素
    • 几何图形编码
    • 要素属性

矢量数据切片为瓦片后,其坐标从地理坐标转换为屏幕坐标,以整数形式存储。整型比浮点型所需的存储空间更小,大大降低了瓦片的传输成本

实例:一个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

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

Mapbox Studio是一个在线制图、分享平台,“平台”意思是,跟之前的Tilemill/Mapbox Stduio Classic的本地制图的模式有很大的不同。可以说,矢量瓦片的出现,给了“在线制图平台”诞生的可能性。用户在Mapbox网站上制作的地图(本身在客户端渲染瓦片的模式对服务端的开销很低了)后,可以将瓦片、样式、字体、Mark等数据直接托管在Mapbox平台上,然后用户自己的APP、网站利用自己的Accesskey 可以访问自己的制作好的瓦片数据跟样式。以下是笔者在线改变水系的颜色的制图。



这个平台肯定是不开源的,没法从源码探究它的实现,但是矢量瓦片这块的基本原理跟Mapbox Studio Classic肯定是一致的。

利用Mapbox提供的开源技术,我们完全可以在本地可以实现一个自己的简易版的Mapbox Studio。

其他

高德最近推出的自定义地图:https://lbs.amap.com/dev/mapstyle/index


看起来也是前端做的矢量瓦片渲染,但是如果是这样也肯定是自己的矢量瓦片标准吧。

=========== End

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐