地图渲染

什么是地图?

通俗的说地图就是地球的画像。

什么是地图渲染?

地图渲染给地图的画像的过程,就是按照一定的比例关系、投影方法,使用一定的符号,把现地球上的山川、道路、建筑等信息在纸或屏幕上描绘出来。

互联网地图的表达方式

投影

球面到平面。

Web墨卡托投影

假设地图中心有一个灯,用一张纸沿着地球赤道包裹起地球,这样地球上每个点都会映射到纸上,这样就得到摩卡拖坐标系。

X轴:由于赤道半径为6378137米,则赤道周长为2*PI*r = 2*20037508.3427892,因此X轴的取值范围:[-20037508.3427892,20037508.3427892]

Y轴:由墨卡托投影的公式可知,同时上图也有示意,当纬度接近两极,即90°时,y值趋向于无穷。为了便于计算,“懒惰的工程师”就把Y轴的取值范围也限定在[-20037508.3427892,20037508.3427892]之间,搞个正方形。

正方形地图

切片与分层

为了避免一次加载全部地图数据,实现按需加载地图,这里需要对地图进行分层和切片。

分层切片

地图渲染的过程

地图渲染总体结构

  1. 获取数据
  2. 数据预处理
  3. 配置样式
  4. 渲染

获取数据

数据包括什么

地图使用点、线、面描绘现实世界,目前地图数据主要包括

  • 背景数据
    • 水系、绿地、铁路、行政区划等
  • 道路数据
    • 路网、限行、红绿灯、监控摄像头等
  • POI(兴趣点)数据
  • 门址数据
  • 建筑物数据
  • 行人导航数据
  • 公共交通数据

如何获取数据

  • 购买商业数据
    • 基础数据:高德,四维图新,TomTom
    • 实时路况:世纪高通,高德,北大千方
    • 卫星图:四维、DigitalGlobe
    • 室内地图:图聚,高德,蜂鸟,indoorAtlas
  • 自己采集数据
  • 使用开源数据 OSM
  • 抓取数据
  • 数据挖掘

数据预处理

  • 确定各种数据的在那些级别显示
  • 合并由于跨图幅而被切割掉的几何对象
  • 生成线和面的标注
  • 数据抽稀,在不影响显示效果的前提下减少数据中点的个数
  • POI Rank
  • POI名称简称
  • 标注数据的避让预处理,过滤掉大量无法显示的标注
  • 处理立交效果
  • 多来源数据的融合与去重

数据抽稀 道格拉斯-普克算法

Douglas-Peucker_animated

  1. 在曲线首尾两点间虚连一条直线,求出其余各点到该直线的距离。
  2. 选其最大者与阈值相比较,若大于阈值,则离该直线距离最大的点保留,否则将直线两端点间各点全部舍去。
  3. 依据所保留的点,将已知曲线分成两部分处理,重复第1、2步操作,迭代操作,即仍选距离最大者与阈值比较,依次取舍,直到无点可舍去,最后得到满足给定精度限差的曲线点坐标。

POI Rank

  1. 根据类型配置,通常是一些经验值,比如火车站就比餐馆更重要。
  2. 根据父子关系计算。
  3. 根据用户的搜索量。

几何对象相识度

面相似度 = A交B面积 / A并B面积

点和线也可以通过生成缓冲区转化成面来计算

配置样式

样式是来定义地图包含那些图层,这些图层使用哪些数据,在哪些层显示,怎么显示的。一个简单的样式示例如下

<Map background-color="blue" srs="+init=epsg:4326">
  <Style name="My Style">
    <Rule>
      <PolygonSymbolizer fill="#f2eff9" />
      <LineSymbolizer stroke="rgb(50%,50%,50%)" stroke-width="0.1" />
    </Rule>
  </Style>
  <Layer name="world" srs="+init=epsg:4326">
    <StyleName>My Style</StyleName>
    <Datasource>
      <Parameter name="file">path/to/shapefile.shp</Parameter>
      <Parameter name="type">shape</Parameter>
    </Datasource>
  </Layer>
</Map>

渲染

渲染栅格瓦片

渲染的过程

  1. 逐个层次,逐个瓦片进行渲染(1到5步)。
  2. 从样式中逐个获取图层。
  3. 读取图层在当前瓦片范围的数据,并进过平移缩放转化成瓦片上的坐标。
  4. 准备画布画笔画刷等,逐条绘制几何数据。
  5. 最终生成渲染好的栅格瓦片地图。
  6. 并发布成地图服务。

最终提供瓦片地图服务(Tile Map Service)。前端根据当前的位置,计算需要哪些瓦片的数据,下载这些瓦片进行拼接。

并行渲染

由于瓦片的数量非常的多,如果切18级一共包括14亿多个小瓦片。这是非常巨大的运算量。由于每个渲染瓦片都是相对独立的,因此可以并行计算。同时为了减少计算的次数,可以一次渲染多个(16*16)瓦片。

并行渲染

我们当时使用Hadoop来进行并行渲染,也可以使用RabbitMQ这样的消息队列实现。Map对原始数据进行切片,Reduce进行渲染栅格瓦片。使用2000个节点、全国切图用时在5个小时以内,生成的瓦片在180G样子。

遇到的问题
  • 难以得到最优化标注
  • 卡在切割一个特别大面的单节点上。
  • 瓦片边缘出现半个字。
  • 线的在瓦片边缘接头处显示不全。

解决办法

  • 当前使用的是贪心算法,先标注最重要的,先占先得,优点是速度快。尝试使用效果更优的Three Rules Suffice for Good Label Placement方法。
  • 采用多次MapReduce逐对原始数据进行切片。
  • 只允许标注在瓦片的内部。
  • 把实际渲染的范围扩大一圈。

演进到矢量瓦片

由于栅格瓦片使用的数据流量大,不支持地图的旋转、3维效果,难以支持多种地图样式,加上前端的渲染性能已逐步提高,目前绝大多数的App已经是前端渲染。

与后端渲染不同的是,前端渲染在切图的过程中并不会生成栅格瓦片,而是生成矢量瓦片。然后由前端根据样式文件通过OpenGL ES或WebGL完成渲染。

矢量瓦片格式

矢量瓦片文件采用Google Protocol Buffers进行编码。它包括若干图层,每个图层包含若干要素。要素是由几何图形和属性信息组成的。

几何图形编码

使用瓦片坐标系,把浮点型坐标转化成整形坐标。瓦片的左上角是坐标系的原点。X轴向右为正,Y轴向下为正。

一个简单的矢量瓦片示例

layers {
  version: 2
  name: "points"
  features: {
    id: 1
    tags: 0
    tags: 0
    type: Point
    geometry: 9
    geometry: 2410
    geometry: 3080
  }
  keys: "hello"
  values: {
    string_value: "world"
  }
  extent: 4096
}
OpenGL渲染

opengl_pipeline

难点

  • 只支持渲染三角形,多边形需要自己进行三角化。
  • 而对与线需要自己实现抗锯齿。
  • 同时显示对于文字需要自己使用纹理贴图实现。

数据更新

由于一次渲染耗时耗时还是相对较长的。在对于新高速通车、新地铁通车等实时性更新要求比较高的情况下,可以增量更新部分区域的瓦片。如果数据有定期的增量更新包(Diff数据),也可以实现实时更新的地图。

增量更新

OSM数据处理的瑞士军刀

Osmosis 是一个用Java开发的命令行工具,用来处理 OpenStreetMap 的地图数据。

pbf转成xml

osmosis --read-pbf beijing.pbf --write-xml beijing.osm

只导出餐馆

osmosis --read-xml input.osm --tf accept-nodes amenity=restaurant --write-xml output-nodes.osm

只导出道路

osmosis --read-xml city.osm --tf accept-ways highway=* --used-node --write-xml highways.osm

只导某个范围的数据

osmosis --read-xml file=beijing.osm --bounding-box top=40.6306 left=115.5322 bottom=39.4871 right=117.4164 --write-xml file=beijing_out.osm

生成osmchange文件

osmosis --read-xml file="planet1.osm" --read-xml file="planet2.osm" --derive-change --write-xml-change file="planetdiff-1-2.osc"

osmosis --read-xml file="planet1.osm" --read-apidb host="x" database="x" user="x" password="x" --derive-change --write-xml-change file="planetdiff-1-2.osc"

osmosis --read-apidb-change host="x" database="x" user="x" password="x" validateSchemaVersion="no" intervalBegin="2016-09-27_00:00:00" --write-xml-change file="planetdiff-1-2.osc"

使用osmchange更新

osmosis --read-xml-change file="planetdiff-1-2.osc" --read-xml file="planet1.osm" --apply-change --write-xml file="planet2.osm"

清空数据库

osmosis --truncate-apidb host="x" database="x" user="x" password="x" validateSchemaVersion="no"

导入数据库

osmosis --read-pbf file=beijing.pbf --write-apidb host="x" database="x" user="x" password="x" validateSchemaVersion="no"

数据库导出

osmosis --read-apidb host="x" database="x" user="x" password="x" --write-pbf file=beijing.pbf

生成replicate数据

mkdir replicate
cd replicate
osmosis --replicate-apidb host="x" database="x" user="x" password="x" validateSchemaVersion="no" iterations=0 minInterval=60000 --write-replication

iterations是执行次数,0是无限循环,minInterval是间隔时间,单位是毫秒。

然后把replicate目录发布成http服务,这样就实现了http://planet.openstreetmap.org/replication/minute/

使用replicate数据更新

初始化更新配置

mkdir WORKDIR
cd WORKDIR
osmosis --read-replication-interval-init

这里会生成configuration.txt文件,修改其中的BaseURL

baseUrl=http://download.geofabrik.de/asia/china-updates/

下载当天的state.txt

wget http://download.geofabrik.de/asia/china-updates/state.txt

以后每天执行更新

osmosis --read-replication-interval --simplify-change --write-xml-change changes.osc.gz
osm2pgsql --append --slim -C 100 --number-processes 1 changes.osc.gz

详细说明见http://wiki.openstreetmap.org/wiki/Osmosis#Example_usage

地图渲染的神器mapnik

mapnik真是做地图渲染的神器,搭配mod_tile便能轻松的发布地图服务。

这是使用高德地图的原始数据,通过TileMill简单配置得到效果。

mapnik

mapnik

mapnik

mapnik

mapnik

mapnik

mapnik

mapnik

mapnik

mapnik

在ec2上使用instavpn搭建vpn服务

今天发现一个非常易用的vpn软件instavpn。使用非常简单,用过的都说好。

the most user-friendly L2TP/IPsec VPN server

instavpn

通过ssh连接上的我的ec2主机。

安装instavpn

curl -sS https://sockeye.cc/instavpn.sh | sudo bash

然后没有然后了,这已经安装完成了。

现在可以通过http://IP-ADDRESS:8080访问web的管理界面。这里可以监控流量使用情况、配置用户。

由于ec2有Security Groups的权限控制。在连接之前,需要把L2TP和web管理工具使用的相关端口打开,分别是UDP 500 1701 4500和TCP 8080。

现在就可以连接我们的VPN了。

vpn

vpn

在ubuntu 12.04 LTS上搭建OpenStreetMap切片服务

为了简化配置,直接添加别人发布的软件仓库,里面包含发布地图服务需要的一切软件。

sudo add-apt-repository ppa:kakrueger/openstreetmap

更新apt

sudo apt-get update

安装libapache2-mod-tile

sudo apt-get install libapache2-mod-tile

这里会进行配置并创建数据库,需要注意的是,导入数据是需要使用这个用户操作。如果没有配置不对,可以通过sudo dpkg-reconfigure openstreetmap-postgis-db-setup重新配置。我这里加上了当前用户,如www-data,ubuntu。

下载osm数据,全球数据可以到http://planet.openstreetmap.org下载,同时http://download.geofabrik.de为我们提供了不同国家的小数据。我只下载了中国的

wget http://download.geofabrik.de/asia/china-latest.osm.pbf

导入数据库,如果数据大,这一步会非常耗时。调整osm2pgsql参数可加快速度。 这里使用的EC2主机,分配给100M内存和1个处理器,大概用十几分钟。由于EC2性能太低,不得已加上--cache-strategy sparse参数。

osm2pgsql --slim -C 100 --cache-strategy sparse --number-processes 1 china-latest.osm.pbf

设置数据导入时间

sudo touch /var/lib/mod_tile/planet-import-complete

重启渲染服务

sudo /etc/init.d/renderd restart

测试

http://localhost/osm/slippymap.html

http://localhost/osm/0/0/0.png

slippymap

PS: 如果地图显示不出来,查看渲染的日志

tail -f /var/log/syslog |grep renderd

数据更新

  • 自动实时更新

mod-tile中提供了增量数据更新工具openstreetmap-tiles-update-expire。试用出现不少问题,并没能成功。

  • 增量更新

下载增量数据,http://planet.openstreetmap.org/replication/可以找到每天、没小时和每分钟的增量更新包。部分区域每天更新的更新包可以从http://download.geofabrik.de/asia/china-updates/下载。

安装osmosis

初始化更新配置

mkdir WORKDIR
cd WORKDIR
osmosis --read-replication-interval-init

这里会生成configuration.txt文件,修改其中的BaseURL

baseUrl=http://download.geofabrik.de/asia/china-updates/

下载当天的state.txt

wget http://download.geofabrik.de/asia/china-updates/state.txt

以后每天执行更新

osmosis --read-replication-interval --simplify-change --write-xml-change changes.osc.gz
osm2pgsql --append --slim -C 100 --cache-strategy sparse --number-processes 1 changes.osc.gz
sudo touch /var/lib/mod_tile/planet-import-complete
  • 完全更新

重新下载osm数据并导入数据。

设置数据导入时间

sudo touch /var/lib/mod_tile/planet-import-complete

PS:如果地图没有更新,可调用render_old重新渲染旧切片。同时把浏览器的缓存清除掉。

参考

http://switch2osm.org/serving-tiles/building-a-tile-server-from-packages/

http://blog.csdn.net/goldenhawking/article/details/7952303