绘制样式

绘制线

Mapnik提供了两种绘制线状要素的方法: LineSymbolizer 和LinePatternSymbolizer。让我们依次来看一看:

In [1]:
import mapnik
line_symbolizer2 = mapnik.LinePatternSymbolizer()
In [2]:
line_symbolizer2.stroke = mapnik.Color('rgb(50%,50%,50%)')
# dir(line_symbolizer2.symbol)
In [3]:
line_symbolizer2.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP
In [4]:
dir(line_symbolizer2.symbol)
Out[4]:
['__call__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__func__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__self__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
In [ ]:
 
In [5]:
line_symbolizer2.stroke_linecap
Out[5]:
2
In [6]:
line_symbolizer2.stroke_linecap = mapnik.stroke_linecap.SQUARE_CAP
In [7]:
line_symbolizer2.stroke_linecap
Out[7]:
1
In [8]:
# -*- coding: utf-8 -*-
import os

import mapnik
# from helper import get_tmp_file

def renderit(line_sym = None, fig_index = 0):

    # mapnik.Color('y')
    m = mapnik.Map(600, 300, "+proj=latlong +datum=WGS84")
    # m.background = mapnik.Color('steelblue')
    s = mapnik.Style()
    r = mapnik.Rule()

    r.symbols.append(line_sym)
    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)

    bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
    m.zoom_to_box(bbox)
    mapnik.render_to_file(m, 'xx_line_{idx}_em.png'.format(idx = fig_index), 'png')


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

# sss = mapnik.Stroke(mapnik.Color('green'),0.1)
line_symbolizer.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP
line_symbolizer.stroke_width = 5.0

fig_index = 0
fig_index += 1
renderit(line_symbolizer, fig_index)

########################
line_symbolizer.stroke_linecap = mapnik.stroke_linecap.BUTT_CAP
# line_symbolizer.stoke_dasharray = [5,10]
fig_index += 1
renderit(line_symbolizer, fig_index)

##############################################
fig_index += 1
line_symbolizer.stroke_opacity = 0.5
renderit(line_symbolizer, fig_index)

######################################
line_symbolizer2 = mapnik.LinePatternSymbolizer()
# line_symbolizer2.file = os.path.join(os.path.split(os.path.realpath(__file__))[0], 'tri.png')
line_symbolizer2.file = '/gdata/tri.png'
# line_symbolizer2.stroke_width

print('x'* 40)
print(dir(line_symbolizer2))
print('x'* 40)
line_symbolizer2.stroke = mapnik.Color('rgb(50%,50%,50%)')
line_symbolizer2.stroke_linecap = mapnik.stroke_linecap.ROUND_CAP
line_symbolizer2.stroke_width = 1

fig_index += 1
renderit(line_symbolizer2, fig_index)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__instance_size__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_c___module__', '_c___qualname__', 'filename', 'symbol']
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
In [9]:
################################################

roadEdgeSymbolizer = mapnik.LineSymbolizer()
roadEdgeSymbolizer.stroke = mapnik.Color("#bf7a3a")
roadEdgeSymbolizer.stroke_width = 7

roadInteriorSymbolizer = mapnik.LineSymbolizer()
roadInteriorSymbolizer.stroke = mapnik.Color("#ffd3a9")
roadInteriorSymbolizer.stroke_width = 6

m = mapnik.Map(600, 300, "+proj=latlong +datum=WGS84")
# m.background = mapnik.Color('steelblue')
s = mapnik.Style()
r = mapnik.Rule()

r.symbols.append(roadEdgeSymbolizer)
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)

#-----------------
# s2 = mapnik.Style()
# r2 = mapnik.Rule()
#
# r2.symbols.append(roadInteriorSymbolizer)
# s2.rules.append(r2)
# m.append_style('My Style2', s2)
# lyr2 = mapnik.Layer('world', "+proj=latlong +datum=WGS84")
# lyr2.datasource = mapnik.Shapefile(file='gdata/world_borders.shp')
# lyr2.styles.append('My Style2')
# m.layers.append(lyr2)

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
# m.zoom_to_box(bbox)
m.zoom_all()

fig_index += 1
mapnik.render_to_file(m, 'xx_line_kz.png', 'png')

上面生成的结果为:

In [10]:
from IPython.display import Image
Image('./xx_line_kz.png')
Out[10]:

LineSymbolizer

LinesSymbolizer可以绘制线状要素,也可以用来表征多边形轮廓,如下图所示:

LinesSymbolizer是Mapnik中最有用的symbolizers之一。这里是用于创建绘制虚线的LinesSymbolizer的Python的代码,例如:

In [11]:
import mapnik
# stroke = mapnik.Stroke()
# stroke.color = mapnik.Color("#008000")
# stroke.width = 1.0
# stroke.add_dash(5, 10)
# symbolizer = mapnik.LineSymbolizer(stroke)

symbolizer = mapnik.LineSymbolizer()
symbolizer.stroke = mapnik.Color("#008000")
symbolizer.stroke_width = 1.0

LineSymbolizer使用Mapnik的 Stroke 对象来定义绘制线。 为了使用LineSymbolizer,首先你需要创建stroke对象和绘制线形的各种选项。 然后,可以创建 LineSymbolizer,传递 stroke 对象到 LineSymbolizer 的构造器中。

symbolizer = mapnik.LineSymbolizer(stroke)

让我们来更仔细的看一下由stroke对象提供的绘制线型的各种选项。

默认情况下,线将会被绘制成黑色。你可以通过设置 strokecolor 属性到Mapnik的颜色对象中来改变颜色。

stroke.color = mapnik.Color("red")

在默认情况下,LineSymbolizer绘制的线有一个像元的宽。 你可以设置stroke的width属性改变线的宽度;你也可以通过设置stroke的opacity属性改变线的透明程度:

stroke.width = 1.5
stroke.opacity = 0.8

注意:你可以通过改变微小的线的宽度来对你的线的宽度进行细粒度的控制。

透明度的范围是从0.0(完全透明)到1.0(完全不透明)。如果没有定义透明度,则线将会完全不透明。

line_cap定义的是绘制线的结尾。Mapnik支持三种水平的line_cap的设置。

默认情况下,线会使用BUTT_CAP格式,但是你可以通过设置strokeline_cap属性来改变这种状况,例如:

stroke1.line_cap = mapnik.line_cap.BUTT_CAP
stroke2.line_cap = mapnik.line_cap.ROUND_CAP
stroke3.line_cap = mapnik.line_cap.SQUARE_CAP

线的连接

改变一条线的方向,可以通过三种标准的方式之一来绘制它的转折端:

默认的行为就是使用MITER_JOIN, 你可以通过设置stroke为一个不同的值的line_join,来得到不同的效果。

In [12]:
# stroke1.line_join = mapnik.line_join.MITER_JOIN
# stroke2.line_join = mapnik.line_join.ROUND_JOIN
# stroke3.line_join = mapnik.line_join.BEVEL_JOIN
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-12-6598a4c50d17> in <module>()
----> 1 stroke1.line_join = mapnik.line_join.MITER_JOIN
      2 stroke2.line_join = mapnik.line_join.ROUND_JOIN
      3 stroke3.line_join = mapnik.line_join.BEVEL_JOIN

AttributeError: module 'mapnik' has no attribute 'line_join'

绘制道路和其他复杂的线性要素

你可以通过叠加两个symbolizers来在地图上绘制道路。 第一个LineSymbolizer是用来绘制道路的轮廓, 第二个LineSymbolizer 是用来绘制道路的内部。例如:

In [13]:
import mapnik
stroke = mapnik.Stroke()
stroke.color = mapnik.Color("#bf7a3a")
stroke.width = 7.0
roadEdgeSymbolizer = mapnik.LineSymbolizer(stroke)
stroke = mapnik.Stroke()
stroke.color = mapnik.Color("#ffd3a9")
stroke.width = 6.0
roadInteriorSymbolizer = mapnik.LineSymbolizer(stroke)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-59c6e1c468a6> in <module>()
      1 import mapnik
----> 2 stroke = mapnik.Stroke()
      3 stroke.color = mapnik.Color("#bf7a3a")
      4 stroke.width = 7.0
      5 roadEdgeSymbolizer = mapnik.LineSymbolizer(stroke)

AttributeError: module 'mapnik' has no attribute 'Stroke'

在绘制街道地图方面这项技术已被普遍应用了。 我们刚刚定义的两个symbolizers将会被叠加并产生道路,如下图所示:

这项技术不仅能够被用来绘制道路;还能够创造性的运用symbolizers,symbolizers是利用Mapnik来实现复杂视觉效果的一个主要技巧。

下面来看一下如何绘制虚线和点线:

你可以添加“breaks”使其出现虚线和点线。要实现这一点,你可以在stroke上添加一个或多个虚线片段。每个虚线的部分定义了划线长度和间隙长度; 线将会利用给定的划线长度来绘制,然后再继续画下一段线之前留下定义长度的空隙:

image1

你可以通过调用stroke的add_dash()的方法在线上添加少许线段:

  • all text stuff
  • dash arrays
stroke.add_dash(5, 5)

这将会给线一个5像元的短划线,接下来是5像元的间隔: 你不能被限制到只有一个虚线片段: 如果调用add_dash()多次,你将创建一个拥有更多片段的线。 这些虚线片段也会被依次处理,并允许你创建更多虚线和点的模式。例如:

In [ ]:
stroke.add_dash(10, 2)
stroke.add_dash(2, 2)
stroke.add_dash(2, 2)

这样就会生成重复的线的图案。

渲染道路的标准样式,即内置浅色填充标签、边缘有稀薄的轮廓, 这些都可以通过重复图层来实现。 首先,你放下一个厚厚的深色道路的图层,比如14像素宽; 其次,你放置另一个薄薄的,浅色道路图层,比如12像素宽。 二者结合,便能准确的显现出网格的街道轮廓, 而且看起来不会有一条路覆盖另一条路的交织的街道十字路口。 很多复杂的视觉效果可以通过类似的方式实现,即通过反复的分层来实现特别的效果。

In [ ]:
#!/usr/bin/env python

import os
import mapnik
# from helper import get_tmp_file

stylesheet = '/gdata/world_map_dash.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')

bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)

m.zoom_to_box(bbox)

mapnik.render_to_file(m, 'xx_line_jv.png', 'png')

LinePatternSymbolizer

LinePatternSymbolizers通常是用在一些特殊状况的,例如:当你使用一个单一的Stroke对象来绘制一条线而不能显现出来的时候。 LinePatternSymbolizers可以接受PDF或TIFF格式的文件, 且沿着线的长度的方向或者多边形轮廓的周围重复绘制图像:

image2

注意:线性要素和多边形的轮廓都有一个方向,即线和多边形的边界按照几何图形被创建时点被定义的顺序从一个点移动到下一个点。

例如,组成上述图顶部的线段的点是从左到右来定义的,即最先被定义的是最左边的点,其次是中间的点,最后是右边的点。

一个要素的方向是很重要的,因为它影响LinePatternSymbolizers绘制图像的方式。如果我们刚刚看到的线串定义的是相反的方向, 那么LinePatternSymbolizers将把其绘制成这个样子:

LinePatternSymbolizers绘制图像面向线的最左端, 因为它是从一个点移动到下一个点。 要想绘制一幅朝向为右的图像,你将不得不在你的要素范围之内调转点的顺序。 要想在你的Python代码中使用LinePatternSymbolizers, 你只需创建一个mapnik.LinePatternSymbolizer的例子, 并给它一个图像文件的名字,文件的格式(PNG或TIFF),图像像元大小:

In [ ]:
symbolizer = mapnik.LinePatternSymbolizer("image.png","png", 11, 11)
In [ ]:
help(mapnik.LinePatternSymbolizer)
In [ ]:
 

绘制多边形

绘制多边形有两个symbolizer绘制线条,也有两个symbolizers绘制多边形的内部:PolygonSymbolizerPolygonPatternSymbolizer

现在让我们来了解一下这两个symbolizers:

PolygonSymbolizer的基本属性设置

用一种单一的颜色填充一个 PolygonSymbolizer 多边形的内部。

常用属性

你可以用下面的方式创建一个 PolygonSymbolizer :

In [ ]:
import mapnik
symbolizer = mapnik.PolygonSymbolizer()

让我们来看一下如何控制绘制多边形的各种选项。

在默认情况下,一个 PolygonSymbolizer 可以把多边形的内部绘制为灰色。 要想改变填充多边形的内部颜色,需要将 PolygonSymbolizer’s fill属性设置为想要的 Mapnik 颜色对象:

In [ ]:
symbolizer.fill = mapnik.Color("red")

在默认情况下,多边形将是完全不透明的。 你可以通过设置PolygonSymbolizer的 opacity 属性表来改变这种状况: 透明度的范围是0.0(完全透明)到1.0(完全透明)。在前面的图中,左边图形的透明度为0.5。

In [ ]:
symbolizer.fill_opacity = 0.5

伽玛校正是一个不隐晦的概念,而且有时也是非常有用的。 如果你绘制两个恰好是以相同的颜色相交的多边形,你能在两个多边形之间看到一条线,正如下图所示: 这是因为Mapnik的多边形边缘抗重叠的方式。 如果你想要这些在相邻多边形之间的线消失,你可以添加一个伽玛校正系数:

In [ ]:
symbolizer.gamma = 0.63

这样会导致两个多边形以同一个方式出现。

使用0.5到0.7的伽玛值大致会移除两个相邻多边形的重叠线。 默认的值为1.0,则意味着将不适用于任何的伽玛校正。

PolygonPatternSymbolizer的用法

使用 PolygonPatternSymbolizer 提供的图像文件填充多边形的内部:

图像将被平铺,也就是说反复绘制以填补整个多边形的内部。

image2

因为图块的右侧将会出现与相邻的图块左侧相接,图块的底部将会直接出现在下面的图块上面(反之亦然), 当以这种方式绘制时,你需要选择一个看起来比较合适的图像。使用 PolygonPatternSymbolizer 是比较简单的,就如同 LinePatternSymbolizer 一样,你创建一个新的实例,然后给他一个图像文件的名称,文件的格式(PNG 或者 TIFF)和图像的宽度和高度:

In [ ]:
symbolizer = mapnik.PolygonPatternSymbolizer("image.png","png", 102, 80)
In [ ]:
# -*- coding: utf-8 -*-
import os

import mapnik
# from helper import get_tmp_file


def renderit(poly_sym=None, fig_index=0):
    # mapnik.Color('y')
    m = mapnik.Map(600, 300, "+proj=latlong +datum=WGS84")
    # m.background = mapnik.Color('steelblue')
    s = mapnik.Style()
    r = mapnik.Rule()
    # polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9'))


    # polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('blue'))
    r.symbols.append(poly_sym)
    # 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_linecap = mapnik.stroke_linecap.ROUND_CAP
    line_symbolizer.stroke_width = 5.0

    # line_symbolizer.stroke_width = 0.1
    # line_symbolizer.stroke_dasharray( [5,10])

    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/world_borders.shp')
    lyr.styles.append('My Style')
    m.layers.append(lyr)

    bbox = mapnik.Box2d(70, 20, 135, 57)

    m.zoom_to_box(bbox)
    # mapnik.render_to_file(m, 'xx_world_fk.png', 'png')
    mapnik.render_to_file(m, 'xx_poly_{idx}.png'.format(idx = fig_index), 'png')


polygon_symbolizer = mapnik.PolygonSymbolizer()
polygon_symbolizer.fill = mapnik.Color('#f2eff9')

fig_index = 0
fig_index += 1
renderit(polygon_symbolizer, fig_index)

polygon_symbolizer.fill = mapnik.Color('#ff0000')
fig_index += 1
renderit(polygon_symbolizer, fig_index)


# polygon2_symbolizer.fill = mapnik.PolygonPatternSymbolizer
#     .symbol(os.path.join(os.path.split(os.path.abspath(__file__))[0], "image.png"), "png", 102, 80)
sym_img = os.path.join(os.getcwd(), "weizhi3.png")


polygon2_symbolizer = mapnik.PolygonPatternSymbolizer()
polygon2_symbolizer.file = sym_img
polygon2_symbolizer.fill = True
polygon2_symbolizer.stroke_file = sym_img
polygon2_symbolizer.stroke_fill = sym_img
# polygon2_symbolizer.fill = sym_img
# polygon2_symbolizer.file = sym_img
print('='* 40)
print(sym_img)
print(dir(polygon2_symbolizer))
print(dir(polygon2_symbolizer.symbol()))

print('='* 40)
fig_index += 1
renderit(polygon2_symbolizer, fig_index)
In [ ]:
#!/usr/bin/env python

import os
import mapnik
# from helper import get_tmp_file

stylesheet = '/gdata/world_map_poly.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')

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
bbox = mapnik.Box2d(70, 20, 135, 57)
m.zoom_to_box(bbox)

mapnik.render_to_file(m,'xx_map_poly.png', 'png')

In [ ]:
 

绘制标注

文本标注可以使用文字将地理空间数据显示在地图上, 与地图符号一样,对于任何一个地图文本标记都是很重要的部分。 在这个章节中,我们将会探究一下在Mapnik中的地图上绘制文本的TextSymbolizer。

文本标记的基本用法

TextSymbolizer允许你在点、线、面要素上进行绘制标记:

In [ ]:
#!/usr/bin/env python

import os
import mapnik
# from helper import get_tmp_file

stylesheet = '/gdata/world_map_label_point.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')

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
bbox = mapnik.Box2d(70, 20, 135, 57)
m.zoom_to_box(bbox)
mapnik.render_to_file(m, 'xx_label_1_zv.png', 'png')


for x in m.styles:
    for y in x[1].rules:
        for z in y.symbols:
            tt = z.extract()
            print('=' * 20)
            print(dir(z.extract()))
            try:
                print('x'* 60)
                print(tt.name())
                print('x' * 60)
            except:
                pass

            print('-' * 10)
            # print(z.extract().filename)
            print(dir(z.symbol))
            print(z.type)
######################################################################

stylesheet = '/gdata/world_map_label_line.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')

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
bbox = mapnik.Box2d(70, 20, 135, 57)
m.zoom_to_box(bbox)
mapnik.render_to_file(m, 'xx_label_2_zv.png', 'png')

########################################################################
stylesheet = '/gdata/world_map_label_poly.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')

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
bbox = mapnik.Box2d(70, 20, 135, 57)
m.zoom_to_box(bbox)
mapnik.render_to_file(m, 'xx_label_3_zv.png', 'png')

image1

img2

TextSymbolizer的基本用法是很简单的。 例如,最后一个例子中的多边形是使用以下代码进行标记的:

In [ ]:
import mapnik
symbolizer = mapnik.TextSymbolizer(mapnik.Expression('[FIELD_NAME]'), "DejaVu Sans Book", 10, mapnik.Color("black"))

symbolizers 将会显示要素的 label 字段的值, 使用给定字体、字体大小、颜色。 无论什么时候创建一个TextSymbolizer对象,都必须提供这四项参数。

仔细看一看这些参数,还有哪些能够控制的显示文本。

选择将要显示的文本通过将一个字段或者属性名称作为TextSymbolizer构造中的第一个参数。 该文本始终来源于基础数据,没有将label强行连接到规则的选项。

对于许多数据源,名称是用来区分大小写的, 所以为了确保键入的字段的名称和属性是完全正确的。 MAMEname 并不等同。

第二个参数是使用创建TextSymbolizer对象时进行定义的给定的字体和字体大小进行绘制的。 有两个选项来选择字体:可以使用Mapnik提供的内置字体中的一个,或者可以安装自己的自定义字体。

设置文字是透明或者不透明的程度,通过设置透明度属性,如下所示:

symbolizer.opacity = 0.5

透明度的范围是0.0(完全透明)到1.0(完全不透明)

TextSymbolizer将文本放置到被标记的要素中有两种方法。使用point placement(在默认情况下), 通过以下的方式Mapnik将会绘制标记到以前显示过的三个要素中:

image2

如图所示,标记已被绘制到每个要素的中心位置。 标记是被水平绘制的,与行的朝向无关。 放置标记到要素中的另一个选项就是使用line placement。使用line placement标记以前的要素会出现以下的结果:

image3

注意:多边形的标记应沿着多边形的边界被绘制,并且标记与线条的方向一致。点要素没有被标记,因为在点要素中没有线条。 你通过设置symbolizer的 label_placement 属性来控制文本的位置,例如:

In [ ]:
sym1.label_placement = mapnik.label_placement.POINT_PLACEMENT
sym2.label_placement = mapnik.label_placement.LINE_PLACEMENT

当使用LINE_PLACEMENT放置标记时,在默认情况下,在线条的中央Mapnik 将会绘制一次标签。 在许多情况下,沿着线条的一段重复标记也是很有道理的。 要做到这一点,你就需要设置symbolizerlabel_spacing属性表,如下 所示:

symbolizer.label_spacing = 30

设置这个属性会导致线或多边形的边界被重复标记。 该值是重复标记之间的间隔量,以像元为单位。使用以上的label spacing,我们的线条和多边形要素将用以下的方式显示:

下面是几种被用来微调显示重复标记的的属性:

  • symbolizer.force_odd_labels = True:TextSymbolizer绘制奇数标记,这样可以使标记在某些情况下看起来更好。
  • symbolizer.max_char_angle_delta = 45:设置变化的最大夹角值(以度为单位)即从一个字符到下一个。使用这种方法可以防止Mapnik尖角附近被标记,例如:
  • symbolizer.min_distance = 40:重复的标记之间的最小距离,以像元为单位。
  • symbolizer.label_position_tolerance = 20:这设置的是能够沿着线条移动的最大距离来避开其他的标记和尖角的一个标记。这个值是以像元为单位的,默认为in_distance/2

控制文本重叠

在默认情况下,Mapnik能确保两个标记不会相交。 如果可能的话,它会移动标记来避免重叠。 如果你仔细的看一下沿着下面两个多边形边界绘制的标记, 你会发现,左边多边形的标记的位置已经被调整了从而避免重叠。

image6

如果Mapnik决定它不能移动标记在不歪曲标记位置基础之上, 则它会将标记完全隐藏起来。你可以在下面的图中看到,在图上两个多边形被移动了,所以他们重叠了。

image7

属性allow_overlap允许你改变这个行为方式。

symbolizer.allow_overlap = True

Mapnik会绘制在另一个地图上面,而不是将重叠的标记隐藏起来。

image7

使用文本标记的“晕”

TextSymbolizer通常会直接在地图上绘制文本。 当文本被放置在地图上的浅色区域,但是如果地图被覆盖的区域是深色或者遥感影像等背景比较乱的情况, 文本将很难读出甚至显示。

当然,可以选择一个浅色的文本的颜色,但是你需要提前了解背景可能是什么样子的, 一个比较好的解决方式就是在文本周围绘制一个“晕”,如下边的图像所示:

halo_fillhalo_fill属性用来定义“晕”的颜色和大小,例如:

In [ ]:
symbolizer.halo_fill = mapnik.Color("white")
symbolizer.halo_radius = 1

半径指定以像元为单位,一般情况下,值为1或2,则能够确保文本在深色的背景之下是可读的。

文本细节的调整

调整文本的位置

在默认情况下,Mapnik计算需要在点上显示的文本,然后将该文本显示在点的中间。

Mapnik提供了两种调整的方式:通过改变vertical alignment(垂直位移),通过指定一个text displacement(文本位移)。 通过改变TextSymbolizer的vertical_alignment属性可以控制垂直位移。有三种你可以使用的垂直位移值:

In [ ]:
sym2.vertical_alignment = mapnik.vertical_alignment.TOP
sym1.vertical_alignment = mapnik.vertical_alignment.MIDDLE
sym3.vertical_alignment = mapnik.vertical_alignment.BOTTOM

mapnik.vertical_alignment.MIDDLE是默认的,将标记垂直的放在点的中心。 如果你将垂直位移改变成mapnik.vertical_alignment.TOP,标记将会被绘制到点的上部。反过来,如果你将垂直改变成mapnik.vertical_alignment.BOTTOM,标记将被绘制到点的下部。

更细致的调整文本位置的其他选项就是使用displacement()方法, 来用给定的像元数目显示文本。例如:

In [ ]:
symbolizer.displacement = (5, 10)

偏移的量从左上角算起。这会导致标记向右边偏向5个单位,从他的正常位置下移10个单位。

注意, 改变标注的垂直位移同样也会改变标记的默认的vertical_alignment值。 这会导致标记用你不想要的方式移动,因为标记的垂直走向被改变了 作为一个设置垂直位移的单方面影响因素。 为了避免这种情况的发生,你同时也应该明确的设置一下vertical_alignment属性, 无论你什么时候改变垂直位移。

还有一些属性可以控制文本显示的细节,如文本的宽度(自动换行)、 字符间距等。

使用wrap_width属性来强制标记在跨越多列时换行,指定的值就是文本每个行的最大值,以像元为单位。 symbolizer.character_spacing = 3,在文本中你可以在字符之间添加额外的空隙。 你同样可以改变不同行之间的距离通过使用line_spacing属性。 字符间距和行间距的值都是以像元为单位的。 有些时候你想改变文本显示的状况。你可以做这件事情通过设置text_convert属性。

文本的高级设置

将文本放置在道路上时,Mapnik 有很多专门的操作。 你会发现,在急转的角落里,文字会弯曲,这样会导致街名难看的扭结。 幸好,有一个max_char_angle_delta属性,可以添加TextSymbolizer, 以使得标签远离急转弯,20度看起来很不错。 虽然仍然可以得到弯曲的道路标识,但是没有不和谐的角落。

如果发现数据集将较长的路段切割成较短的几部分, 会发现Mapnik不能使标签与之适应, 即使适应也会产生每个路段的不必要的重复。 我们的道路数据来源于商业, 这些道路数据已经被优化以便于灵活地制图, 许多较长的道路分支依据车道数量极其其他特征被分成许多小段。 我们对展现如此高层次的细节没有兴趣, 所以他们更愿意提供这样一个版本的数据, 同名且连接在一起道路融合成更长的线路。

路线和高速公路标签的常见的形式是“盾牌”, 由Mapnik作为ShieldSymbolizer应用。 盾牌是文字和图形的组合,意在表明短的道路号码, 与名称不同(如“加州13”与“沃伦高速公路”)。 像上面的顺序技巧,盾牌需要一些策略来得到正确的位置。 你可以使用常规的SQL LENGTH(名称)功能结合Mapnik样式的过滤器,来创建简单的条件: 例如,使用宽宽的盾牌图形标注长长的路线编号, 一个窄窄的盾牌图形标注较短的路线编号。

字体

Mapnik使用自己的字体配置方式。 在Debian中,这个路径是/usr/share/fonts/truetype/ttf-dejavu/, 可以把要用到字体放到这个文件夹下面,同时还需要注意字体文件的权限。

为了寻找出哪个字体是可用的,运行下面的程序:

In [ ]:
import mapnik
for font in mapnik.FontEngine.face_names():
    print (font)

中文标注的问题

Mapnik支持中文标注,但是在使用的时候要注意字符编码。 但是要注意的是,Shape文件默认不使用UTF-8编码,可以使用ogr2ogr命令来转换成SQLite格式。

还需要要注意,在点状文件中,已经设置了标注,不要再设置符号, 因为可能会被挡住。

绘制点状要素

最后来了解一下点状要素的绘制。

点是最简单的要素,为什么要放到最后来说呢。 主要是因为点的显示除了样式之外,文本标记的配合显示也比较独特。 关于点要素及其显示方式,只能用简约而不简单来形容了。

使用Mapnik绘制点有两种方式: PointSymbolizer允许你在给定的点上绘制图像, ShieldSymbolizer将图像和文本标记结合起来产生一个“盾”。 现在分别来看一下是如何使用的。

PointSymbolizer

PointSymbolizer能在点上绘制图片,默认的构造函数没有提供参数,并显示每一个点作为一个 4 × 4 像元的黑色方点:

In [ ]:
symbolizer = PointSymbolizer()

image1

你可以为图片文件提供名称、类型、尺寸,PointSymbolizer将使用这些特征绘制每个点。

In [ ]:
symbolizer = PointSymbolizer("point.png", "png", 9, 9)

需要知道的是PointSymbolizer绘制影像会覆盖到你所想要的点上。 可能需要在影片的周围留白(或者透明) [1], 这样影像中你想要的部分就会出现在你想要的点上。 例如,如果你希望在一个确切的位置绘制一根针脚,你可能需要设置格式,如下图所示:

额外的(透明的)空白确保针脚的点位于图像的中央,使影像正好在地图所需的位置上。

无论你是否提供影像,PointSymbolizer都有两个用来修正行为的属性:

  • symbolizer.allow_overlap = True:如果你将这个属性设置成True,即使影像重叠,所有的点都将会被绘制。缺省情况下不会重叠。
  • symbolizer.opacity = 0.75:透明或者不透明怎样来绘制影像。值为0.0,则绘制的影像是完全透明的,而值为1.0(默认情况下),则绘制的影像是完全不透明的。

ShieldSymbolizer

ShieldSymbolizer允许你绘制标记,并将文本与图片结合。 在本章节中,我们将会着眼于ShieldSymbolizer绘制点。 ShieldSymbolize绘制的是一个和文本标记相关的影像:

ShieldSymbolizerTextSymbolizerPointSymbolizer在呈现相同的数据时具有相同的工作方式。 仅有的不同就是ShieldSymbolizer确保文本和影像呈现在一起; 你永远也得不到一个没有文本的影像,或者反之亦然。 当你创建一个ShieldSymbolizer,你需要提供较多的参数。

In [ ]:
symbolizer = mapnik.ShieldSymbolizer( fieldName, font, fontSize, color, imageFile, imageFormat, imageWidth, imageHeight)

其中:

  • fieldName是作为文本字段所要显示的字段或者属性的名称。
  • font是绘制文本所使用的字型名称。
  • fontSize是文本的尺寸的,以点数为单位。
  • color是Mapnik颜色对象定义用来绘制文本的颜色。
  • imageFile是掌握显示影像的文件的名字(和可选的路径)
  • imageFormat是定义图像文件格式的字符串(PNG or TIFF)
  • imageWidth是该影像文件的宽度,以像元为单位。
  • imageHeight是影像文件的高度,以像元为单位。

因为ShieldSymbolizerTextSymbolizer的一个子类, 所有TextSymbolizer可以利用的定位和格式的选项同样对ShieldSymbolizer也有用。 由于,他也绘制一个影像,一个ShieldSymbolizer同样拥有PointSymbolizeallow_overlap和opacity属性。

请注意你可能需要调用ShieldSymbolizerdisplacement()方法来正确的定位文本, 若在默认情况下文本直接出现在点上,即图像的中央。

In [ ]:
#!/usr/bin/env python

import os
import mapnik

stylesheet = '/gdata/world_map_shield.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')

# bbox = mapnik.Box2d(118, 36.6, 124.6, 40.7)
bbox = mapnik.Box2d(70, 20, 135, 57)
m.zoom_to_box(bbox)

mapnik.render_to_file(m, 'xx_shield_lz.png', 'png')

使用TextSymbolizer来绘制点符号

这里要介绍一种变通的技巧,直接使用TextSymbolizer来绘制点符号。

在前面两节中,我们介绍了使用PointSymbolizerShieldSymbolizer两种方式对点状要素进行绘制的方法, 除了默认的情况,都要求使用图片来绘制点状符号。除去分辨率的因素,由于需要指定图片的大小, 在更换图片或是需要更换出图的大小时,都有一个重新设置的问题。

考虑一下前面提到过的标注,使用字体的时候,只需要指定字体大小(按一定的规范), 则会自动计算产生精美的结果。也可以使用标注的方式来绘制点符号。

前提是要有相应的字库。这里我选择使用的是ESRI公司在ArcGIS软件中携带的字体 [2]。

使用SpatiaLite数据库文件,对表进行更新: alter table dxt25w_E49C001001_respt_point add sty char(1);
-- 村 update dxt25w_E49C001001_respt_point set sty='(' where gb=31091; -- 乡 update dxt25w_E49C001001_respt_point set sty='5' where gb=31090; -- 镇 update dxt25w_E49C001001_respt_point set sty='R' where gb=31080; -- 区县 update dxt25w_E49C001001_respt_point set sty='H' where gb=31060 or gb=31061; -- 县 update dxt25w_E49C001001_respt_point set sty='P' where gb=31050; -- 市 update dxt25w_E49C001001_respt_point set sty='Q' where gb=31030; -- 地区盟 update dxt25w_E49C001001_respt_point set sty='O' where gb=31040;

将字体设置为ESRI Default Marker Regular,即可产生相应的效果。

[1] 使用图像处理软件可以做到这一点,如开源图像处理软件 GIMP

[2] 这个涉及到版权问题,使用时请慎重

绘制栅格影像

GDAL和栅格数据源允许你包含一幅地图上的栅格影像。有了数据源,就可以使用 RasterSymbolizer 将其显示在地图的图层上,如下图所示:

image1

创建一个RasterSymbolizer是非常简单,如上面第4行所示。 RasterSymbolizer把图层中的栅格文件数据源中的内容自动绘制到地图上,RasterSymbolizer 支持以下用来控制显示栅格数据的选项:

symbolizer.opacity 控制栅格数据的透明度,值为 0.0 ,则影像完全透明, 值为 1.0,则影像完全不透明。在默认情况下,栅格影像是不透明的。

symbolizer.mode 属性告诉RasterSymbolizer怎样结合光栅数据呈现地图。 这些模式与在图形编辑器中显示图层的方式相似,例如Photoshop或者GIMP。 支持以下合并模式:

image2

symbolizer.scaling 可以用来控制光栅图像的算法。 可用的选项是:fast(采用最近邻居算法), bilinear(在所有四个颜色通道中使用双线性插值法), 和bilinear8(仅仅为一个颜色通道使用双线性插值法)。

注意:Mapnik目前不支持动态的栅格数据的重投影, 如果你生成一幅地图使用一个不同于栅格数据投影的投影方式, 你需要在它显示之前对栅格数据进行重新投影,例如,通过使用 gdalwarp工具。

RasterSymbolizer的主要用途是展现shaded relief背景,如上面的图所显示的。这能给读者留下一个地形的基本印象。

前面的图像是使用来自国家高程数据集的一个Digital Elevation Map(DEM文件格式)的数据文件创建的。该文件是使用 Hillshade选项gdaldem应用程序处理的来创建一幅地貌晕渲的灰度图像。 这个图像是使用RasterSymbolizer设置成hard_light模式进行显示的, 平铺在由GSHHS shoreline database定义的海岸线的浅绿色背景之上。

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

import mapnik
# from helper import get_tmp_file

datasource = mapnik.Gdal(file='/gdata/lu75c.tif')
layer = mapnik.Layer("myLayer")
layer.datasource = datasource
layer.styles.append("myLayerStyle")
symbol = mapnik.RasterSymbolizer()

s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.RasterSymbolizer())
s.rules.append(r)

m = mapnik.Map(600, 300, "+proj=latlong +datum=WGS84")
m.background = mapnik.Color('steelblue')

rule = mapnik.Rule()
rule.symbols.append(symbol)
style = mapnik.Style()
style.rules.append(rule)

m.append_style('My Style', s)

# lyr = mapnik.Layer('world',"+proj=latlong +datum=WGS84")
# lyr.datasource = mapnik.Shapefile(file='gdata/world_borders.shp')
layer.styles.append('My Style')

m.layers.append(layer)

print(layer.envelope())
m.zoom_to_box(layer.envelope())
mapnik.render_to_file(m, 'xx_raster.png', 'png')


In [ ]:
import helper; helper.info()