Filter是一中语言,一种XML实现的语言,这就使得它非常适合于分布式系统。我们接触过的OWS服务都不同程度的使用到它。SLD用它来实现复杂的Rule选择。WFS在所有需要定位操作对象的地方都会使用Filter。这一章我们换一种讨论的方式,我不想再罗列乏味的Tag,然后加上翻译过来的解说。我们应该实实在在的实现一个程序,在实现的过程中完成我们的讨论。
前面的章节我们一直维护着一个WMS服务器“ShapeMapServer”,刚开始我并没有把它当回事,我只是想把它作为一个范例来使用,但是随着话题的深入,这个范例也注定逐渐复杂,功能也慢慢丰富起来。这一章我们将一起来面对这个程序,我们一起来为他增加功能,通过编码我们将对Filter有所体会。我们的任务是改进服务器处理SLD文件的能力,我们将为它增加Filter功能。
先来简单介绍一下OGC的Filter。Filter的作用是构建一个表达式,返回值就是Feature的集合,换句话说Filter就如他的名字一般为我们从一个集合中过滤出一个满足我们要求的子集。而过滤的方法就是Filter定义的操作符。Filter定义了三种操作符:地理操作符(Spatial operators),比较操作符(Comparison operators)和逻辑操作符(Logical operators)。我们来一一解释。
Spatial operators定义了地理属性的操作方式,他们有:Equals,Disjoint,Touches,Within,Overlaps,Crosses,Intersects,Contains,DWithin,
Beyond,BBOX。Comparison operators定义了标量属性的操作方式,他们有:PropertyIsEqualTo,PropertyIsNotEqualTo,PropertyIsLessThan,PropertyIsGreaterThan,
PropertyIsLessThanOrEq,PropertyIsGreaterThanO,PropertyIsLike,PropertyIsNull,
PropertyIsBetween。Logical operators定义了组合这些操作的方式,他们有:And,Or,Not。我们用一个例子来展示他们的作用。假设我们想获得所有人口在一千万以上,并且在我们给定的范围内的城市,我们要这样来构建我们的Filter:
<And>
<PropertyIsGreaterThan>
<PropertyName>population</PropertyName>
<Literal>10000000</Literal>
</PropertyIsGreaterThan>
<BBOX>
<PropertyName>geom</PropertyName>
<Envelope srsName="EPSG:4326">
<lowerCorner>-180 -90</lowerCorner>
<upperCorner>180 90</upperCorner>
</Envelope>
</BBOX>
</And>
</Filter>
Filter就像SQL的where子句,事实上如果你要实现一个强大的WFS服务器,你就不得不提供将Filter翻译成SQL的功能。
现在我们回到我们的任务上来,实现支持Filter的Style。为了能够在一个章节的时间里完成这个任务,我们首先得划定工作范围,也就是需求:
1、 支持所有的操作符;
2、 提供两个函数,sin和cos;
需求很简单,任务却很繁重,我们首先需要增加测试框架。一直以来我们的程序都在“裸奔”而且也不算坏,但是随着结构开始复杂,这种状态就很难维持了,我们需要回归测试。于是我选择NUnit来做这个工作。我并不是要用时下很流行的“TDD”来完成开发,实际上我很喜欢TDD,但是我从来也没有真真掌握它。所以我们只用自动测试框架来测试,仅此而已。
经过一番分析,这个过程是在我的脑海里完成的,我觉得有必要设计一个叫做“StyledLayer”的类,它是从“SharpMap.Layers.Layer”派生来的。这个Layer负责按照Style定义的方式渲染Feature。我们在Style之谜中已经看到Style是由Rule组成的。每一个Rule都有一个Symbolizer代表渲染方式,至多一个Filter来决定哪些Feature将使用这个Rule,还有至多一对Zoom限制来决定这个Rule是否被选择。以前我们一直忽略Rule的这些特性,仅仅使用了Symbolizer,现在我们必须面对它们了。SLD标准文档告诉我们,一个SLD数据块包含多个Rule,每个Feature在渲染的时候都需要通过这些Rule,只要满足Rule的条件,Feature就应该按照Rule定义的Symbolizer渲染一次。顺序与Rule的顺序一致。Rule还可以通过定义ElseFilter来增加分支,整体的过程就是这样了。
我们可以用伪代码来描述上述过程:
for feature in features
symbolizer = rule.Test(feature)
if symbolizer then
symbolizer.Render(feature)
endif
endfor
endfor
这段代码对我们的设计将有一定的指导意义,现在让我们把StyledLayer放一放,先来看看Rule。Rule无疑是最重要的概念之一,有必要为它定义一个类,方法就是Test(feature:Feature):Symbolizer。然后是Symbolizer,它的方法是Render(feature:Feature)。Filter呢,我们一直在讨论的Filter并没有出现在这里。Filter将躲在Rule的幕后起作用。Rule的Test方法将会用到SLD文件中定义的Filter表达式。我们现在来看看Test方法的伪代码:
return symbolizer
else if elseFilter.Test(feature) then
return elseSymbolizer
显然Filter也应该是一个类,而且它也有一个叫Test的方法。除了这点我们还需要注意ElseFilter在这里是如何起作用的,这个设计不是很好,我们修改一下:
return symbolizer
return elseRule.Test(feature)
这一次感觉好多了。我在这里提到“感觉”是因为我也说不清为什么这个比上一个好。现在所有基本的设计就完成了。我们有了三个类Rule,Symbolizer和Filter。而且我们已经实现了Rule的方法Test。但是我们还没有实现Filter的Test方法(Symbolizer的设计是我以前就已经完成的,就不再赘述了)。我们知道有三种Filter操作符,每种还有自己的子类操作符。我们可以这样来实现Filter的Test:
switch op:
case And:…
case Or:…
case Not:…
case…
…
好了我不开玩笑了。毫无疑问,稍微有点OO常识的人都知道Filter其实是一个抽象概念,我们在这里将他定义成接口:IFilter。而他的实现类分别是ComparisonOp,SpatialOp,LogicOp。他们也是抽象概念,就是Abstract类。他们再分别派生出具体的操作符类。
这三张大图占了不少空间,但是也说明了不少问题。
现在来看看另外一个概念Expression。想像一下,如果我们希望比较的是城市男女的比例而不是男人或女人的数量该怎样办,我们需要先从男女数量来计算比例,然后再比较。OGC的标准为我们提供了计算表达式(Expression)来解决这个问题。它包括:Add,Sub,Mul,Div和Function。显然这个Expression也是一个抽象概念,于是我们有了另外一个接口:IExpression。
上图也说明了我们的设计。到此所有Filter的设计算是完成了。我们也可以据此开始编码,虽然还不能在我们的服务器中运行,但是可以在测试框架下检查效果。
下面我们需要通过XML文件生成这套Rule的方法。我们将用到工厂类(Factory)来完成这个工作。感谢DotNet提供的反射(Reflect)机制,我们的工厂类可以很容易的与Xml的Tag关联起来,大大简化了代码。由于这不是本章的重点,我就不在这里详细描述了。
实现了从Xml创建Rule我们的任务就基本完成了。现在我们可以来看看效果了。
下图是原来的渲染效果,它只是使用了一个Symbolizer来渲染线段,这里是SLD文件。
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
下图是新的渲染效果,我们希望“Amazon”流域的所有河流除了原来的效果外还用蓝色的粗线渲染一次,这里是SLD文件
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)