/ GIS

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)。
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 下暂时还没有对应的方法。