在"
OGC之路(1)WMS总结
"中我们讨论了WMS标准。我们可以从WMS服务器很方便的获得指定区域内的地图,但是我们只能获得渲染后的地图。有时候我们希望获得指定图层的Feature数据包括地理坐标和属性,更进一步,当我们需要修改数据源的数据的时候,WMS就不能满足需要了。OGC为我们提供了另外一个标准Web Feature Service(WFS)来对应以上需求。顾名思义,WFS是通过网络操作Feature的服务。它支持客户端对服务器的Feature执行INSERT, UPDATE, DELETE, LOCK, QUERY,DISCOVERY操作。乍一看感觉WFS像是一个数据库。那么是不是还有类似于SQL的东东呢,确实是这样,关于这个主题我们会在随后讨论。让我们先从一些基本概念开始。
先来说说Feature与FeatureType。我们说过Feature是对现实抽象的基本元素。它把现实物体抽象为属性数据和地理数据,然后再加上一个Id来标识。许多同类的Feature往往会要求统一处理,对他们分组就显得有必要了。于是Layer出现了,一个Layer就是一组具有相同属性结构的Feature(这是我的理解)。很多系统中同一个Layer里面的Feature还必须具有相同的几何类型,例如都是Point或者都是Polygon。Layer还有一个的Style,这样Feature就会以一致的方式渲染。
Feature的属性范围很宽,几乎没有限制,只要能和它代表的现实物体产生联系的数据,包括地理数据,都可以作为属性。实际上,我见过的几乎所有GIS系统都把一个Feature对应到一条数据库记录上,在C#里这往往表现为DataRow对象的实例。把Layer对应到数据库表(视图)上,在C#里这往往表现为DataTable对象的实例。于是我们会发现GIS数据的持久化依然是一个古老的主题"ORM"。那么对Feature的检索是不是也类似数据库的检索呢。差不多吧,一般,企业级的GIS应用,Feature的属性数据和地理数据都保存在数据库里。对他们的操作也就是一大串的SQL。有些系统的地理数据访问有专门的中间件来辅助,目前几乎所有的商用数据库都自己提供了GIS数据访问支持,看来GIS很快就会成为Web应用的标配了。OGC有一个"
简单对象访问协议
",里面有很完整的关于GIS
对象建模
和
数据库建模
的设计指导,是相当不错的学习资料。
那FeatureType是什么呢,我们可以用类比的方式来描述:用OO语言中类型(Type)和实例(Instance)的关系来类比,FeatureType是Feature的类型,Feature是FeatureType的实例。
从数据库获取数据我们会使用SQL,我们需要告诉数据库服务器我们需要哪些表的哪些数据,"select name from peoples where age > 30",这条语句的含义相信大家一看就明白了。从WFS服务器获得数据时我们面临同样的问题,我们需要告诉服务器我们希望得到哪些FeatureType的哪些Feature。具体的格式我们会在介绍GetFeature时讨论,这里需要介绍一个很重要的概念Filter。在前一章我们已经使用过Filter,当时并没有过多纠缠,但是我们已经知道Filter是一种语言,就像SQL一样Filter也提供了表达式,函数等语法与扩展来帮助构建查询。我们可以把Filter看做SQL语句中"where"的部分。
WFS定义了如下方法:GetCapabilities,DescribeFeatureType,GetFeature, LockFeature(可选),Transaction(可选)。
-
GetCapabilities。
WMS有"GetCapabilities",WFS也有"GetCapabilities",将来我们还会看到WCS也提供"GetCapabilities"。而且他们的意义都是一样的,返回服务器的Capability。OGC在定义各种标准时经常会遇到这种情况,本来在一个标准中使用的概念在其他的标准中同样会用到,为了不违反DRY原则,专家们抽象出一些标准来描述基础操作,相应的,先前的标准就成了抽象标准的派生物。是不是有点OO的感觉:)。这里我们就会遇到一个抽象标准OGC Web Services Common Specification(OWS)。GetCapabilities就是OWS的主要方法。派生标准会重写这个方法,WMS的GetCapabilities我们已经很熟悉了,现在我们来看看WFS的GetCapabilities。
WFS的GetCapabilities的调用格式与WMS差不多,
http://localhost:8080/geoserver/ows?service=WFS&request=GetCapabilities
,但是返回数据却有很大区别。由于数据太多我就不把Capability数据块贴上来了。
我们看到了这样几个节点:ServiceIdentification,ServiceProvider,OperationsMetadata,FeatureTypeList和Filter_Capabilities。前三个节点是OWS定义的,后面两个是WFS追加的。如果你一直从第一章看过来,那你应该很熟悉这种结构了。这些节点的自描述能力很好,从字面上我们就能看出他们的作用。再对比他们的内容,也就八九不离十了。跳过前三个节点,目前他们对我们用处不大。
我们来看节点FeatureTypeList。很清楚,这个节点包含服务器发布的FeatureType(FeatureType节点)。以及对这些FeatureType的操作(Operations节点)。FeatureType都有一个Name节点,我们可以用它的值来指定FeatureType。DefaultSRS节点告诉我FeatureType在使用哪个坐标系。OutputFormats告诉我们FeatureType用哪些格式返回数据。WGS84BoundingBox就是这个FeatureType的范围的经纬度。
另外一个值得关注的就是Filter_Capabilities节点。这个节点告诉我们服务器对Filter的支持情况。
其实在WFS标准里面,Capability数据块的结构还要复杂得多。目前我们了解这些也就足够了。有了这些数据我们就知道我们该如何访问一个WFS服务器。
-
DescribeFeatureType
在操作数据库的时候,我们有时需要反射数据库的结构,在ADO.NET里我们会使用函数"GetSchema"来获得一个描述了数据库元信息的XML数据块。在使用WFS的时候我们也存在同样问题。有时候我们需要知道某个FeatureType有哪些属性以及分别是什么类型,这时我们就需要DescribeFeatureType方法。一个典型的DescribeFeatureType调用是这样的:
http://localhost:8080/geoserver/ows?service=WFS&request=DescribeFeatureType&TypeName=states
。返回值是这样的
Code
<?
xml version="1.0" encoding="UTF-8"
?>
<
xsd:schema xmlns:xsd
="http://www.w3.org/2001/XMLSchema" xmlns:gml
="http://www.opengis.net/gml" xmlns:topp
="http://www.openplans.org/topp" elementFormDefault
="qualified" targetNamespace
="http://www.openplans.org/topp"
>
<
xsd:import namespace
="http://www.opengis.net/gml" schemaLocation
="http://localhost:8080/geoserver/schemas/gml/3.1.1/base/gml.xsd"
/>
<
xsd:complexType name
="statesType"
>
<
xsd:complexContent
>
<
xsd:extension base
="gml:AbstractFeatureType"
>
<
xsd:sequence
>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="the_geom" nillable
="true" type
="gml:MultiSurfacePropertyType"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="STATE_NAME" nillable
="true" type
="xsd:string"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="STATE_FIPS" nillable
="true" type
="xsd:string"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="SUB_REGION" nillable
="true" type
="xsd:string"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="STATE_ABBR" nillable
="true" type
="xsd:string"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="LAND_KM" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="WATER_KM" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="PERSONS" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="FAMILIES" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="HOUSHOLD" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="MALE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="FEMALE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="WORKERS" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="DRVALONE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="CARPOOL" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="PUBTRANS" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="EMPLOYED" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="UNEMPLOY" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="SERVICE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="MANUAL" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="P_MALE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="P_FEMALE" nillable
="true" type
="xsd:double"
/>
<
xsd:element maxOccurs
="1" minOccurs
="0" name
="SAMP_POP" nillable
="true" type
="xsd:double"
/>
</
xsd:sequence
>
</
xsd:extension
>
</
xsd:complexContent
>
</
xsd:complexType
>
<
xsd:element name
="states" substitutionGroup
="gml:_Feature" type
="topp:statesType"
/>
</
xsd:schema
>
。这个数据块携带FeatureType的结构信息。为以后的访问提供了支持。我们要注意一个叫"the_geom"的属性它的类型是"gml:MultiSurfacePropertyType",这就是Feature的地理数据的属性名,我们可以通过这个名称来获得Feature的地理数据。WFS用这种方法来统一数据的访问形式,避免了需要特殊处理的情况。
-
GetFeature
终于到了这个方法,这个方法可以说是WFS的基础,它的用途一目了然,获得Feature。如果你敲入这个调用:
http://localhost:8080/geoserver/ows?service=WFS&request=GetFeature&TypeName=topp:states
。那么你会得到一个很大的xml文档,实际上如果你的服务器发布了一个真实的应用,建议你千万不要这么干。这个调用意味着返回topp:states的所有数据。就和"select * from topp:states"的意思差不多。我们来改造一下这个调用:
http://localhost:8080/geoserver/ows?service=WFS&request=GetFeature&TypeName=topp:states&FeatureId=states.3
。现在好多了,我们看到它返回的数据是这样的
Code
<
wfs:FeatureCollection numberOfFeatures
="1" timeStamp
="2009-10-13T00:14:43.515+08:00" xsi:schemaLocation
="http://www.openplans.org/topp http://localhost:8080/geoserver/wfs?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=topp:states http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd" xmlns:ogc
="http://www.opengis.net/ogc" xmlns:tiger
="http://www.census.gov" xmlns:wfs
="http://www.opengis.net/wfs" xmlns:topp
="http://www.openplans.org/topp" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf
="http://www.openplans.org/spearfish" xmlns:ows
="http://www.opengis.net/ows" xmlns:gml
="http://www.opengis.net/gml" xmlns:xlink
="http://www.w3.org/1999/xlink"
><
gml:featureMembers
><
topp:states gml:id
="states.3"
><
gml:boundedBy
><
gml:Envelope srsName
="urn:x-ogc:def:crs:EPSG:4326"
><
gml:lowerCorner
>
38.44949 -75.791435
</
gml:lowerCorner
><
gml:upperCorner
>
39.84000800000001 -75.045998
</
gml:upperCorner
></
gml:Envelope
></
gml:boundedBy
><
topp:the_geom
><
gml:MultiSurface srsName
="urn:x-ogc:def:crs:EPSG:4326"
><
gml:surfaceMember
><
gml:Polygon
><
gml:exterior
><
gml:LinearRing
><
gml:posList
>
38.55747600000001 -75.70742 38.649551 -75.71106 38.83017000000001 -75.724937 39.141548 -75.752922 39.24775299999999 -75.761658 39.295849000000004 -75.764664 39.38300699999999 -75.772697 39.72375500000001 -75.791435 39.72444200000001 -75.775269 39.77481800000001 -75.745934 39.820347 -75.695114 39.83819600000001 -75.644341 39.84000800000001 -75.583794 39.826435000000004 -75.470345 39.798869999999994 -75.42083 39.789658 -75.412117 39.778130000000004 -75.428009 39.763248000000004 -75.460754 39.74171799999999 -75.475128 39.71997099999999 -75.476334 39.71474499999999 -75.489639 39.61279300000001 -75.610725 39.566722999999996 -75.562996 39.46376799999999 -75.590187 39.36694 -75.515572 39.25763699999999 -75.402481 39.073036 -75.397728 39.01238599999999 -75.324852 38.945910999999995 -75.307899 38.808670000000006 -75.190941 38.799812 -75.083138 38.44949 -75.045998 38.449963 -75.068298 38.45045099999999 -75.093094 38.455208 -75.350204 38.463066 -75.69915 38.55747600000001 -75.70742
</
gml:posList
></
gml:LinearRing
></
gml:exterior
></
gml:Polygon
></
gml:surfaceMember
></
gml:MultiSurface
></
topp:the_geom
><
topp:STATE_NAME
>
Delaware
</
topp:STATE_NAME
><
topp:STATE_FIPS
>
10
</
topp:STATE_FIPS
><
topp:SUB_REGION
>
S Atl
</
topp:SUB_REGION
><
topp:STATE_ABBR
>
DE
</
topp:STATE_ABBR
><
topp:LAND_KM
>
5062.456
</
topp:LAND_KM
><
topp:WATER_KM
>
1385.022
</
topp:WATER_KM
><
topp:PERSONS
>
666168.0
</
topp:PERSONS
><
topp:FAMILIES
>
175867.0
</
topp:FAMILIES
><
topp:HOUSHOLD
>
247497.0
</
topp:HOUSHOLD
><
topp:MALE
>
322968.0
</
topp:MALE
><
topp:FEMALE
>
343200.0
</
topp:FEMALE
><
topp:WORKERS
>
247566.0
</
topp:WORKERS
><
topp:DRVALONE
>
258087.0
</
topp:DRVALONE
><
topp:CARPOOL
>
42968.0
</
topp:CARPOOL
><
topp:PUBTRANS
>
8069.0
</
topp:PUBTRANS
><
topp:EMPLOYED
>
335147.0
</
topp:EMPLOYED
><
topp:UNEMPLOY
>
13945.0
</
topp:UNEMPLOY
><
topp:SERVICE
>
87973.0
</
topp:SERVICE
><
topp:MANUAL
>
44140.0
</
topp:MANUAL
><
topp:P_MALE
>
0.485
</
topp:P_MALE
><
topp:P_FEMALE
>
0.515
</
topp:P_FEMALE
><
topp:SAMP_POP
>
102776.0
</
topp:SAMP_POP
></
topp:states
></
gml:featureMembers
></
wfs:FeatureCollection
>
。
用SQL语句来解释,这个调用就是"select * from topp:states where id= states.3"。我们返回了一个叫states.3的Feature的数据,而返回数据的格式就是GML。我们先不要管GML,只看这个数据块还是不难理解的。它包含states.3的BoundingBox和属性数据。属性数据就是我们在DescribeFeatureType的返回值中看到的字段的值。而第一个属性"the_geom"就是这个Feature的几何数据。这个Feature是一个MultiSurface,它由一个Polygon组成,而这个Polygon又是由一个LinearRing构成。说白了这个Feature就是一个多边形。这个LinearRing由一系列点组成,第一个点与最后一个点重合,这和ESRI Shape的Polygon结构相似。至此我们已经能够成功的获得Feature的数据了。
来看另外一个调用:
http://localhost:8080/geoserver/wfs?request=GetFeature&version=1.1.0&typeName=topp:states&BBOX=-75.102613,40.212597,-72.361859,41.512517,EPSG:4326
。这一次我们没有指定id,而是用一个bbox参数来作为检索条件,这就和WMS很像了。
在真实的应用中,我们不只需要用id来选择Feature,我们还需要更强大的检索机制,就像SQL的"where"一样。这样我们就需要Filter。前面我们都采用了GET方法来执行调用,但是GET只能实现相对简单的调用,要执行复杂的调用我们需要POST方法。下面我们就来看一个XML数据块
Code
<!--
Performs a between filter to find the states with an area
between 100,000 and 150,000.
Also, it just returns the STATE_NAME, LAND_KM, and geometry
(instead of all the attributes). -->
<
wfs:GetFeature service
="WFS" version
="1.1.0"
xmlns:topp
="http://www.openplans.org/topp"
xmlns:wfs
="http://www.opengis.net/wfs"
xmlns:ogc
="http://www.opengis.net/ogc"
xmlns:gml
="http://www.opengis.net/gml"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.opengis.net/wfs
http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"
>
<
wfs:Query typeName
="topp:states"
>
<
wfs:PropertyName
>
topp:STATE_NAME
</
wfs:PropertyName
>
<
wfs:PropertyName
>
topp:LAND_KM
</
wfs:PropertyName
>
<
wfs:PropertyName
>
topp:the_geom
</
wfs:PropertyName
>
<
ogc:Filter
>
<
ogc:PropertyIsBetween
>
<
ogc:PropertyName
>
topp:LAND_KM
</
ogc:PropertyName
>
<
ogc:LowerBoundary
><
ogc:Literal
>
100000
</
ogc:Literal
></
ogc:LowerBoundary
>
<
ogc:UpperBoundary
><
ogc:Literal
>
150000
</
ogc:Literal
></
ogc:UpperBoundary
>
</
ogc:PropertyIsBetween
>
</
ogc:Filter
>
</
wfs:Query
>
</
wfs:GetFeature
>
。用POST把这个数据块发给WFS服务器,我们会看到返回了与前面相似的数据块。用SQL来描述这个调用"select STATE_NAME, LAND_KM, the_geom from states where LAND_KM < 150000 and LAND_KM >= 100000"。意思十分清楚了。现在来简单介绍一下Filter。
在Filter中这种比较被分为空间操作(Spatial operators),比较操作(Comparison operators)和逻辑操作(Logical operators)三类。空间操作主要针对几何数据,例如我们可以用操作"DWithin"来判断一个几何元素是否完全包含另外一个。比较操作主要针对一般的属性数据,例如我们已经使用的"PropertyIsBetween",判断了属性"LAND_KM "的值是否介于给定的两个值之间。逻辑操作就是"and","or"和"not"操作,它被用来组合前两种操作的结果。依靠这些操作我们可以组合出丝毫不逊于SQL的查询。
到目前为止我们一直是通过指定属性名或给出一个定值来构造查询,如果我们希望比较某个属性经过一种计算得到的值,例如"2* LAND_KM",该怎么办呢。Filter为我们提供了算数操作(Arithmetic operators)和函数(Functions)。
算数操作有"Add, Sub, Mul, Div"四则运算,小学水平:)。而函数则为Filter的扩展提供了可能。除了标准函数,服务器还可以增加自己的函数,只要符合Filter的标准。我们在Capability数据块中可以看到的服务器支持哪些函数,以及他们的签名。
-
LockFeature
从现在开始我们将进入"幽暗的山谷",这里风景秀丽但是危机四伏。这不是危言耸听,一直以来我们仅仅是从服务器获得数据,但是接下来的操作将修改服务器的状态。设想,你维护着一个提供GIS服务的系统,你需要定期更新数据。那你就需要一个方法,不单是能获得这些数据还要能修改这些数据。设想和你一样的维护人员有许多,你们同时维护着这个数据源。如果你们只是同时查询,下载,打印数据,那唯一的问题就是服务器会偶尔显得拥堵。但是如果你们同时修改数据问题就来了。我们来设想这样的情况,A获得了图层states的1,2,3,4四个Feature,然后把Feature 1的属性做了修改,这时B向服务器发出删除Feature的指令,碰巧Feature 1被删除了,此时A才将修改后的数据提交到服务器。会发生什么?服务器首先是找不到Feature 1,然后它会返回异常给A说Feature 1不存在,而A会很郁闷,Feature 1明明是存在的!这种情况就是分布式系统常常需要处理的,并发访问。不幸的是几乎所有GIS系统都是分布式的。
说了这么多无非是想引入LockFeature。WFS为我们考虑到了并发访问的问题,它提出的解决方案就是锁住需要修改的Feature。我们只需要告诉服务器我们希望锁住哪些Feature,然后服务器会返回两组数据,一组是成功锁定的Feature Id,一组是无法锁定的Feature Id。我们来看一个典型调用
Code
<
LockFeature
version
="1.1.0"
service
="WFS"
lockAction
="SOME"
xmlns
="http://www.opengis.net/wfs"
xmlns:myns
="http://www.someserver.com/myns"
xmlns:ogc
="http://www.opengis.net/ogc"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.opengis.net/wfs ../wfs/1.1.0/WFS.xsd"
>
<
Lock typeName
="topp:states"
>
<
ogc:Filter
>
<
ogc:FeatureId fid
="states.1"
/>
<
ogc:FeatureId fid
="states.3"
/>
</
ogc:Filter
>
</
Lock
>
</
LockFeature
>
和它的返回
Code
<?
xml version="1.0" encoding="UTF-8"
?>
<
wfs:LockFeatureResponse xsi:schemaLocation
="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd" xmlns:ogc
="http://www.opengis.net/ogc" xmlns:tiger
="http://www.census.gov" xmlns:wfs
="http://www.opengis.net/wfs" xmlns:topp
="http://www.openplans.org/topp" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf
="http://www.openplans.org/spearfish" xmlns:ows
="http://www.opengis.net/ows" xmlns:gml
="http://www.opengis.net/gml" xmlns:xlink
="http://www.w3.org/1999/xlink"
><
wfs:LockId
>
GeoServer_6074053a2e54aae0
</
wfs:LockId
><
wfs:FeaturesNotLocked
><
ogc:FeatureId fid
="states.1"
/><
ogc:FeatureId fid
="states.3"
/></
wfs:FeaturesNotLocked
></
wfs:LockFeatureResponse
>
。所有含义一目了然。我们可以用Filter来指定需要锁定的Feature,返回数据告诉我们这些Feature哪些成功锁定,哪些不能锁定。不能锁定的Feature显然就是已经被锁定了的。
这里有两个地方需要注意,首先是调用参数里面有一个"expiry"属性。这个值代表希望锁定的时间,以分钟为单位。如果超过这个时间还没有解锁,服务器会自动解锁,这样就避免了死锁。另外一个是返回值里有一个LockId节点。它的值标识了一次锁定行为,这个值一直到此次锁定解开前都是有效地,在Transaction中我们会用到它。
值得注意的是,在WFS里LockFeature是可选的,也就是说标准并不要求服务器一定要提供这个方法,所以我们在使用时必须检查GetCapabilities的返回值,看这个服务器是否支持锁定Feature。
-
Transaction
砍杀了许多怪物,赞足了经验和血瓶,我们终于要打大Boss了。Transaction,熟悉数据库的朋友对这个词都不陌生,事务。WFS修改数据的方法就是"提交事务"。这个方法也是可选的,这就意味着你完全可以开发一个不支持修改操作的GIS服务器,然后声称你的服务器符合WFS标准。
一如既往,我们先来看典型调用
Code
<
wfs:Transaction service
="WFS" version
="1.0.0"
xmlns:cdf
="http://www.opengis.net/cite/data"
xmlns:ogc
="http://www.opengis.net/ogc"
xmlns:wfs
="http://www.opengis.net/wfs"
xmlns:topp
="http://www.openplans.org/topp"
>
<
wfs:LockId
>
OgcWfs_237erh8437578
</
wfs:LockId
>
<
wfs:Delete typeName
="topp:tasmania_roads"
>
<
ogc:Filter
>
<
ogc:PropertyIsEqualTo
>
<
ogc:PropertyName
>
topp:TYPE
</
ogc:PropertyName
>
<
ogc:Literal
>
alley
</
ogc:Literal
>
</
ogc:PropertyIsEqualTo
>
</
ogc:Filter
>
</
wfs:Delete
>
</
wfs:Transaction
>
。这个调用中用到Filter其实我们应该了解在所有OGC标准中需要定位和检索的地方都会用到Filter,它就像一个通用组件。我们专心看看LockId这个节点,在讨论LockFeature时我们见过它,它标识一次成功的锁定操作。现在这个值终于有用武之地了。我们锁定Feature的目的就是为了不受干扰的修改它们,如果我们要修改的Feature有被锁定的对象我们就需要提供这个Id,不然我们会得到一个错误。
和数据库一样,Transaction提供Insert,Update和Delete操作。此外服务器开发商还可以增加自己的操作,并且在Capability中列出。一个Transaction节点可以包含许多这些操作,下面我们一个个介绍他们。先从Insert开始。
Insert操作为指定的FeatureType创建新的Feature实例,Feature的数据用GML描述。它会返回新Feature的Id,顺序和提交的顺序一致。其实WFS有一套复杂的机制允许用户指定新Feature的Id,详细情况请阅读标准文档(毕竟我们这个文章不是标准文档的翻译)。这是一个典型的Insert调用
Code
<
wfs:Transaction service
="WFS" version
="1.0.0"
xmlns:wfs
="http://www.opengis.net/wfs"
xmlns:topp
="http://www.openplans.org/topp"
xmlns:gml
="http://www.opengis.net/gml"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://www.openplans.org/topp http://localhost:8080/geoserver/wfs/DescribeFeatureType?typename=topp:tasmania_roads"
>
<
wfs:Insert
>
<
topp:tasmania_roads
>
<
topp:the_geom
>
<
gml:MultiLineString srsName
="http://www.opengis.net/gml/srs/epsg.xml#4326"
>
<
gml:lineStringMember
>
<
gml:LineString
>
<
gml:coordinates decimal
="." cs
="," ts
=" "
>
494475.71056415,5433016.8189323 494982.70115662,5435041.95096618
</
gml:coordinates
>
</
gml:LineString
>
</
gml:lineStringMember
>
</
gml:MultiLineString
>
</
topp:the_geom
>
<
topp:TYPE
>
alley
</
topp:TYPE
>
</
topp:tasmania_roads
>
</
wfs:Insert
>
</
wfs:Transaction
>
<!--
YOU PROBABLY DO NOT WANT TO RUN THIS QUERY SINCE
IT WILL MODIFY YOUR SOURCE DATA FILES
It will add a simple line to the tasmania_roads dataset.
-->
<
wfs:Transaction service
="WFS" version
="1.0.0"
xmlns:wfs
="http://www.opengis.net/wfs"
xmlns:topp
="http://www.openplans.org/topp"
xmlns:gml
="http://www.opengis.net/gml"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://www.openplans.org/topp http://localhost:8080/geoserver/wfs/DescribeFeatureType?typename=topp:tasmania_roads"
>
<
wfs:Insert
>
<
topp:tasmania_roads
>
<
topp:the_geom
>
<
gml:MultiLineString srsName
="http://www.opengis.net/gml/srs/epsg.xml#4326"
>
<
gml:lineStringMember
>
<
gml:LineString
>
<
gml:coordinates decimal
="." cs
="," ts
=" "
>
494475.71056415,5433016.8189323 494982.70115662,5435041.95096618
</
gml:coordinates
>
</
gml:LineString
>
</
gml:lineStringMember
>
</
gml:MultiLineString
>
</
topp:the_geom
>
<
topp:TYPE
>
alley
</
topp:TYPE
>
</
topp:tasmania_roads
>
</
wfs:Insert
>
</
wfs:Transaction
>
以及返回
Code
<?
xml version="1.0" encoding="UTF-8"
?>
<
wfs:WFS_TransactionResponse version
="1.0.0" xmlns:wfs
="http://www.opengis.net/wfs" xmlns:ogc
="http://www.opengis.net/ogc" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation
="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.0.0/WFS-transaction.xsd"
><
wfs:InsertResult
><
ogc:FeatureId fid
="new0"
/></
wfs:InsertResult
> <
wfs:TransactionResult
> <
wfs:Status
> <
wfs:SUCCESS
/> </
wfs:Status
> </
wfs:TransactionResult
></
wfs:WFS_TransactionResponse
>
。
Update操作需要指定要修改的Feature和要修改的属性以及属性的新值。它可以用我们的Filter来指定Feature。这是一个典型的Update调用
Code
<!--
YOU PROBABLY DO NOT WANT TO RUN THIS QUERY SINCE
IT WILL MODIFY YOUR SOURCE DATA FILES
This will update one of the geometry fields in the tasmania_roads dataset.
-->
<
wfs:Transaction service
="WFS" version
="1.0.0"
xmlns:topp
="http://www.openplans.org/topp"
xmlns:ogc
="http://www.opengis.net/ogc"
xmlns:wfs
="http://www.opengis.net/wfs"
xmlns:gml
="http://www.opengis.net/gml"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
>
<
wfs:Update typeName
="topp:tasmania_roads"
>
<
wfs:Property
>
<
wfs:Name
>
the_geom
</
wfs:Name
>
<
wfs:Value
>
<
gml:MultiLineString srsName
="http://www.opengis.net/gml/srs/epsg.xml#4326"
>
<
gml:lineStringMember
>
<
gml:LineString
>
<
gml:coordinates
>
500000,5450000,0 540000,5450000,0
</
gml:coordinates
>
</
gml:LineString
>
</
gml:lineStringMember
>
</
gml:MultiLineString
>
</
wfs:Value
>
</
wfs:Property
>
<
ogc:Filter
>
<
ogc:FeatureId fid
="tasmania_roads.1"
/>
</
ogc:Filter
>
</
wfs:Update
>
</
wfs:Transaction
>
以及返回
Code
<?
xml version="1.0" encoding="UTF-8"
?>
<
wfs:WFS_TransactionResponse version
="1.0.0" xmlns:wfs
="http://www.opengis.net/wfs" xmlns:ogc
="http://www.opengis.net/ogc" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation
="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.0.0/WFS-transaction.xsd"
><
wfs:InsertResult
><
ogc:FeatureId fid
="none"
/></
wfs:InsertResult
> <
wfs:TransactionResult
> <
wfs:Status
> <
wfs:SUCCESS
/> </
wfs:Status
> </
wfs:TransactionResult
></
wfs:WFS_TransactionResponse
>
。
Delete操作只需要指定Feature就ok了。
到这里我们就把Transaction介绍完了,我发现一个问题,WFS不支持创建新的FeatureType。我不知道专家们为什么这样设计,我只能接受这个实事,毕竟WFS已经够复杂了够强大了。
WFS不可能通过这么短的文字讨论明白,就算仔细阅读标准文档也只能一知半解,糊弄人可以。最佳学习途径就是自己写个程序试试。我们下面就来实践一把。为了保持连贯性我在以前的例子基础上作修改。大家还记得我们的"简单到死的"WMS服务器"SharpMapServer"吧。它只能用ShapeFile作为数据源,这一次我们将增加WFS数据源。
WFS服务器我们目前只能使用现成的,我使用的是GeoServer。如果你
只想运行一下看看效果,
下载Demo
,并且建议你安装
GeoServer
的最新版。如果你对自己配置感兴趣并且想修改这个程序,在代码里有一个使用说明"readme.doc",详细介绍了SharpMapServer的配置方法。
到此为止,我们已经见过OGC的两大法宝了,其实还有另外几个服务器协议,像与WMS,WFS并列的WCS。但是我觉想玩点别的,我们就从一直被我们忽略的Filter说起。我们来逐步完善SharpMapServer,让它也能支持比较完整的Style特性。
注:本文所有演示都是使用FireFox浏览器在GeoServer上进行的,GeoServer的最新版本可以在这里获得:
http://geoserver.org/
。