網絡要素服務(WFS)詳解

1. 概述

前置文章:
地圖服務器GeoServer的安裝與配置
GeoServer發佈地圖服務(WMS、WFS)
網絡地圖服務(WMS)詳解

WMS是一個返回圖片地圖的服務,圖片本身就是柵格數據的一種,而對於矢量數據則可以進行矢量柵格化;因此,WMS的數據源既可以是柵格數據,也可以是矢量數據。而WFS則不同,它是一個專門針對於矢量數據的服務,其返回的也是矢量要素本身。在Web環境中,圖片是很容易進行可視化展示的,甚至圖片本身就是GUI中一類很重要的元素。但矢量要素則不同,是不太容易可視化的。例如,如果要在前端的HTML5頁面中展示獲取的要素,就需要調用HTML5的Canvas元素來進行繪圖,這其中涉及到繁複的操作不說,也很有可能會有性能問題。因此,WFS並不關心可視化問題,而是爲返回GIS矢量數據而設計的,同時還支持矢量的查詢、增加、刪除以及修改等事務性操作。

WFS與WMS一樣,同樣使用HTTP來實現的各種操作,不同的是由於進行請求要求發送複雜的XML數據,簡單的Get請求方式可能會受到數據量的限制,這種情況下需要使用Post方式進行請求。而在Web前端環境中,XML數據並不方便使用(最方便的是JSON數據),經常要考慮到繁瑣的字符串拼接以及字符轉義的問題。另一方面,由於WFS需要傳輸的參數比較多,在其標準規範《OpenGIS_Web_Map_Service_WMS_Implementation_Specification》使用了XML Schema(描述XML結構的語言)這一複雜的語言來描述需要傳遞的XML數據;並且一個操作的數據描述還分散在文檔不同的地方。官方的參考資料尚且如此複雜,普通GIS從業人員也就很少願意主動去使用,這無疑限制了造成WFS的應用場景。應該來說,WFS的設計出來的年代比較早,XML格式還是主流,如果使用JSON格式來進行數據傳輸,應該會方便不少。

目前WFS有2.0.2、2.0.0、1.1.3、1.1.0和1.0.0等多個版本,不過有4種操作是每個版本都有並且比較常見的,如下表1所示。由於有的操作與WMS比較類似,有的操作又比較繁瑣,在下面的介紹中就不再對參數進行窮舉說明,以實際的例子爲主。

【表1 WFS支持的操作】

操作 描述
GetCapabilities 生成元數據文檔,描述服務器提供的WFS服務以及有效的WFS操作和參數
DescribeFeatureType 返回WFS服務支持的要素類型的描述
GetFeature 從數據源中返回所選要素,包括幾何和屬性值
Transaction 通過創建、更新和刪除來編輯現有要素類型

2. GetCapabilities

這個操作與WMS的GetCapabilities操作比較類似,都是生成描述服務器提供的WFS服務能力的元數據信息。例如我們在瀏覽器地址欄中輸入如下地址:

http://localhost:8080/geoserver/wfs?
service=wfs&
version=2.0.0&
request=GetCapabilities

此時會返回一個XML文件,如下圖所示:

圖8.33 WFS GetCapabilities返回結果

3. DescribeFeatureType

在請求實際數據之前,往往需要知道要請求要素類型的信息,此時可以使用DescribeFeatureType操作。除此之外,該操作還可以獲取屬性的字段名稱,以及字段類型。例如我們獲取第8.1.3節發佈的矢量要素test:multipolygons的類型,可通過如下地址來進行訪問:

http://localhost:8080/geoserver/wfs?
service=wfs&
version=2.0.0&
request=DescribeFeatureType&
typeName=test:multipolygons&
outputFormat=application/json

由於我們設置了輸出類型爲JSON,因此會返回一個JSON數據,如下圖8.34所示:

圖8.34 WFS DescribeFeatureType返回結果

4. GetFeature

4.1 Get訪問方式

接下來就是WFS中最重要的操作GetFeature了,通過該操作可以返回矢量數據源的要素信息,包括幾何信息和屬性信息。例如,要獲取矢量要素的全部信息,可通過如下地址來進行訪問:

http://localhost:8080/geoserver/wfs?
service=wfs&
version=2.0.0&
request=GetFeature&
typeNames=test:multipolygons&
outputFormat=application/json

此時返回的是所有的350個要素信息,如下圖所示:

圖8.35 WFS GetFeature返回所有要素

很多時候返回所有的要素信息並不是我們想要的,我們希望進行空間查詢,例如查找一個矩形範圍內要素,那麼可以通過在瀏覽器中輸入如下地址來實現:

http://localhost:8080/geoserver/wfs?
service=wfs&
version=2.0.0&
request=GetFeature&
typeNames=test:multipolygons&
outputFormat=application/json&
srsName=EPSG:4326&
bbox=38.8954267799311,-77.039412232917,38.8965224165805,-77.0380063000187

其中srsName表示空間座標參考,bbox表示具體的四至範圍。此時的返回結果如下圖所示,可以看到返回的矢量要素只有21個了:

圖8.36 WFS GetFeature返回矩形範圍內要素

如果我們要進行屬性查詢,例如查找特定要素ID的特定屬性值,可通過在瀏覽器中輸入如下地址來實現:

http://localhost:8080/geoserver/wfs?
service=wfs&
version=2.0.0&
request=GetFeature&
typeNames=test:multipolygons&
outputFormat=application/json&
featureID=multipolygons.2&
propertyName=name,building

featureID表示要素Id,propertyName表示要素字段名。此時返回的結果可以看到該要素具體的屬性值,如下圖所示:

圖8.37 WFS GetFeature返回要素屬性值

4.2 Post訪問方式

以上幾種方式都是通過在瀏覽器中輸入如下地址,也就是通過HTTP協議的Get請求來實現。但是如果進行空間查詢的參數數據量特別大,比如查詢一個多邊形範圍內的要素就很麻煩了。雖然仍然可以通過給Get請求的filter參數傳遞一個XML格式的文本字符串的方式來實現,但是可能會受到URL長度的限制。因此,複雜的空間查詢最好通過POST請求來實現。

不過,使用Post訪問方式的示例就要麻煩一點。爲了避免在訪問WFS服務時遇到跨域問題,我們需要發佈一個靜態網頁,通過JavaScript來實現Post請求。具體操作是新建一個test.html文件夾,內容如下例1所示:

【例1 給WFS發送Post請求】

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>test handle response</title>
  <script>
    var url = "http://localhost:8080/geoserver/wfs";
    var xhr = new XMLHttpRequest();
    xhr.open("POST", url);
    //xhr.open("GET", url);
    xhr.setRequestHeader("Content-Type", "text/xml");
    xhr.onload = function (e) {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          console.log(xhr.responseText);
        } else {
          console.error(xhr.statusText);
        }
      }
    };

    xhr.onerror = function (e) {
      console.error(xhr.statusText);
    };

    var xml = `<?xml version='1.0' encoding='UTF-8'?>
<wfs:GetFeature service=\"WFS\" version=\"2.0.0\" outputFormat=\"json\" 
xmlns:wfs=\"http://www.opengis.net/wfs/2.0\" 
xmlns:fes=\"http://www.opengis.net/fes/2.0\" 
xmlns:gml=\"http://www.opengis.net/gml/3.2\" 
xmlns:test=\"https://test\" 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
xsi:schemaLocation=\"http://www.opengis.net/wfs/2.0 
http://schemas.opengis.net/wfs/2.0/wfs.xsd 
http://www.opengis.net/gml/3.2 
http://schemas.opengis.net/gml/3.2.1/gml.xsd\">
  <wfs:Query typeNames='test:multipolygons'>
    <fes:Filter>
      <fes:Intersects>
        <fes:ValueReference>test:the_geom</fes:ValueReference>
        <gml:Envelope srsName=\"EPSG:4326\">
          <gml:lowerCorner>
            -77.039412232917 38.8954267799311
          </gml:lowerCorner>
          <gml:upperCorner>
            -77.0380063000187 38.8965224165805
          </gml:upperCorner>
        </gml:Envelope>
      </fes:Intersects>
    </fes:Filter>
  </wfs:Query>
</wfs:GetFeature>`;
    xhr.send(xml); 
  </script>
</head>
<body>
</body>
</html>

然後將這個文件放入到一個新的文件夾geoservertest,最後將geoservertest文件夾放入到Tomcat的項目發佈目錄webapps中,如下圖所示:

圖8.38 發佈一個測試Post請求的靜態網頁

在這個示例中,使用了XMLHttpRequest來發送Post請求,並且在請求頭中標明數據內容是一個XML文件。我們這裏使用的是一個XML格式的文本字符串,實際上我們要傳輸的XML數據內容經過格式化如下所示:

<?xml version='1.0' encoding='UTF-8'?>
<wfs:GetFeature service="WFS" version="2.0.0" outputFormat="json"
  xmlns:wfs="http://www.opengis.net/wfs/2.0"
  xmlns:fes="http://www.opengis.net/fes/2.0"
  xmlns:gml="http://www.opengis.net/gml/3.2"
  xmlns:test="https://test"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 
http://schemas.opengis.net/wfs/2.0/wfs.xsd 
http://www.opengis.net/gml/3.2 
http://schemas.opengis.net/gml/3.2.1/gml.xsd">
  <wfs:Query typeNames='test:multipolygons'>
    <fes:Filter>
      <fes:Intersects>
        <fes:ValueReference>test:the_geom</fes:ValueReference>
        <gml:Envelope srsName="EPSG:4326">
          <gml:lowerCorner>
            -77.039412232917 38.8954267799311
          </gml:lowerCorner>
          <gml:upperCorner>
            -77.0380063000187 38.8965224165805
          </gml:upperCorner>
        </gml:Envelope>
      </fes:Intersects>
    </fes:Filter>
  </wfs:Query>
</wfs:GetFeature>

我們可以看到XML其中一些屬性和屬性的值就是之前的參數,例如service="WFS"、version="2.0.0"、outputFormat="json"以及typeNames='test:multipolygons'。而fes:filter正是前面提到的用於設置過濾數據的元素;fes:Intersects則表示相交,test:the_geom表示相交查詢要素的幾何字段名稱;gml:Envelope整個節點則通過GML(Geographic Markup Language,地理標記語言)描述了一個矩形範圍。

我們在瀏覽器輸入訪問地址:http://localhost:8080/geoservertest/test.html ,打開瀏覽器調試器,可以看到在瀏覽器控制檯輸出了返回的信息。也可以檢查該訪問請求,查看具體的返回信息,如下圖所示。可以看到返回的要素個數和前面Get請求的結果一樣,也是21個要素。這是因爲我們空間查詢輸入的四至範圍是一樣的。不過Post請求可以通過GML構造複雜的幾何要素來進行空間查詢,這時Get請求不能做到的。

圖8.39 WFS GetFeature使用Post請求返回信息

5. Transaction

Transaction操作可以創建、修改和刪除WFS發佈的要素,加上GetFeature的查詢操作,就組成了類似於處理常規數據庫數據的“增刪改查”操作。區別只在WFS服務的Transaction和GetFeature操作針對的是遠端的地理空間數據。這也是將這個操作命名爲Transaction(事務)的原因。簡要來說,Transaction操作支持四個動作(Action),分別是Insert(插入)、Replace(替換)、Update(更新)和Delete(刪除)。由於Transaction操作也比較複雜,通常使用Post請求來實現。

還是使用例1所示的test.html頁面來進行WFS的Transaction操作。由於WFS操作Post請求發送的請求的文件頭都差不多,區別主要在於發送的內容,也就是XML數據;那麼我們就只需要修改發送的XML格式字符串就可以了。因此,Transaction操作所使用的示例與例1相同,這裏只列出具體的XML數據。

5.1 Insert

既然我們要插入一個要素,首先就需要描述一個要素信息來進行傳輸。但是WFS要求請求的要素信息都是GML描述的,比如這裏我們的示例矢量數據類型是面要素(multipolygon),那麼應該如何去描述呢?最簡單的方式是通過GetFeature查看默認格式的要素信息,就可以看到GML描述的要素,如下所示:

<test:multipolygons gml:id="multipolygons.5">
  <gml:name/>
  <test:the_geom>
      <gml:MultiSurface srsName="http://www.opengis.net/gml/srs/epsg.xml#4326" srsDimension="2" gml:id="multipolygons.5.the_geom">
          <gml:surfaceMember>
              <gml:Polygon gml:id="multipolygons.5.the_geom.1">
                  <gml:exterior>
                      <gml:LinearRing>
                          <gml:posList>-77.0383595 38.8960779 -77.0383609 38.8961371 -77.0383618 38.8961764 ... -77.0383595 38.8960779</gml:posList>
                      </gml:LinearRing>
                  </gml:exterior>
                  <gml:interior>
                      <gml:LinearRing>
                          <gml:posList>-77.0386713 38.8958537 -77.0387129 38.8958542 -77.0387253 38.8958338 ... -77.0386713 38.8958537</gml:posList>
                      </gml:LinearRing>
                  </gml:interior>
              </gml:Polygon>
          </gml:surfaceMember>
      </gml:MultiSurface>
  </test:the_geom>
  <test:osm_id>3211113</test:osm_id>
  <test:osm_way_id/>
  <test:type>multipolygon</test:type> 
</test:multipolygons>

這段GML描述,如果我們對矢量比較熟悉的話,理解起來就會非常容易。一個面要素可能有一個外環和多個內環。環是起點和終點爲同一個點的線串,線串由一系列連續的點組成。我們可以仿照這個格式,也創建一個GML格式的要素信息,將其嵌入到要傳輸的XML數據中。具體的插入要素要發送Post請求的XML數據如下所示:

<?xml version="1.0"?>
<wfs:Transaction service="WFS" version="2.0.0"
    xmlns:test="https://test"
    xmlns:fes="http://www.opengis.net/fes/2.0"
    xmlns:gml="http://www.opengis.net/gml/3.2"
    xmlns:wfs="http://www.opengis.net/wfs/2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd">
    <wfs:Insert>
        <test:multipolygons gml:id="multipolygons.351">
            <test:the_geom>
                <gml:MultiSurface srsName="http://www.opengis.net/gml/srs/epsg.xml#4326" srsDimension="2" gml:id="multipolygons.352.the_geom">
                    <gml:surfaceMember>
                        <gml:Polygon gml:id="multipolygons.351.the_geom.1">
                            <gml:exterior>
                                <gml:LinearRing>
                                    <gml:posList>-77.039412232917 38.8954267799311 -77.039412232917 38.8965224165805 -77.0380063000187 38.8965224165805 -77.0380063000187 38.8954267799311 -77.039412232917 38.8954267799311</gml:posList>
                                </gml:LinearRing>
                            </gml:exterior>
                        </gml:Polygon>
                    </gml:surfaceMember>
                </gml:MultiSurface>
            </test:the_geom>
        </test:multipolygons>
    </wfs:Insert>
</wfs:Transaction>

在這個XML中我們可以看到一些熟悉的配置,例如service="WFS",version="2.0.0"等。wfs:Insert表示使用wfs的插入操作,test:multipolygons則索引到我們要插入的要素圖層名稱。test是我們在前文中創建的工作空間,我們同時還創建了對應的命名空間URI:https://test ;工作空間需要與命名空間URI相關聯,這也是爲什麼要寫xmlns:test="https://test"。除此之外,剩下的就是通過GML描述的面要素了,可以看到我們構建了一個四邊形。

同樣的還是在瀏覽器輸入訪問地址http://localhost:8080/geoservertest/test.html 來發送Post請求。如果一切順利的話,再通過GetFeature操作(http://localhost:8080/geoserver/wfs?service=wfs&version=2.0.0&request=GetFeature&typeNames=test:multipolygons&outputFormat=application/json )就可以看到剛剛插入的新的要素,如下圖所示:

圖8.40 WFS的Transaction操作的Insert(插入)結果

5.2 Replace

有了Insert操作作爲基礎,理解Replace的實現就非常容易了。Replace操作Post請求需要傳輸的XML數據如下:

<?xml version='1.0' encoding='UTF-8'?>
<wfs:Transaction version="2.0.0" service="WFS"
    xmlns:test="https://test"
    xmlns:fes="http://www.opengis.net/fes/2.0"
    xmlns:wfs="http://www.opengis.net/wfs/2.0"
    xmlns:gml="http://www.opengis.net/gml/3.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0
			http://schemas.opengis.net/wfs/2.0/wfs.xsd">
    <wfs:Replace>
        <test:multipolygons gml:id="multipolygons.351">
            <test:the_geom>
                <gml:MultiSurface srsName="http://www.opengis.net/gml/srs/epsg.xml#4326" srsDimension="2" gml:id="multipolygons.352.the_geom">
                    <gml:surfaceMember>
                        <gml:Polygon gml:id="multipolygons.352.the_geom.1">
                            <gml:exterior>
                                <gml:LinearRing>
                                    <gml:posList>-77.039412232917 38.8954267799311 -77.039412232917 38.8965224165805 -77.0380063000187 38.8965224165805 -77.039412232917 38.8954267799311
                                    </gml:posList>
                                </gml:LinearRing>
                            </gml:exterior>
                        </gml:Polygon>
                    </gml:surfaceMember>
                </gml:MultiSurface>
            </test:the_geom>
        </test:multipolygons>
        <fes:Filter>
            <fes:ResourceId rid="multipolygons.351"/>
        </fes:Filter>
    </wfs:Replace>
</wfs:Transaction>

可以看到XML數據內容與Insert操作差不多,不過要注意的是多了一個fes:Filter元素來幫助選定到具體需要替換的要素。最後通過GetFeature操作查詢替換的要素如下圖所示,可以看到我們將一個四邊形要素替換成了三角形:

圖8.41 WFS的Transaction操作的Replace(替換)結果

5.3 Update

前面Insert和Replace操作的對象都是要素的幾何信息,其實要素的屬性信息也可以修改。例如可以通過Update操作來更新要素的屬性信息,其Post請求需要傳輸的XML數據如下:

<?xml version='1.0' encoding='UTF-8'?>
<wfs:Transaction version="2.0.0" service="WFS"
   xmlns:fes="http://www.opengis.net/fes/2.0"
   xmlns:wfs="http://www.opengis.net/wfs/2.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.opengis.net/wfs/2.0
                       http://schemas.opengis.net/wfs/2.0.0/wfs.xsd">
   <wfs:Update typeName="test:multipolygons">
      <wfs:Property>
         <wfs:ValueReference>name</wfs:ValueReference>
         <wfs:Value>bound</wfs:Value>
      </wfs:Property>
      <wfs:Property>
         <wfs:ValueReference>other_tags</wfs:ValueReference>
         <wfs:Value>test</wfs:Value>
      </wfs:Property>      
      <fes:Filter>
         <fes:ResourceId rid="multipolygons.351"/>
      </fes:Filter>
   </wfs:Update>
</wfs:Transaction>

可以看到我們爲這個新增加並且替換後的要素更新了兩個屬性字段(name和other_tags)的值,通過GetFeature操作查詢要素的結果如下圖所示:

圖8.42 WFS的Transaction操作的Update(更新)結果

5.4 Delete

最後就讓我們形成一個迴環,將這個新增並且修改的矢量要素刪除掉吧,Delete操作的Post請求需要傳輸的XML數據如下:

<?xml version='1.0' encoding='UTF-8'?>
<wfs:Transaction version="2.0.0" service="WFS"
    xmlns:fes="http://www.opengis.net/fes/2.0"
    xmlns:wfs="http://www.opengis.net/wfs/2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0
                       http://schemas.opengis.net/wfs/2.0/wfs.xsd">
    <wfs:Delete typeName="test:multipolygons">
        <fes:Filter>
            <fes:ResourceId rid="multipolygons.351"/>
        </fes:Filter>
    </wfs:Delete>
</wfs:Transaction>

經過GetFeature操作查詢後,我們發現這個矢量數據的要素個數又回到了350個,如下圖所示:

圖8.43 WFS的Transaction操作的Delete(刪除)結果

6 注意事項

除了以上四種常用的操作,WFS還有一些其他操作,有的操作還是特定版本特有的,篇幅所限筆者這裏就不介紹了。另外,相信讀者也能感受到,WFS提供的一些操作確實非常複雜繁瑣。對於空間數據的增刪改查,直接使用地理數據庫+定製的後端接口也許更爲方便安全一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章