Prerequisites
You need basic JavaScript and Leaflet framework knowledge.
Introduction
Despite the evolution of web-mapping frameworks, editing vector geographic data mostly occurs in desktop applications. Today - in 2015 - it is high time to move on to editing in browsers.
There are several open source libraries for rendering web maps, for example, OpenLayers
and Leaflet
. Quite a long time ago, we made a choice for Leaflet
and we still use it active in our projects. We would like to use Leaflet
for geodata editing too and to be able to integrate with the existing spatial data repositories.
Generally GIS-servers (geoserver, mapserver) are used to achieve this purpose, they are able to publish a large variety of data formats due to OGC standards. So, WMS protocol perfectly do the visualization of the static
map, but does not implement editing functions, for which is reasonable to use WFS protocol with the ability to change data. WMS requests return already rendered tiles, but WFS requests - raw information, "source code" for these tiles. Leaflet supports extension modules - plugins, so you can look for a necessary component or write your own. Since we didn’t find a proper modules for Leaflet, we started out own implementation.
According to the requests statistic on leaflet.uservoice.com, this module is of great interest not only for us.
Description of WFS-T and Its Purpose
OGC Web Feature Service standard allows to request and edit (in the case of PostScript "-T" - transaction) spatial data, using queries to the server. CRUD functions are divided into the following requests by that standard: GetFeature
for reading and Transaction
for others.
Two ways can be used for the interaction between the client and the server: the first uses XML and POST requests, the second - key\value pair and GET
requests.
Data can be obtained using GET
requests like:
%WFSServerURL%?service=WFS&version=1.0.0&request=GetFeature&
typeName=osm_perm_region:perm_water_polygon&maxFeatures=50&outputFormat=application/json
service=WFS | The service type is always the same |
version=1.0.0 | The version of WFS standard. At the moment, there are 3 versions: 1.0.0, 1.1.0, 2.0.0. We use version 1.1.0, because some manufacturers of server side doesn’t implement version 2.0.0 |
request=GetFeature | Request type, also supported values: GetCapabilities , DescriberFeatureType |
typeName=osm_perm_region:perm_water_polygon | The type name of data published by WFS-server |
maxFeature=50 | The maximum number of objects, which will be returned by the server |
outputFormat=application/json | The format of the data, that will be returned by the server. Only one data format - GML is regulated by the standard, but some implementations can use formats, different from GML, for example, geoserver is able to return data in geoJson format |
In some cases to create\change\delete data, you can also use key-value pairs in GET
request, but making POST
request to %WFSServerURL%
with data in XML format gives more possibilities.
GML is used to describe spatial data. Server sends data using this format natively (in case of unspecified flag outputFormat
) and only this format can be used to make requests for data changes. For example, point [0, 0]
in GML can be represented in the following way:
<gml:point srsname="http://www.opengis.net/def/crs/EPSG/0/4326">
<gml:pos srsdimension="2">0.0 0.0</gml:pos>
</gml:point>
Another OGC standard specifies filters which can be used to set limitations for delete
and update
queries. Initially, only GmlObjectId
is enough, as it is used to update / delete objects. In course of time, other filters will be needed.
Example for ID=1
:
<ogc:filter>
<ogc:gmlfeatureid gml:id="1">
</ogc:gmlfeatureid></ogc:filter>
Creation of Leaflet Plugin
As mentioned above, Leaflet has a well-designed module infrastructure, so we need to be able to write these plugins. Introductory to plugin creation and a sample of ILayer
implementation are provided on the Leaflet homepage. There are also many articles on other resources in web.
We will need to create our own layer to get data form the service and render it on loading. Some plugins were found to read WFS, all of them are inherited from L.GeoJSON
layer and read data only in the GeoJSON format. But the standard does not oblige the manufacturers of the server-side to provide data in geoJson, GML format is mandatory. Looking at the reading process in OpenLayers
, we took the idea from there: to use a separate class for reading, that can understand the needed format. As well as L.GeoJSON
, we inherited our implementation from L.FeatureGroup
. Two reading formats were implemented, they are GML and GeoJSON.
Receiving Data
Executed by AJAX request and given to the reading class for processing, here we just pass the transformation geoJson in markers\polygons\polylines to standard Leaflet
function:
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 parse GML - finally, we have 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
Some functions were implemented, they remember the changes while interacting with the plugins of visual editing of Leaflet
objects (leaflet.draw
, Leaflet.Editable
). As soon as the editing is finished, the save()
method must be called, as it generates the GML description of the changes — element “wfs:Transaction
", and, for each of the changed objects, the appropriate action: “wfs:Insert
”, “wfs:Update
”, “wfs:Delete
”. After that AJAX request is made.
Example of the subscription to Leaflet.Editable
plugin events:
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);
});
The function of the interpretation in the description of GML geometry was implement for each Leaflet
primitive (Marker
, Polyline
, Polygon
, etc.), for example, for the 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
Read Only
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);
})
Demo
Editing
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');
Demo
Plans for the Plugin Development
The possibility of using different versions of WFS;
Support of other aspects of OGC Filter Encoding standard.
The Source Code and Development Process
The project lives on GitHub. Grunt is used for automation. For testing a bunch of karma+mocha+chai+sinon is used. If you wish to participate, you are welcome.
Links
- OGC standards: WMS, WFS, GML, Filter Encoding
- Description of some of the standards - live.osgeo.org
- leaflet.wfs-t - found wfst plugin for leaflet, as for minuses — alpha version, building queries(xml) by concatenation, only geojson format.
- geoserver.org - one of the open GIS servers with the support of WFST, the sample data is published there.