目录

上一个主题

7.1. Mapnik概述

下一个主题

7.3. 数据源

关注公众号


常见问题

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


>>> from helper import info; info()
页面更新时间: 2020-02-21 17:25:16
操作系统/OS: Linux-4.19.0-8-amd64-x86_64-with-debian-10.3 ;Python: 3.7.3

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

地图和图层对象的属性和方法。

7.2.1. 创建地图对象

将数据源、symbolizers、规则、样式设置了,并合并到Mapnik的图层中,然后再将这些图层合并放到地图中。 要做这件事,首先需要创建一个mapnik.Map对象作为一个整体代表地图。

>>> import mapnik
>>> map = mapnik.Map(600, 400 )

初始化Map对象需要3个参数Map(width, height, srs),即以像元为单位的地图宽度和高度, 和一个可选的Proj.4格式初始化字符串 srs 。 如果你不指定一个空间参照系,地图会使用未经过投影的 WGS84 基准面的经度或纬度坐标。

>>> map.srs
'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'

在创建完一个地图后,需要设置地图背景的颜色,然后通过调用map.append_style()方法,将不同的样式添加到地图中。

>>> style1, style2, style3 = [mapnik.Style()] * 3
>>> map.append_style("s1", style1)
>>> map.append_style("s2", style2)
>>> map.append_style("s3", style1)
>>> map.append_style("s1", style3)

上面第一行是使用 Mapnik的 Style() 实例化了三个样式对象。 需要注意,一个样式对象,可以赋给多个值;而在 map对象中,如果已经有了命名的样式,则不能再添加。

7.2.2. 创建图层对象

创建图层对象需要在一幅地图上创建不同的图层。要做这件事,需要创建一个 mapnik.Layer对象来代表每一个图层:

>>> layer = mapnik.Layer('lyrname')
>>> layer.srs
'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'

每一个图层都被给定一个特定的名字,并可以选择与它相关的空间参考。 空间参考的使用与 Map对象一样,如果没有给定的空间参考系,在缺省情况下同样可以使用未经过投影的 WGS84 基准面的经度或纬度坐标。

如果已经创建了地图图层,给它分配一个数据源,并选择适用于该层的样式,通过名称识别每一个样式:

>>> ds = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')
>>> layer.datasource = ds
>>> layer.styles.append("s1")
>>> layer.styles.append("s2")

一个图层,可以添加多个样式。

最后,将新图层添加到地图中。

>>> map.layers.append(layer)

图层的顺序

数据源产生的顺序是非常重要的。如果你从 Shapefile 或者数据库中抽出道路,由此而来的顺序即是制图的顺序。 这是掌控地图外观的主要方法。

如果使用PostGIS绘制道路图, 按照几何长度降序排列可以使较长的道路最先被标注, 这样使得在没有空间标注的情况下,缩放的城市看上去更准确。

如果标注城镇,按人口多少降序排列,则可以使较大的城市首先被绘制, 较小的村落放到最后。结合Mapnik的冲突检测,绘制一张地图, 大的重要位置必定包含在内,小的位置仅在有空间的地方。 对于一个州或者一个国家的缩放图,这是最好的展现效果。

你可以为数据添加明确的优先级标志,便于细粒度控制。 这些是你特别想要实现的: 省会城市先于小城镇,天桥先于立交桥,酒吧先于邮局。

Mapnik可以根据条款顺序,帮助你正确定义并在样式表中生成模块。 而形文件则需要通过ogr2ogr 文件的sql选择再重新生成。

关于Mapnik中的图层顺序,关键是要理解地图的叠加。 如今在线地图应用都坚持这一构架,即在用户使用的浏览器中产生我们都了解和喜欢的可点击拖拽的世界地图。 在地图叠加中,从最顶层到最底层有四个主要的图层面板。

Mapnik采用了画家的算法,来绘制形状和文字, 这实际上意味着,一旦一些东西落实到画布上,便会留在原处。 直接而显而易见的的结果是样式表上定义的图层顺序是十分重要的。 最先加入的图层先被绘制,后来加入的图层会绘制在其他图层的上方, 这一原则也可以通过“后来者居上”来辅助记忆。 海洋和海岸线应该置于顶部,旅游景点和街道名称,则置于底部。

让我们来仔细的看一下一些可选的方法和Mapnik 的Map 和Layer的属性。 当你操作你的地图图层和设置地图图层和样式时这些会非常有用,这些将会选择性的应用基于地图当前的比例尺因子。

mapnik.Map 类别提供了几种有用的额外的方法和属性。

  • map.envelope() 这种方法返回代表需要显示的地图区域的mapnik.Envelope对象。 mapnik.Envelope支持许多有用的方法和属性, 但是最重要的就是包含了minx, minymaxxmaxy属性。 这些定义了地图边界框坐标。

  • map.aspect_fix_mode = mapnik.aspect_fix.GROW_CANVAS 这控制了Mapnik怎样调整地图,如果地图边界的长宽比与要呈现的地图影像的长宽比不一致。支持以下值:

    • GROW_BBOX扩大地图的边界框来匹配将要生成地图影像的长宽比。这是一个默认的行为。

    • GROW_CANVAS扩大将要生成的地图来匹配地图边界框的长宽比。

    • SHRINK_BBOX缩小地图的边界框来匹配生成影响的长宽比。

    • SHRINK_CANVAS缩小要生成影像来匹配地图边界框的长宽比。

    • ADJUST_BBOX_HEIGHT扩大或缩小地图边界框的高度,在宽度保持恒定的同时,来匹配要生成图像的长宽比。

    • ADJUST_BBOX_WIDTH扩大或缩小地图边框的宽度,在保持高度一定的同时,来匹配要生成影像的长宽比。

    • ADJUST_CANVAS_HEIGHT扩大或缩小要生成地图的高度,在保持宽度一定的同时,来匹配多边形的边界框的长宽比。

    • ADJUST_CANVAS_WIDTH扩大或缩小要生成影像的宽度,在保持高度一定的同时,来匹配地图边框的长宽比。

  • map.scale_denominator() 返回当前用于生成地图的比例尺分母。比例尺分母取决于地图的边界和 要生成影像的大小。

  • map.scale() 返回当前地图使用的比例尺因子,比例尺因子取决于地图的边界和要呈 影像,

  • map.zoom_all() 设置地图的边界框来涵盖每个地图图层的边框。这样能确保所有的地图 数据出现在地图上。

  • map.zoom_to_box(mapnik.Envelope(minX, minY, maxX, maxY)) 将地图的边界框设置成给定的边界的大小。需要注意的是minXminYmaxXmaxY都在地图的坐标系统中。

注:可选(了解你的数据) 你可以调用envelop()函数关闭数据源查看完整的坐标范围数据:

>>> import mapnik
>>> map = mapnik.Map(600, 400 )
>>> ds = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')
>>> ds.envelope()
Box2d(-180.0,-89.99999999999994,180.00000000000023,83.53036100000006)

这显示了数据的 (mainx, miny, maxx, maxy) 。 因为上述x坐标在 ( − 180, 180)之间。 y或者是纬度值在( − 90, 90)之间。 我们知道这是地理坐标,使用度为单位。 不错的迹象,这是WGS84(又名EPSG:4326). 这个特殊的shapfile把投影信息存储成一个WKT字符串, 在 ne_110m_admin_0_countries.prj file. 但是 Mapnik 需要通过Proj.4字符串知道这个具体的 通用的空间参考系统+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs。 看下面的layer.srs值,了解为什么它是有关系的。

图层对象属性和方法

mapnik.Layer类别有以下有用的属性和方法:

  • layer.envelope() 这种方法返回代表了包含所有地图图层数据的地图的矩形的区域mapnik.Envelope对象。 mapnik.Envelope对象支持大量有用的方法和属性, 但是最重要的是包含minx, miny, maxxmaxy属性。

  • layer.active = False 这可以用来在地图中隐藏图层。

  • layer.minzoom = 1.0/100000 这用来设置如果该图层将要出现在地图上,必须应用的最小的比例尺因子。如果没有进行设置,该图层将没有一个小比例尺因子。

  • layer.maxzoom = 1.0/10000 这设置的是如果图层将要在地图上绘制,必须应用的最大的比例尺因子。如果没有进行设置,该图层将没有一个大比例尺因子。

  • layer.visible(1.0/50000) 这种方法返回True,如果该图层以给定的比例尺因子绘制在地图上。图层是可见的,如果它是活跃的并且给定的值在图层的最大和最小的值之间。

>>> import os
>>> import mapnik
>>> # from helper import get_tmp_file
>>>
>>> # fig_index = 0
>>> stylesheet = '/gdata/world_map.xml'
>>> # image = 'xx_world_style_from_xml.png'
>>> m = mapnik.Map(600, 300)
>>> mapnik.load_map(m, stylesheet)
>>> m.zoom_all()
>>> m.background = mapnik.Color('steelblue')
>>>
>>> print(m.scale())
>>> # fig_index += 1
>>> mapnik.render_to_file(m, 'xx1.png', 'png')
>>>
>>> chinabox = mapnik.Box2d(73, 0, 135, 54)
>>> m.zoom_to_box(chinabox)
>>>
>>> print(m.scale())
>>> # fig_index += 1
>>> mapnik.render_to_file(m, 'xx2.png', 'png')
>>>
>>> for x in m.layers:
>>>     print(x.name)
>>>     print(x.envelope())
0.6000000000000004
0.18
world
Box2d(-180.0,-89.99999999999994,180.00000000000023,83.53036100000006)

使用颜色

许多的Mapnik的symbolizers要求提供一个颜色的值。这些颜色值是使用mapnik.Color类别进行定义的。mapnik.Color能够通过以下方式进行定义:

  • mapnik.Color(r, g, b, a): 创建一个颜色对象通过提供单独的红色、绿色、蓝色和α(不透明度)值,每一个值的范围是0到255.

  • mapnik.Color(r, g, b): 创建一个颜色对象通过提供红色、绿色、蓝色的组成。每一个值都应该在0到255之间。结果的对象是完全不透明的。

  • mapnik.Color(colorName): 创建一个颜色对象通过制定一个标准的CSS颜色的名称 [1]。

  • mapnik.Color(colorCode): 创建一个颜色对象通过使用HTML颜色代码。例如,#806040是中度棕色。

7.2.3. 地图晕渲

在创建mapnik.Map对象和设置各种各样的symbolizers、 规则、样式、数据源和其中的图层之后,需要将图像转换成彩色图像。

Mapnik支持大量的渲染后端。

Cairo渲染器是Mapnik的辅助渲染器。 Cairo被加到r656中也是因为其图像输出到不同的格式中去。 Cairo还有一个增加的优势,即是支持矢量和栅格的输出。 Mapnik可以渲染cairo支持的任何表面,也可以直接生成或是渲染到cairo context中。你可以使用OSM输出工具演示PNG,JPEG,SVG,PDF和PS格式。 Cairo在Mapnik Scons创建过程是可选的,但是如果发现就会自动启用(使用 pkg-config)。 pkg-config必须找到libcairo,Cairomm(C++ bindings) 和 Pycairo (python bindings)。

Python示例代码:用Mapnik的Cairo渲染器到SVG中。

>>> import mapnik
>>> import cairo
>>> import os
>>>
>>>
>>> # mapfile = 'world_population.xml'
>>>
>>> mapfile = '/gdata/world_population.xml'
>>>
>>>
>>> projection = '+proj=latlong +datum=WGS84'
>>>
>>> mapnik_map = mapnik.Map(1000, 500)
>>> mapnik.load_map(mapnik_map, mapfile)
>>> bbox = mapnik.Box2d(-180.0,-90.0,180.0,90.0)
>>> mapnik_map.zoom_to_box(bbox)
>>>
>>> mapnik.render_to_file(mapnik_map, 'xx_a.png', 'png')

Write to SVG

>>> surface = cairo.SVGSurface('xx_a.svg', mapnik_map.width, mapnik_map.height)
>>> mapnik.render(mapnik_map, surface)
>>> surface.finish()

Or write to PDF

>>> surface = cairo.PDFSurface('xx_a.pdf', mapnik_map.width, mapnik_map.height)
>>> mapnik.render(mapnik_map, surface)
>>> surface.finish()

提示:Cairo可以写到PostScript或者是其他的图像格式。 mapnik.render()可以生成到Cairo Context中。

渲染结果输出

在呈现出一幅地图图像之前,确定已经为地图设置合适的区域边界框, 这样就可以展现出世界地图中你感兴趣的区域。 你可以通过调用map.zoom_to_box()来用一组给定的坐标明确的设置地图的边界框来做这件事, 或者你也可以调用map.zoom_all()来让地图自动的设置它的边界基于将要显示的数据。 一旦你已经设置了边界框,你可以通过调用render_to_file()函数生成你的地图,如下所示:

mapnik.render_to_file(map, 'map.png')

参数是mapnik.Map的对象和用来编写地图的影像文件的名称。

这一点是非常重要的。不然仅仅是地图渲染的非常华丽, 但错误的范围或缩放级别却会导致失败的产品。

如果你想要更多的控制影像的格式,你可以添加定义影像格式的额外的参数。

mapnik.render_to_file(map, 'map.png', 'png256')

输出支持的图片格式包括:

Image Format

Description

png

A 32-bit PNG format image

png256

An 8-bit PNG format image

jpeg

A JPEG-format image

svc

An SVG-format image

pdf

A PDF file

ps

A postscript format file

渲染函数

大多数的渲染函数例如 save_to_string 还需要一个第三参数, 它必须是一个 rgba_palette 对象。 可以通过传递拥有RGBA(4字节)值的缓冲区,RGB(3字节)值的缓冲区或者是 一个photoshop执行文件(在网页另存为对话框中产生的), RGBA_palette 构造正好772字节。注意调色板对象是可变的,并包含一个查找表, 为了快速编码,取回的所有的值都加入到缓存。

7.2.4. 地图制图的投影问题

微软,雅虎,谷歌,OpenStreetMap,以及作者开发的 OSGeo 中地图模块( https://www.osgeo.cn/map/ )等都是使用的墨卡托投影, 是全球覆盖的理想方式。

墨卡托投影的一个简化特征是基于一个清晰的球形的地面输出,使得 JavaScript 或 ActionScript 客户端计算起来更容易。大部分Modest Maps都基于这种假设, 恰当定义你地图输出的SRS是:

+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+k=1.0 +units=m +nadgrids=@null +no_defs

为样式定义的最大或最小缩放尺度设置一些明智的快捷键。

>>> import os
>>>
>>> import mapnik
>>> #  from helper import get_tmp_file
>>> # mapnik.Color('y')
>>> m = mapnik.Map(1000, 800, '+init=epsg:4326')
>>> m.background = mapnik.Color('steelblue')
>>> s = mapnik.Style()
>>> r = mapnik.Rule()
>>> # polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9'))
>>> polygon_symbolizer = mapnik.PolygonSymbolizer()
>>> polygon_symbolizer.fill = mapnik.Color('#f2eff9')
>>>
>>> # polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('blue'))
>>> r.symbols.append(polygon_symbolizer)
>>> # line_symbolizer = mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1)
>>>
>>> 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)
>>> lyr = mapnik.Layer('world', "+proj=latlong +datum=WGS84")
>>> lyr.datasource = mapnik.Shapefile(file='/gdata/GSHHS_c.shp')
>>> lyr.styles.append('My Style')
>>> m.layers.append(lyr)
>>> m.zoom_to_box(lyr.envelope())
>>> # mapnik.render_to_file(m, 'xx_world_fk.png', 'png')
>>>
>>> mapnik.render_to_file(m, 'xx_b.png', 'png')