目录

上一个主题

7. 使用 Mapnik 进行地图制图

下一个主题

7.2. 地图和图层对象的属性和方法

关注公众号


常见问题

  1. Windows下的安装说明
  2. Jupyter免费在线实验环境
  3. 勘误与补充


>>> from helper import info; info()
页面更新时间: 2019-10-31 21:34:09
操作系统/OS: Linux-4.19.0-6-amd64-x86_64-with-debian-10.1
Python: 3.7.3

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

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

7.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/mapnik-intro_5_0.png

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

7.1.3. 创建数据源

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

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

7.1.4. 创建图层

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

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

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

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

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

7.1.5. 地图渲染

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

>>> m.layers.append(layer)
>>> m.zoom_all()

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

>>> mapnik.render_to_file(m,'xworld2.png', 'png')
>>> Image('xworld2.png')
_images/mapnik-intro_18_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/mapnik-intro_21_0.png

7.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文件来改变地图的样式。

结果如下图所示:

image0

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

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

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

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

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

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

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

>>> # !/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')
/usr/lib/python3/dist-packages/ipykernel/__main__.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

>>> # %load /gdata/world_population.xml
>>> <!DOCTYPE Map>
>>>
>>> <!-- Sample Mapnik XML template by Dane Springmeyer -->
>>> <Map srs="+proj=latlong +datum=WGS84" background-color="white" minimum-version="0.7.2">
>>>
>>>     <Style name="population">
>>>
>>>         <!-- Built from Seven Class sequential YIGnBu from www.colorbrewer.org -->
>>>         <!-- Quantile breaks originally from QGIS layer classification -->
>>>         <Rule>
>>>             <Filter>[level] = 1</Filter>
>>>             <PolygonSymbolizer fill="#c7e9b4"/>
>>>             <LineSymbolizer stroke="black" stroke-width=".1"/>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <Filter>[level] = 2</Filter>
>>>             <PolygonSymbolizer fill="#7fcdbb"/>
>>>             <LineSymbolizer stroke="black" stroke-width=".1"/>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <Filter>[level] = 3</Filter>
>>>             <PolygonSymbolizer fill="#1d91c0"/>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <ElseFilter/>
>>>             <!-- This will catch all other values - in this case just India and China -->
>>>             <!-- A dark red polygon fill and black outline is used here to highlight these two countries -->
>>>             <PolygonSymbolizer fill="darkred"/>
>>>             <LineSymbolizer stroke="black" stroke-width=".7"/>
>>>         </Rule>
>>>
>>>     </Style>
>>>
>>>     <!--
>>>     <Style name="countries_label">
>>>         <Rule>
>>>             <Filter>[POP_EST] &gt;= 4320000 and [POP_EST] &lt; 9450000</Filter>
>>>             <TextSymbolizer size="7" fill="black" face-name="DejaVu Sans Bold" halo-fill="#DFDBE3" halo-radius="1"
>>>                             wrap-width="20">[NAME]
>>>             </TextSymbolizer>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <Filter>[POP_EST] &gt;= 9450000 and [POP_EST] &lt; 25650000</Filter>
>>>             <TextSymbolizer size="9" fill="black" face-name="DejaVu Sans Book" halo-fill="#DFDBE3" halo-radius="1"
>>>                             wrap-width="20">[NAME]
>>>             </TextSymbolizer>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <Filter>[POP_EST] &gt;= 25650000 and [POP_EST] &lt; 1134000000</Filter>
>>>             <TextSymbolizer size="12" fill="white" face-name="DejaVu Sans Book" halo-fill="#2E2F39" halo-radius="1"
>>>                             wrap-width="20">[NAME]
>>>             </TextSymbolizer>
>>>         </Rule>
>>>
>>>         <Rule>
>>>             <Filter>[POP_EST] &gt;= 1134000000</Filter>
>>>             <TextSymbolizer size="15" fill="white" face-name="DejaVu Sans Book" halo-fill="black" halo-radius="1"
>>>                             wrap-width="20" allow-overlap="true" avoid-edges="true">[NAME]
>>>             </TextSymbolizer>
>>>         </Rule>
>>>                 </Style>
>>> -->
>>>
>>>     <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>

结果为:

>>> Image('world_population.png')
_images/mapnik-intro_33_0.png