- 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檔案,如下圖所示:
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所示:
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個要素資訊,如下圖所示:
很多時候返回所有的要素資訊並不是我們想要的,我們希望進行空間查詢,例如查詢一個矩形範圍內要素,那麼可以透過在瀏覽器中輸入如下地址來實現:
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個了:
如果我們要進行屬性查詢,例如查詢特定要素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表示要素欄位名。此時返回的結果可以看到該要素具體的屬性值,如下圖所示:
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中,如下圖所示:
在這個示例中,使用了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請求不能做到的。
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 )就可以看到剛剛插入的新的要素,如下圖所示:
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操作查詢替換的要素如下圖所示,可以看到我們將一個四邊形要素替換成了三角形:
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操作查詢要素的結果如下圖所示:
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個,如下圖所示:
6 注意事項
除了以上四種常用的操作,WFS還有一些其他操作,有的操作還是特定版本特有的,篇幅所限筆者這裡就不介紹了。另外,相信讀者也能感受到,WFS提供的一些操作確實非常複雜繁瑣。對於空間資料的增刪改查,直接使用地理資料庫+定製的後端介面也許更為方便安全一些。