Tilemill + PostGIS + Mapnik 获得自定义样式的 OSM 地图切片
注:OSM 默认数据集中的国家和地区并不正确,需要注意辨别改正。
最近的项目需要用OpenStreetMap数据生成一个自定义样式的切片底图,尝试了一段时间,发现可以用 Tilemill 读取数据并生成地图样式,再用 Mapnik 进行地图切片。切片制作使用 Windows 平台,用到的工具有:
- python 2.7
- Tilemill
- PostgreSQL 9.3 + PostGIS 2.1
- Mapnik 2.2
- generate_tiles.py 脚本(可以从 OSM Mapnik SVN下载)
下载 OSM 数据并导入到 PostGIS
从 bbbike 选择下载某个区域的 OSM 数据,或者直接从metro extracts下载整个国家的数据。
新建一个 PostGIS 数据库。之后使用 osm2pgsql 工具将下载的 OSM 数据导入到 PostGIS 中。
配置 OSM-bright 项目
现在数据库中已经有了导入的空间数据,为了将这些数据渲染成地图,还需要地图渲染引擎以及样式文件。因为使用了OSM作为源数据,著名的 Mapbox.com 已经为我们准备好了样式文件。
Mapbox 已经准备好了配置文件,将其中的 PostGIS 数据库换成我们自己的数据库,再生成一下项目文件即可。这里使用 OSM-bright 样式。
将项目文件下载到本地,复制一份 configure.py.sample 并重命名为 configure.py,在文本编辑器中打开,修改其中的数据库连接参数:
config["postgis"]["host"] = "localhost"
config["postgis"]["port"] = "5432"
config["postgis"]["dbname"] = "osm"
config["postgis"]["user"] = "postgres"
config["postgis"]["password"] = "********"
另外,Tilemill 需要使用两个shapefile,从下面的链接下载这两个shapefile,解压到 OSM-bright 项目下的 shp 文件夹:
$ wget http://data.openstreetmapdata.com/simplified-land-polygons-complete-3857.zip
$ wget http://data.openstreetmapdata.com/land-polygons-split-3857.zip
解压完成后,最好使用 shapeindex工具对shapefile重建索引。
准备工具工作完成后,运行 python make.py
,就可以得到使用本地数据的 OSM-bright 项目了。这个命令会在项目文件夹下生成一个 build 目录,同时,生成好的项目也会复制到 Tilemill 的默认工程目录中。
在 Tilemill 中打开刚刚生成的项目,等待几分钟,完成初始化之后就可以看到渲染出来的地图了。可以看到,Tilemill 的样式文件实际上是一系列类似于 css 的样式声明(carto css)。
Tilemill 使用 Mapnik 进行地图渲染。其实使用 Tilemill 也可以得到地图切片,但在数据量较大的情况下,Tilemill导出成 MBTILES的功能并不稳定。因此,可以使用 Tilemill 将当前项目的样式文件导出为 Mapnik XML,再通过命令行工具来进行地图切片的工作。
安装 Mapnik
Windows 下 Mapnik 的安装可以参考 Mapnik 的 GitHub wiki 页面。
修改 generate_tiles.py 脚本
Mapnik 只是一个地图渲染引擎,本身并没切片功能。要得到地图切片,需要使用 OSM SVN 中提供的脚本 generate_tiles.py。
generate_tiles.py 是针对 Linux 操作系统编写的,在 Windows 下使用需要先修改。
首先,Windows 下一般没有 HOME
环境变量,脚本第194行的 home = os.environ['HOME']
会导致未处理异常。这里 home
变量的含义是默认路径,手动给 home 赋一个路径即可,比如 home = 'c:\\'
。
同样,第198行指定地图样式文件与输出目录的代码也需要修改,脚本中默认使用 Linux 风格的路径,需要改成 Windows 风格。另外,也可以直接使用一个绝对路径,不一定要用 home
变量:
mapfile = 'd:\\osm-local.xml'
# ...
tiledir = 'd:\\osm\\tiles\\'
修改完成后执行 python generate_tiles.py
命令,会发现创建 tiledir
所指代的输出目录时,报出了 Windows Error,错误。原因是脚本中所使用的 os.mkdir()
函数不能创建多级目录。手动在 D: 盘下创建一个 osm 文件夹,可以解决这个问题。
另外,在使用 Tilemill 导出的 xml 样式文件时,可能会出现找不到字体的问题,需要手动修改样式文件。打开样式文件后可以看到第3行中的 font-directory
属性,默认值为 .\fonts
。可将这个参数设成系统的字体目录 c:\windows\fonts
,然后将所有需要用到的字体统统安装一遍。另外,如果使用了 Dejavu Sans
字体族,还需要将这些字体名称中的 Italic
改成 Oblique
。总之,要保证样式文件中使用的所有字体都可以在 font-directory
下找到。
最后,把脚本末尾自带的 rendertiles() 删除,写上我们自己的 rendertiles 语句:
bbox = (-180.0,-90.0, 180.0,90.0)
minZoom = 0
maxZoom = 6
render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom, "ChinaOSMBright")
修改完成后重新执行脚本,参数没问题的话,就可以看到输出的地图切片了:
完成之后可以将切片放到 Apache 的 htdocs 目录下,加载到 OpenLayers 3中查看:
// 加载地图切片图层
tilesLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [attribution],
maxZoom: 8,
url: 'http://localhost/data/china-osm-bright/{z}/{x}/{y}.png'
})
});
map.addLayer(tilesLayer);
小结
通过这种方式,可以避免自己动手编写样式文件,用OSM数据相对方便地得到设计好的底图切片,适合需要在局域网中提供切片底图的应用场景。
这种方法的缺点在于,如果生成的切片缩放等级较高,可能需要消耗大量的时间和存储空间,并用生成的绝大多数切片可能完全没有用处。在 Linux 操作系统下,可以使用 Apache 的 mod_tiles 插件来根据请求渲染地图切片,而不需要预先生成所有等级的切片。Windows 下暂时还没有对应的方法。