65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Leaflet 中编辑空间数据

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (6投票s)

2015 年 8 月 31 日

CPOL

5分钟阅读

viewsIcon

23085

实现 Leaflet 插件以编辑空间数据

必备组件

您需要具备基本的 JavaScript 和 Leaflet 框架知识。

引言

尽管 Web 地图框架不断发展,但矢量地理数据的编辑大多仍在桌面应用程序中进行。如今 - 在 2015 年 - 是时候将其转移到浏览器中进行编辑了。

有几个开源库可用于渲染 Web 地图,例如 OpenLayersLeaflet。很久以前,我们就选择了 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 请求类型,还支持的值:GetCapabilitiesDescriberFeatureType
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 标准规定了可用于限制 deleteupdate 查询的过滤器。最初,只有 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.drawLeaflet.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 原始对象(MarkerPolylinePolygon 等)实现了 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 组合。如果您想参与,我们热烈欢迎。

链接

  1. OGC 标准:WMSWFSGMLFilter Encoding
  2. 部分标准的说明 - live.osgeo.org
  3. leaflet.wfs-t - 找到了 Leaflet 的 wfst 插件,缺点是:alpha 版本,通过字符串拼接构建查询(xml),仅支持 geojson 格式。
  4. geoserver.org - 最早支持 WFST 的开源 GIS 服务器之一,示例数据在那里发布。
© . All rights reserved.