在 Leaflet 中编辑空间数据
实现 Leaflet 插件以编辑空间数据
必备组件
您需要具备基本的 JavaScript 和 Leaflet 框架知识。
引言
尽管 Web 地图框架不断发展,但矢量地理数据的编辑大多仍在桌面应用程序中进行。如今 - 在 2015 年 - 是时候将其转移到浏览器中进行编辑了。
有几个开源库可用于渲染 Web 地图,例如 OpenLayers
和 Leaflet
。很久以前,我们就选择了 Leaflet
,并且至今仍在我们的项目中积极使用。我们也希望使用 Leaflet
进行地理数据编辑,并能够与现有的空间数据存储集成。
通常,GIS 服务器(geoserver、mapserver)用于实现此目的,它们能够发布各种数据格式,因为它们遵循 OGC 标准。因此,WMS 协议非常适合可视化 静态
地图,但不支持编辑功能。对于编辑功能,使用支持数据更改的 WFS 协议是合理的。WMS 请求返回已渲染的图块,而 WFS 请求返回原始信息,“源代码”。Leaflet 支持扩展模块 - 插件,因此您可以寻找所需的组件或自己编写。由于我们没有找到合适的 Leaflet 模块,因此我们开始了自己的实现。
根据 leaflet.uservoice.com 上的请求统计数据,该模块不仅对我们来说非常重要。
WFS-T 及其目的说明
OGC Web Feature Service 标准允许使用对服务器的查询来请求和编辑(在 PostScript "-T" - 事务的情况下)空间数据。CRUD 功能根据该标准分为以下请求:用于读取的 GetFeature
和用于其他操作的 Transaction
。
客户端和服务器之间的交互可以使用两种方式:第一种使用 XML 和 POST 请求,第二种使用键值对和 GET
请求。
可以使用 GET
请求获取数据,例如:
%WFSServerURL%?service=WFS&version=1.0.0&request=GetFeature& typeName=osm_perm_region:perm_water_polygon&maxFeatures=50&outputFormat=application/json
service=WFS | 服务类型始终相同 |
version=1.0.0 | WFS 标准的版本。目前有 3 个版本:1.0.0、1.1.0、2.0.0。我们使用版本 1.1.0,因为一些服务器端制造商没有实现版本 2.0.0。 |
request=GetFeature | 请求类型,还支持的值:GetCapabilities 、DescriberFeatureType |
typeName=osm_perm_region:perm_water_polygon | WFS 服务器发布的数据类型名称 |
maxFeature=50 | 服务器将返回的对象的最大数量 |
outputFormat=application/json | 服务器返回的数据格式。标准只规定了一种数据格式 - GML,但某些实现可以使用不同于 GML 的格式,例如,geoserver 能够以 geoJson 格式返回数据。 |
在某些情况下,要创建\更改\删除数据,您也可以在 GET
请求中使用键值对,但向 %WFSServerURL%
发送 XML 格式数据的 POST
请求提供了更多可能性。
GML 用于描述空间数据。服务器使用此格式原生发送数据(在未指定标志 outputFormat
的情况下),并且只有此格式可用于发出数据更改请求。例如,GML 中的点 [0, 0]
可以表示为:
<gml:point srsname="http://www.opengis.net/def/crs/EPSG/0/4326">
<gml:pos srsdimension="2">0.0 0.0</gml:pos>
</gml:point>
另一个 OGC 标准规定了可用于限制 delete
和 update
查询的过滤器。最初,只有 GmlObjectId
就足够了,因为它用于更新/删除对象。随着时间的推移,将需要其他过滤器。
示例:ID=1
<ogc:filter>
<ogc:gmlfeatureid gml:id="1">
</ogc:gmlfeatureid></ogc:filter>
创建 Leaflet 插件
如上所述,Leaflet 具有精心设计的模块基础设施,因此我们需要能够编写这些插件。Leaflet 主页上提供了插件创建的介绍和 ILayer
实现的示例。网上还有许多其他资源的文章。
我们需要创建自己的图层以从服务中获取数据并在加载时渲染。发现了一些用于读取 WFS 的插件,它们都继承自 L.GeoJSON
图层,并且只读取 GeoJSON 格式的数据。但是,标准并不强制服务器端制造商提供 geoJson 格式的数据,GML 格式是强制性的。查看 OpenLayers
中的读取过程,我们从中获得了一个想法:使用一个单独的类进行读取,该类可以理解所需的格式。与 L.GeoJSON
一样,我们的实现继承自 L.FeatureGroup
。实现了两种读取格式:GML 和 GeoJSON。
接收数据
通过 AJAX 请求执行,并交给读取类进行处理,这里我们只是将 geoJson 转换为标记\多边形\折线,然后传递给标准的 Leaflet
函数。
var layers = [];
var geoJson = JSON.parse(response.rawData);
for (var i = 0; i < geoJson.features.length; i++) {
var layer = L.GeoJSON.geometryToLayer(geoJson.features[i],
options.pointToLayer || null, options.coordsToLatLng, null);
layer.feature = geoJson.features[i];
layers.push(layer);
}
return layers;
或解析 GML - 最后,我们得到相同的标记\多边形\折线。
var layers = [];
var xmlDoc = L.XmlUtil.parseXml(rawData);
var featureCollection = xmlDoc.documentElement;
var featureMemberNodes = featureCollection.getElementsByTagNameNS
(L.XmlUtil.namespaces.gml, 'featureMember');
for (var i = 0; i < featureMemberNodes.length; i++) {
var feature = featureMemberNodes[i].firstChild;
layers.push(this.processFeature(feature));
}
var featureMembersNode = featureCollection.getElementsByTagNameNS
(L.XmlUtil.namespaces.gml, 'featureMembers');
if (featureMembersNode.length > 0) {
var features = featureMembersNode[0].childNodes;
for (var j = 0; j < features.length; j++) {
var node = features[j];
if (node.nodeType === document.ELEMENT_NODE) {
layers.push(this.processFeature(node));
}
}
}
return layers;
编辑对象
实现了一些功能,它们在与 Leaflet
对象可视化编辑插件(leaflet.draw
、Leaflet.Editable
)的交互过程中会记住更改。一旦编辑完成,必须调用 save()
方法,因为它会生成更改的 GML 描述 - “wfs:Transaction
”元素,并且对于每个已更改的对象,都会执行相应的操作:“wfs:Insert
”、“wfs:Update
”、“wfs:Delete
”。之后会发起 AJAX 请求。
订阅 Leaflet.Editable
插件事件的示例
map.on('editable:created', function (e) {
wfst.addLayer(e.layer);
});
map.on('editable:editing', function (e) {
wfst.editLayer(e.layer);
});
map.on('editable:delete', function (e) {
wfst.removeLayer(e.layer);
});
为每个 Leaflet
原始对象(Marker
、Polyline
、Polygon
等)实现了 GML 几何描述的解析功能,例如,对于标记,它看起来像这样:
L.Marker.include({
toGml: function (crs) {
var node = L.XmlUtil.createElementNS('gml:Point', {srsName: crs.code});
node.appendChild(L.GMLUtil.posNode(L.Util.project(crs, this.getLatLng())));
return node;
}
});
使用示例
只读
var map = L.map('map').setView([0, 0], 2);
var boundaries = new L.WFS({
url: 'http://demo.opengeo.org/geoserver/ows',
typeNS: 'topp',
typeName: 'tasmania_state_boundaries',
crs: L.CRS.EPSG4326,
style: {
color: 'blue',
weight: 2
}
}).addTo(map)
.on('load', function () {
map.fitBounds(boundaries);
})
编辑
var wfst = new L.WFS.Transaction({
url: 'http://myserver/geoserver/ows',
typeNS: 'myns',
typeName: 'POIPOINT',
style: {
color: 'blue',
weight: 2
}
}).addTo(map).once('load', function () {
map.fitBounds(wfst);
wfst.enableEdit();
});
map.on('editable:created', function (e) {
wfst.addLayer(e.layer);
});
map.on('editable:editing', function (e) {
wfst.editLayer(e.layer);
});
L.easyButton('fa-save', function () {
wfst.save();
}, 'Save changes');
插件开发计划
使用不同版本 WFS 的可能性;
支持 OGC Filter Encoding 标准的其他方面。
源代码和开发过程
项目托管在 GitHub 上。Grunt 用于自动化。测试使用 karma+mocha+chai+sinon 组合。如果您想参与,我们热烈欢迎。
链接
- OGC 标准:WMS、WFS、GML、Filter Encoding
- 部分标准的说明 - live.osgeo.org
- leaflet.wfs-t - 找到了 Leaflet 的 wfst 插件,缺点是:alpha 版本,通过字符串拼接构建查询(xml),仅支持 geojson 格式。
- geoserver.org - 最早支持 WFST 的开源 GIS 服务器之一,示例数据在那里发布。