網路要素服務(WFS)詳解

charlee44發表於2024-01-21

目錄
  • 1. 概述
  • 2. GetCapabilities
  • 3. DescribeFeatureType
  • 4. GetFeature
    • 4.1 Get訪問方式
    • 4.2 Post訪問方式
  • 5. Transaction
    • 5.1 Insert
    • 5.2 Replace
    • 5.3 Update
    • 5.4 Delete
  • 6 注意事項

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提供的一些操作確實非常複雜繁瑣。對於空間資料的增刪改查,直接使用地理資料庫+定製的後端介面也許更為方便安全一些。

相關文章