Mapnik概述

Mapnik是一个强大的使用地理空间数据创建地图的强大工具包 ,其核心是一个 C++ 的共享库提供空间数据访问和可视化的算法和模式,特别是包含一些地理对象,如地图、层、数据源、特征和地理几何等。它提供了一些功能,可以用来设计具有良好视觉效果的地图。Mapnik会产生地图,地图对象是Mapnik的API的核心。地图对象提供了生成图像输出格式的方法(通常是PNG或者是PDF)。

Mapnik的应用主要包括三个方面:它可以作为一个 C++ 代码的共享库;它可以用来编写 Python 脚本;还可以用来编写和处理 XML 配置文件。

Mapnik地图内部有很多图层,每一层都与数据源密切相关。每种典型的数据源(例如,Shapefile数据源、in-memory数据源、Raster数据源)都具有多种要素(如点、线、面等)。一个数据源可以指示一个shapefile文件、空间数据库、光栅图像文件或大量的其他空间数据源。在大多数情况下,建立一个数据源层是很容易的。

理解 Mapnik 的工作机制必须注意,使用Mapnik来渲染地图时顺序非常重要。它使用Painter算法来决定Z轴次序,即图层按一定顺序描绘,“顶”层在其他层之上,最后描绘。

在每一层中,地理空间数据可视化的显示是通过一种叫做symbolizer的东西控制的。虽然在Mapnik有许多不同类型的symbolizers可以利用,但是我们这里感兴趣的是三种symbolizers。

一种样式定义了图层中对象是怎么渲染的。一种样式包含了一种或多种规则,可以有选择性的限制其输出。过滤出数据源提供的对象的一个子集。例如只显示那些具有特殊属性的对象。过滤式可以选择的--对于简单的地图通常每层有一个规则,再没有其他的过滤器。每一个规则持有一个或者多个符号,这是用于在实际输出时绘制几何图形的。根据符号类和设置几何图型可以以很多种形式产生。

Mapnik制图快速开始

首先看一下Mapnik工作的主要流程,如:

<img src="xxfig_k3.png" width=400>

Mapnik工作流程

下面我们来看如何使用Mapnik来渲染一幅世界地图。 与前面介绍的各种工具的使用方法不同,介绍 Mapnik ,首先应给出一个完整的例子,然后再横向扩展介绍各种功能。

要开始本节的编程,需要一点基础数据。这个数据是Shapefile,名称为 world_borders.shp

安装 Mapnik

首先在终端中打开一个Python编译器。

然后导入并绑定python:

建立地图对象

首先要建立Mapnik对象。

In [1]:
import mapnik
m = mapnik.Map(400,200,"+proj=latlong +datum=WGS84")
m.background = mapnik.Color('steelblue')
mapnik.render_to_file(m,'xworld.png', 'png')

map.srs 为要生成地图的投影, m.srs 缺省为: +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs

创建一个给定宽度和高度的地图,设置背景的颜色为“steelblue”。

In [2]:
from IPython.display import Image
Image('xworld.png')
Out[2]:

创建样式

使用样式是用来决定数据是如何渲染的。

In [3]:
s = mapnik.Style()
r=mapnik.Rule()
polygon_symbolizer = mapnik.PolygonSymbolizer()
polygon_symbolizer.stroke = mapnik.Color('#0f0f0f')
r.symbols.append(polygon_symbolizer)
line_symbolizer = mapnik.LineSymbolizer()
# line_symbolizer.stroke = mapnik.Color('rgb(50%,50%,50%)')
r.symbols.append(line_symbolizer)
s.rules.append(r)
m.append_style('My Style',s)

上面 sr ,分别是地图对象的样式与规则, polygon_symbolizerline_symbolizer 分别定义了多边形与线状要素的样式; 然后,符号添加到规则中,规则添加到样式中,样式再加入到地图中。

创建数据源

在上面的第二步中你需要下载一个世界边界的 shapefile, 我们用 Python 把他转成一个 Mapnik 数据源。 如果 Python 编译器和下载的 shape 文件在同一个目录中, 那么你可以创建一个相对路径来创建数据源,不然就使用绝对路径。

In [4]:
ds = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')

创建图层

图层是在Mapnik中定义的,需要指定数据源与样式。在 Mapnik 中, 可以将图层视为核心,其他所有都是围绕图层开展的。

Mapnik层包含了周围的数据源,并存储有用的属性,现在让我们创建一个层,并把数据源加进去。 名称为\ world\ ,也可以是其他有效的名字。

In [5]:
layer = mapnik.Layer('world')

注: layer.srs 是数据源的源投影,需要和数据坐标的投影匹配, 否则地图可能是空白的。在这种情况下,默认的srs mapnik假设和数据的投影匹配。如果不是这种情况的话,需要把 layer.srs 设置成正确的值。 现在把数据集加到层里面。最后需要确定我们上面创建的样式也适用于层,根据其字符串参考,如下:

In [6]:
layer.datasource = ds
layer.styles.append('My Style')

地图渲染

地图渲染是关键的一步,添加的顺序很重要。 最后把图层加到地图里,然后显示图层的全部范围, 如果你不把图层全部显示,那么输出很可能是空白的。 可以使用\ zoom_all\ 函数,来计算 map\ 相关图层的最大范围。

In [7]:
m.layers.append(layer)
m.zoom_all()

最后渲染地图。把数据写成png格式然后保存到当前目录的 world.png

In [8]:
mapnik.render_to_file(m,'xworld2.png', 'png')
Image('xworld2.png')
Out[8]:

注意:如果你重新运行脚本的话,生成的地图将会被覆写。

现在你可以用文本编辑器打开脚本,改变维数,颜色或者数据源(记住当你改变数据源时要使用正确的srs)。

In [9]:
# -*- coding: utf-8 -*-

import mapnik
# from helper import get_tmp_file

m = mapnik.Map(600, 300)
m.background = mapnik.Color('steelblue')
s = mapnik.Style()
r = mapnik.Rule()
polygon_symbolizer = mapnik.PolygonSymbolizer()
polygon_symbolizer.fill = mapnik.Color('#f2eff9')
r.symbols.append(polygon_symbolizer)

line_symbolizer = mapnik.LineSymbolizer()
line_symbolizer.stroke = mapnik.Color('rgb(50%,50%,50%)')
line_symbolizer.stroke_width = 0.1

r.symbols.append(line_symbolizer)
s.rules.append(r)
m.append_style('My Style', s)
ds = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')
layer = mapnik.Layer('world')
layer.datasource = ds
layer.styles.append('My Style')
m.layers.append(layer)
m.zoom_all()
mapnik.render_to_file(m, 'xx_a.png', 'png')

# print("rendered image to 'world.png'")
In [10]:
from IPython.display import Image
Image('xx_a.png')
Out[10]:
In [ ]:
 

使用XML渲染地图

不同版本的 Mapnik 接口改变也比较多,从而导致 Python 绑定存在许多问题。目前在 Python Mapnik 的开发中, 很多的 issue 处于 open 状态,有的问题已经存在一年多了。使用 Python + XML 是目前比较现实的方法。

使用Mapnik还有一个值得研究的方法。 Mapnik不但以编程方式创建symbolizers、规则、样式和图层, Mapnik还允许你使用一个地图定义文件来存储所有的信息。 这是一个用于定义生成地图的不同的Mapnik对象的XML格式的文件。

还要说明一点,你可以选择使用地图定义软件,而不是使用Python代码来创建不同的Mapnik对象通过手动的方式。 这是一个XML格式的文件,它定义了所有地图内的symbolizers、过滤器,样式和图层。 然后你Python代码只需创建一个新mapnik.Map对象,并告诉Mapnik从XML格式的文件中加载地图的内容。 这允许你地图定义的内容与Python代码分离开, 它能够生成地图并且以同样的方式作为一个HTML模板引擎分离形式和内容在Web应用程序中。

使用XML样式表来渲染地图

此页面将引导你通过使用python绑定和XML文件来改变地图的样式。

结果如下图所示:

首先你需要用一个python脚本来设置基本的地图参数和XML样式表中的点。 复制下面的代码然后粘贴到world_map.py中。

In [11]:
#!/usr/bin/env python
import mapnik
stylesheet = '/gdata/world_map.xml'
image = 'xworld2.png'
m = mapnik.Map(600, 300)
# m = mapnik.Map(1400, 600)
mapnik.load_map(m, stylesheet)
mapnik.render_to_file(m,'xworld2.png', 'png')
m.zoom_all()
mapnik.render_to_file(m, image)
print("rendered image to '%s'" % image)
rendered image to 'xworld2.png'

最后创建world_style.xml,参考是world_map.py。 复制XML,把文件储存在world_style.xml,和world_map.py 也在同一目录下。

现在运行Python脚本:

python world_map.py

它将会输出和入门教程匹配的相同的文件中的一个png图像。

查看文件: %load world_style.xml <!DOCTYPE Map>

My Style shape /gdata/ne_110m_admin_0_countries.shp

实例:生成世界人口专题地图

本教程是介绍一些更先进的Mapnik样式功能,主要是规则和过滤。 这个XML文件只兼容Mapnik 2.x或更高版本。

这个脚本将会产生一个图像

提示:文本的标注位置可能随着Mapnik版本的不同而不同。

把下面的代码储存到 world_population.py 文件中。

In [12]:
#!/usr/bin/env python
import mapnik
mapfile = "/gdata/world_population.xml"
m = mapnik.Map(700, 300)
mapnik.load_map(m, mapfile)
bbox = mapnik.Envelope(mapnik.Coord(-180.0, -75.0), mapnik.Coord(180.0, 90.0))
m.zoom_to_box(bbox)
mapnik.render_to_file(m, 'world_population.png', 'png')
/home/bk/.local/lib/python3.5/site-packages/ipykernel_launcher.py:6: DeprecationWarning: 'Envelope' is deprecated and will be removed in Mapnik 3.x, use 'Box2d' instead
  

把下面的XML储存到 world_population.xml ,目录和 world_population.py 相同。

查看文件: %load /gdata/world_population.xml <!DOCTYPE Map>

<Layer name="countries" srs="+proj=latlong +datum=WGS84" status="on">
    <!-- Style order determines layering hierarchy -->
    <!-- Labels go on top so they are listed second -->
    <StyleName>population</StyleName>
    <StyleName>countries_label</StyleName>
    <Datasource>
        <Parameter name="type">shape</Parameter>
    <Parameter name="file">/gdata/GSHHS_c.shp</Parameter>
    </Datasource>
</Layer>

</Map>

结果为:

In [13]:
Image('world_population.png')
Out[13]:

In [14]:
import helper; helper.info()
页面更新时间: 2019-02-28 18:53:04
操作系统/OS: Linux-4.9.0-8-amd64-x86_64-with-debian-9.8
Python: 3.5.3
In [ ]: