>>> from env_helper import info; info()
页面更新时间: 2024-01-13 11:38:52
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

9.7. 绘制标注

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

9.7.1. 文本标记的基本用法

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

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

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

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

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

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

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

symbolizer.opacity = 0.5

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

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

虽然但是, 这里使用 XML 文件作为数据源:

>>> import mapnik
>>> import os
>>>
>>> stylesheet = 'test_map_label_point.xml'
>>>
>>> m = mapnik.Map(600, 200)
>>> mapnik.load_map(m, stylesheet)
>>>
>>> m.zoom_all()
>>> env = m.envelope()
>>> print(env)
>>> box = mapnik.Box2d(env.minx - .2, env.miny - .2, env.maxx + .2, env.maxy + .2)
>>> m.zoom_to_box(box)
>>>
>>> mapnik.render_to_file(m,'xx_map_label_point.png')
Box2d(3.0,1.0,18.0,6.0)

结果如下:

Mapnik多边形要素点状标注实例

图 9.9 Mapnik多边形要素点状标注实例

查看这个 XML 文件:

>>> !cat test_map_label_point.xml
<Map background-color="#ffffff" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
    <Style name="My Style">
        <Rule>
            <PolygonSymbolizer fill="#efefef"/>
            <LineSymbolizer stroke="rgb(50%,50%,50%)" stroke-width="1" stroke-dasharray="8,20"/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="10" fill="black"
                            placement="point" allow-overlap="false">[name]
            </TextSymbolizer>
        </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/fig_data/fig_data_poly.shp</Parameter>
        </Datasource>
    </Layer>
</Map>

当使用 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

9.7.2. 使用文本标记的“字晕”

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

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

9.7.3. 调整文本的位置

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

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

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

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

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

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

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

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

下面这个例子展示了几种不同的标准方式:

>>> import os
>>> import mapnik
>>>
>>> stylesheet = 'test_fig_label_position.xml'
>>>
>>> # image = 'xx_world_style_from_xml.png'
>>> m = mapnik.Map(600, 150)
>>> 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(-1, -1, 8, 1)
>>> m.zoom_to_box(bbox)
>>> mapnik.render_to_file(m, 'xx_fig_label_position.png')

以下为渲染的结果 :

Mapnik中点状要素不同标注方式

图 9.10 Mapnik中点状要素不同标注方式

查看这个 XML 文件:

>>> !cat test_fig_label_position.xml
<Map background-color="#efefef" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
    <Style name="My Style">
        <Rule>
            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" placement="point"
                            allow-overlap="true">[name]
            </TextSymbolizer>
            <Filter>[id] = 1</Filter>
        </Rule>
        <Rule>
            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" placement="point"
                            allow-overlap="true" vertical-alignment="MIDDLE">[name]
            </TextSymbolizer>
            <Filter>[id] = 2</Filter>
        </Rule>
        <Rule>
            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" placement="point"
                            allow-overlap="true" vertical-alignment="bottom"
                            horizontal-alignment="right" justify-alignment="right">[name]
            </TextSymbolizer>
            <Filter>[id] = 3</Filter>
        </Rule>
        <Rule>
            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" placement="point"
                            allow-overlap="true" dx="2" dy="25" character-spacing="3">[name]
            </TextSymbolizer>
            <Filter>[id] = 4</Filter>
        </Rule>
        <Rule>
            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" placement="point"
                            allow-overlap="true" vertical-alignment="BOTTOM" orientation="45">[name]
            </TextSymbolizer>
            <Filter>[id] = 5</Filter>
        </Rule>
        <Rule>

            <PointSymbolizer/>
            <TextSymbolizer face-name="DejaVu Sans Book" size="20" halo-fill="red" halo-radius="2"
                            placement="point" allow-overlap="true">[name]
            </TextSymbolizer>
            <Filter>[id] = 6</Filter>
        </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/fig_data/fig_data_pt.shp</Parameter>
        </Datasource>
    </Layer>
</Map>

9.7.4. 文本的高级设置

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

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

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

9.7.5. 字体

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

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

>>> import mapnik
>>> for font in mapnik.FontEngine.face_names():
>>>     print (font)
Bitstream Vera Sans Bold
Bitstream Vera Sans Bold Oblique
Bitstream Vera Sans Mono Bold
Bitstream Vera Sans Mono Bold Oblique
Bitstream Vera Sans Mono Oblique
Bitstream Vera Sans Mono Roman
Bitstream Vera Sans Oblique
Bitstream Vera Sans Roman
Bitstream Vera Serif Bold
Bitstream Vera Serif Roman
C059 Bold
C059 Bold Italic
C059 Italic
C059 Roman
D050000L Regular
DejaVu Math TeX Gyre Regular
DejaVu Sans Bold
DejaVu Sans Bold Oblique
DejaVu Sans Book
DejaVu Sans Condensed
DejaVu Sans Condensed Bold
DejaVu Sans Condensed Bold Oblique
DejaVu Sans Condensed Oblique
DejaVu Sans ExtraLight
DejaVu Sans Mono Bold
DejaVu Sans Mono Bold Oblique
DejaVu Sans Mono Book
DejaVu Sans Mono Oblique
DejaVu Sans Oblique
DejaVu Serif Bold
DejaVu Serif Bold Italic
DejaVu Serif Book
DejaVu Serif Condensed
DejaVu Serif Condensed Bold
DejaVu Serif Condensed Bold Italic
DejaVu Serif Condensed Italic
DejaVu Serif Italic
Droid Sans Fallback Regular
FontAwesome Regular
GLYPHICONS Halflings Regular
Lato Black
Lato Black Italic
Lato Bold
Lato Bold Italic
Lato Hairline
Lato Hairline Italic
Lato Heavy
Lato Heavy Italic
Lato Italic
Lato Light
Lato Light Italic
Lato Medium
Lato Medium Italic
Lato Regular
Lato Semibold
Lato Semibold Italic
Lato Thin
Lato Thin Italic
Liberation Mono Bold
Liberation Mono Bold Italic
Liberation Mono Italic
Liberation Mono Regular
Liberation Sans Bold
Liberation Sans Bold Italic
Liberation Sans Italic
Liberation Sans Regular
Liberation Serif Bold
Liberation Serif Bold Italic
Liberation Serif Italic
Liberation Serif Regular
Nimbus Mono PS Bold
Nimbus Mono PS Bold Italic
Nimbus Mono PS Italic
Nimbus Mono PS Regular
Nimbus Roman Bold
Nimbus Roman Bold Italic
Nimbus Roman Italic
Nimbus Roman Regular
Nimbus Sans Bold
Nimbus Sans Bold Italic
Nimbus Sans Italic
Nimbus Sans Narrow Bold
Nimbus Sans Narrow Bold Oblique
Nimbus Sans Narrow Oblique
Nimbus Sans Narrow Regular
Nimbus Sans Regular
Noto Color Emoji Regular
Noto Mono Regular
Noto Sans Mono Bold
Noto Sans Mono Regular
P052 Bold
P052 Bold Italic
P052 Italic
P052 Roman
Standard Symbols PS Regular
URW Bookman Demi
URW Bookman Demi Italic
URW Bookman Light
URW Bookman Light Italic
URW Gothic Book
URW Gothic Book Oblique
URW Gothic Demi
URW Gothic Demi Oblique
WenQuanYi Micro Hei Mono Regular
WenQuanYi Micro Hei Regular
WenQuanYi Zen Hei Mono Regular
WenQuanYi Zen Hei Regular
WenQuanYi Zen Hei Sharp Regular
Z003 Medium Italic
cmex10 LyX
cmmi10 LyX
cmr10 LyX
cmsy10 LyX
dsrom10 LyX
esint10 LyX
eufm10 LyX
msam10 LyX
msbm10 LyX
rsfs10 LyX
stmary10 LyX
wasy10 LyX

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

中文标注的问题

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