数据显示的规则

正如我们在前面看到的,Mapnik使用rules来指定特定的symbolizers将会被用来实现一个特定的功能。 rules组合在一起形成style,不同的样式将会被添加到同一个地图中,当你设置Layer时,则会由名称提及。 在本节中,我们将会探究规则、过滤器 和样式之间的关系,然后了解一下可以对Mapnik的类别做些什么。

让我们仔细看一看Mapnik的Rule类别。一个Mapnik的rule包括两个部分的列表:conditions,symbolizers。

如果rule的条件满足,则symbolizers将会在地图上绘制匹配的要素。

rule支持三种类型的条件:

  • 一个Mapnik的filter可以被用来指定表达式,如果要被绘制,则要素必须满足该表达式。
  • rule本身可以指定必须满足的最大最小比例尺分母。这可以用来设置给定比例尺的地图的rules。
  • rule可以有其他的条件,这就意味着如果在style中没有其他rule满足其条件,则该rule将只会使用该style。

如果rule的所有条件都满足,则symbolizers的关联列表会将该要素呈现到地图上。

让我们更仔细的了解一下这些条件。

Filters

使用Mapnik的Filter()构造函数的一个参数: 一个字符串定义了一个表达式,如果rule被应用,则该要素必须与表达式匹配。 然后,将返回字段对象存储到规则的filter的属性表中。

rule.filter = mapnik.Filter("...")

让我们研究一个非常简单的过滤表达式,将一个字段或属性表与一个特定的值做一下对照比较。

filter = mapnik.Filter("[level] = 1")

字符串值可以通过在值周围放置单引号来进行比较,例如:

filter = mapnik.Filter("[type] = 'CITY'")

注意:字段名称和值,对大小写都是敏感的,所以你必须用方括号包围字段和属性名称。

当然,简单的字段的值进行比较是你能做的最基本的比较方式。 过滤表达式有其强大而灵活的语法来定义其条件,在概念上类似于一个SQL where表达式。下面的句法图描述的就是所有写入过滤表达式字符串的选项。

image1

Mapnik的过滤器还支持空间操作符,如 Equals,Disjoint,Touches,Within, Overlaps,Crosses,Intersects,Contains,DWithin,Beyond和 BBOX。

按比例尺显示要素

观察下面的两幅地图:

image2

由上面的地图可以发现,在世界地图上绘制街道并没有点。 同样,在地图上显示的国家轮廓范围过大以至于不能为独立的城绘制详细的海岸线。 但是,如果你的应用程序允许用户从世界地图下至单独的街道进行放大, 你会需要一组Mapnik的样式来生成地图,而不需要他绘制的比例尺。 基于地图的比例尺分母,Mapnik可以通过选择性的显示要素让你做到这一点。 如果你用 1:100,000 的比例尺在纸上打印一幅地图, 那么比例尺分母就是冒号后面的数字(在这种情况下100000)。 数字化绘制地图会使这一点更加复杂,但是基本原理仍然是相同的。Mapnik的 rule 可以有一个与之相关的最大或最小的比例尺分母值。

rule.min_scale = 10000 rule.max_scale = 100000

如果最小最大比例尺的分母已经设置,则该rule将会被运用;如果地图的比例尺在这个范围内, 你也可以将最大最小比例尺因子应用到整个图层中:

layer.minzoom = 1.0/100000 layer.maxzoom = 1.0/200000

注意:此处使用的是 1.0,而不是 1

需要注意的是,rules使用的是比例尺分母, 而图层使用的是比例尺因子。两者的关系相当混乱,并不直接。

只有当整个地图的现有比例尺因素在这个范围内,整个图层才会被显示出来。 如果你有一个数据源,这将会是非常有用的,当以一定的比例尺显示地图时, 该数据源将会被应用,例如,当用户放大时,只能使用高分辨率海岸线数据。

比例尺分母可以直观的使用。例如,比例尺分母值为20,000,则代表一个地图 大约是用1:20,000的比例尺绘制的。但是,这仅仅是个近似比例尺;比例尺分母的 实际计算需要考虑两个重要因素:

  1. 因为Mapnik作为一个位图图像呈现在地图上, 图像内各个像元的大小发挥作用, 由于位图图像能用不同像元的大小显示在不同的计算机屏幕上, Mapnik使用一个被地理空间信息联盟定义的 tandardized rendering pixel size来定义像元将是多大。 这个值为0.28mm,大约是现代显示屏上一个像元的大小。

  2. 使用地图投影对于比例尺分母的计算有很大影响。 地图投影总是歪曲真实的距离,投影在赤道比较准确,可是越接近两级就越不准确。

使用Mapnik的公式来计算比例尺分母是相当的复杂。 只要求Mapnik为我们计算比例尺分母和比例尺因子是比较容易的。

import mapnik map = mapnik.Map(width, height, projection) map.zoom_to_box(bounds) print (map.scale_denominator(), map.scale())

然后你可以放大地图到你想要的尺寸,看一看它是什么比例尺因子和比例尺分母,然后你就可以插入到你的style中来确定选择哪些要素显示到一个给定比例尺的地图中。

“Else” 规则

想象一下将用一个颜色绘制很多要素,其他的面要素用一种不同的颜色。实现它的方法就是使用Mapnik rules,如下图所示:

rule1.filter = mapnik.Filter("[level] = 1") ... rule2.filter = mapnik.Filter("[level] != 1")

对于一个简单的过滤公式这种方法刚刚好,但是当公式变得更加复杂的时候,使用一个“else” rules将会变得更加简单,如下面的代码片段所示:

rule1.filter = mapnik.Filter("[level] = 1") ... rule2.set_else(True)

如果你为一个 rule调用set_else(True),则该rule可以被使用, 当且仅当,在同一个style中没有其它rule拥有满足它的过滤条件。

如果你有大量的过滤条件,其他的规则将会特别有用, 并且在结束时想要一个包罗万象的rule,如果没有其他的rule能够被用来绘制地图的要素, 则该规则将会被运用。例如:

rule1.filter = mapnik.Filter("[type] = 'city'") rule2.filter = mapnik.Filter("[type] = 'town'") rule3.filter = mapnik.Filter("[type] = 'village'") rule4.filter.set_else(True)

实例

In [2]:
# -*- coding: utf-8 -*-
import os

import mapnik
# from helper import get_tmp_file


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


polygon_symbolizer2.fill = mapnik.Color('#ff0000')

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


# polygon_symbolizer = mapnik.PolygonSymbolizer(mapnik.Color('blue'))
r.symbols.append(polygon_symbolizer)
r2.symbols.append(polygon_symbolizer2)
# line_symbolizer = mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1)

# r.filter = mapnik.Filter("[NAME] = 'China'")
r.filter = mapnik.Expression("[NAME] = 'China'")
r2.filter = mapnik.Expression("[NAME] = 'Mongolia'")
r3.set_else(True)

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)
r2.symbols.append(line_symbolizer)
r3.symbols.append(line_symbolizer)
s.rules.append(r)
s.rules.append(r2)
s.rules.append(r3)
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(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_filter.png', 'png')
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-2-a07db542122f> in <module>()
     58 m.zoom_to_box(bbox)
     59 # mapnik.render_to_file(m, 'xx_world_fk.png', 'png')
---> 60 mapnik.render_to_file(m, 'xx_filter.png', 'png')

RuntimeError: Shape Plugin: no attribute 'NAME' in '/gdata/GSHHS_c'. Valid attributes are: id,level,source,parent_id,sibling_id,area,Shape_Leng,Shape_Area.

In [ ]: