>>> from env_helper import info; info()
页面更新时间: 2023-07-08 22:48:50
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-10-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

9.1. 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。

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

9.1.1. Mapnik制图快速开始

首先看一下Mapnik工作的主要流程。

Mapnik工作流程

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

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

安装 Mapnik

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

然后导入并绑定python:

建立地图对象

首先要建立Mapnik对象。

>>> 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”。

>>> from IPython.display import Image
>>> Image('xworld.png')
_images/sec1_intro_5_0.png

9.1.2. 创建样式

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

>>> 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 分别定义了多边形与线状要素的样式; 然后,符号添加到规则中,规则添加到样式中,样式再加入到地图中。

9.1.3. 创建数据源

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

>>> ds = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')

9.1.4. 创建图层

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

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

>>> layer = mapnik.Layer('world')

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

>>> layer.datasource = ds
>>> layer.styles.append('My Style')

9.1.5. 地图渲染

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

>>> m.layers.append(layer)
>>> m.srs='+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'
>>> m.zoom_all()

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

>>> mapnik.render_to_file(m,'xworld2.png', 'png')
>>> Image('xworld2.png')
_images/sec1_intro_19_0.png

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

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

>>>
>>> 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'")
rendered image to 'world.png'
>>> from IPython.display import Image
>>> Image('xx_a.png')
_images/sec1_intro_22_0.png

9.1.6. 使用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 中。

>>>
>>> import mapnik
>>> stylesheet = '/gdata/world_style.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'
>>> Image('xworld2.png')
_images/sec1_intro_26_0.png

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

现在运行Python脚本:

python world_map.py

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

查看这个样式文件:

>>> # %load /gdata/world_style.xml
>>> <!DOCTYPE Map>
>>> <!-- Sample Mapnik XML template by Dane Springmeyer -->
>>> <Map background-color="steelblue" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
>>>   <Style name="My Style">
>>>     <Rule>
>>>       <PolygonSymbolizer fill="#f2eff9" />
>>>       <LineSymbolizer stroke="rgb(50%,50%,50%)" stroke-width="0.1" />
>>>     </Rule>
>>>   </Style>
>>>   <Layer name="world" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
>>>     <StyleName>My Style</StyleName>
>>>     <Datasource>
>>>       <Parameter name="type">shape</Parameter>
>>>       <Parameter name="file">/gdata/GSHHS_c.shp</Parameter>
>>>     </Datasource>
>>>   </Layer>
>>> </Map>