Despite the development of cartographic web frameworks, the editing of vector geographic data is still, for the most part, happening in desktop applications. In the yard of 2015, it’s time to move on to editing in browsers.
There are several open libraries for drawing web maps, for example, OpenLayers and Leaflet. Quite a long time ago, our choice fell on Leaflet and we continue to actively use it in the implementation of projects. To edit the geodata, I would like to use it and, at the same time, be able to integrate with existing spatial data stores.
To achieve the latter goal, as a rule, GIS servers (geoserver, mapserver) are used, which are able to publish a large variety of data formats according to
standards OGC . Thus, the
WMS protocol does an excellent job with the visualization function of the finished map, but does not imply an editing function, for which it is reasonable to use the
WFS protocol with the ability to change data. Requests to WMS return already drawn tiles - pictures, and to WFS - raw information, “source code” behind these tiles. Leaflet supports extension modules, respectively, you can search for the finished component, or write your own. Since the search for ready-made modules for Leaflet did not give satisfactory results, we started our own implementation.
')
According to statistics of requests for
leaflet.uservoice.com it is clear that this module is interesting not only for us.
Let's start with a description of the WFS-T and what it is used for
The OGC Web Feature Service standard allows you to query and edit (in the case of the “-T" - transaction) the spatial data using server requests. The CRUD standard functions are divided into GetFeature read and Transaction requests for the rest.
Two ways can be used for client and server interaction: the first uses XML and POST requests, the second uses key / value pairs and GET requests.
You can get data using GET requests of the form:
% WFSServerURL%? Service = WFS & version = 1.0.0 & request = GetFeature & typeName = osm_perm_region: perm_water_polygon & maxFeatures = 50 & outputFormat = application / json,
service = WFS | service type is always the same |
version = 1.0.0 | WFS standard version. At the moment there are 3 versions: 1.0.0, 1.1.0, 2.0.0. We will use version 1.1.0, since 2.0.0 is not implemented by all server-side manufacturers. |
request = GetFeature | request type |
typeName = osm_perm_region: perm_water_polygon | type of data published by the WFS server |
maxFeature = 50 | the number of objects that will be returned by the server |
outputFormat = application / json | The format of the data to be returned by the server. The standard regulates only one data format - GML, but some implementations may use other than GML, for example, geoserver can give data to geoJson |
In some cases, you can also use key-value pairs in a GET request to create \ change \ delete data, but performing a POST request on% WFSServerURL% with XML data gives more options.
Examples of changing objects:
Creating<wfs:Transaction service="WFS" version="1.0.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://www.openplans.org/topp http://localhost:8080/geoserver/wfs/DescribeFeatureType?typename=topp:tasmania_roads"> <wfs:Insert> <topp:tasmania_roads> <topp:the_geom> <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"> <gml:lineStringMember> <gml:LineString> <gml:coordinates decimal="." cs="," ts=" "> 494475.71056415,5433016.8189323 494982.70115662,5435041.95096618 </gml:coordinates> </gml:LineString> </gml:lineStringMember> </gml:MultiLineString> </topp:the_geom> <topp:TYPE>alley</topp:TYPE> </topp:tasmania_roads> </wfs:Insert> </wfs:Transaction>
Update <wfs:Transaction service="WFS" version="1.0.0" xmlns:topp="http://www.openplans.org/topp" xmlns:ogc="http://www.opengis.net/ogc" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"> <wfs:Update typeName="topp:tasmania_roads"> <wfs:Property> <wfs:Name>the_geom</wfs:Name> <wfs:Value> <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"> <gml:lineStringMember> <gml:LineString> <gml:coordinates>500000,5450000,0 540000,5450000,0</gml:coordinates> </gml:LineString> </gml:lineStringMember> </gml:MultiLineString> </wfs:Value> </wfs:Property> <ogc:Filter> <ogc:FeatureId fid="tasmania_roads.1"/> </ogc:Filter> </wfs:Update> </wfs:Transaction>
Deletion <wfs:Transaction service="WFS" version="1.0.0" xmlns:cdf="http://www.opengis.net/cite/data" xmlns:ogc="http://www.opengis.net/ogc" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp"> <wfs:Delete typeName="topp:tasmania_roads"> <ogc:Filter> <ogc:PropertyIsEqualTo> <ogc:PropertyName>topp:TYPE</ogc:PropertyName> <ogc:Literal>alley</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> </wfs:Delete> </wfs:Transaction>
GML is used to describe spatial data. In this format, the server returns data by default (with the outputFormat flag not set), and only in it requests are accepted when data changes. For example, the point [0, 0] in GML can be represented as follows:
<gml:Point srsName="http://www.opengis.net/def/crs/EPSG/0/4326"> <gml:pos srsDimension="2">0.0 0.0</gml:pos> </gml:Point>
Filters are used to limit requests for deletion and alteration of data — another of the OGC standards. In the beginning it will be enough - GmlObjectId, it is used to update / delete objects. Further other filters will be required.
Example for ID = 1:
<ogc:Filter> <ogc:GmlFeatureId gml:id=1/> </ogc:Filter>
Creating Leaflet Plugin
As mentioned above, Leaflet has a well-thought-out modular infrastructure, so you need to be able to write these very modules. An introduction to creating a plugin is on
the Leaflet site , and an example of the implementation of
ILayer . Also there are several articles on Habré.
We need to create our own layer, which, when loaded, will receive data from the service and draw it. Several plug-ins were found for reading the WFS, all of them inherited from the L.GeoJSON layer and read the data right away in the right format. But the standard does not oblige the server-side manufacturers to provide data to geoJson (I have seen such a possibility only for geoserver), but the GML format is obligatory. Having looked at how reading was done in OpenLayers, we took the idea from there: for reading, use a separate class that knows how to understand the format you want. Like L.GeoJSON, we inherited our implementation from L.FeatureGroup. 2 GML and GeoJSON reading formats were implemented.
Data acquisition
This is done by the AJAX request and sent to the reading class for processing; here we simply transfer the geoJson transformation to the markers \ polygons \ polylines to the depths of the Leaflet itself:
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;
Or GML parsim - the output is the same markers \ polygons \ polylines:
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;
Editing objects
Functions are written which, interacting with the plug-ins of visual editing of Leaflet objects (leaflet.draw, Leaflet.Editable), remember the changes made. After editing is finished, you need to call the save () method, which will form a GML description of the changes - the “wfs: Transaction" element, and, for each of the changed objects, the corresponding Action (wfs: Insert, wfs: Update, wfs: Delete) After this, an AJAX request is made.
Example of subscribing to events for Leaflet.Editable plugin:
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); });
For each Leaflet primitive (Marker, Polyline, Polygon, etc.), the function of its translation into the description of GML geometry was written, for example, for a marker it looks like this:
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; } });
Examples of using
Only reading
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); })
linkEditing
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');
linkPlans for the development of the plugin
- Migration to Leaflet 0.8 (the interiors of the Multi-classes have changed, and the polylines with polygons have got the ring's);
- Ability to use different versions of WFS;
- Support for other items of the standard OGC Filter Encoding.
Sources and development
The project lives on
GitHub . For automation, Grunt is used. For testing using a bunch of karma + mocha + chai + sinon. Those who wish to participate - welcome.
Links
- ↑ OGC Standards: WMS , WFS , GML , Filter Encoding
- Description of some standards in Russian - live.osgeo.org
- leaflet.wfs-t is the found wfst plugin for leaflet, from the minuses it is abandoned, alpha version, query assembly (xml) by concatenation, only markers and polygons, only geojson.
- geoserver.org is one of the open gis servers with WFST support, it contains demo data.