Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Editing Spatial Data in Leaflet

3.80/5 (6 votes)
31 Aug 2015CPOL5 min read 22.8K  
Realization of leaflet plugin for editing spatial data

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:

XML
<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:

XML
<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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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

JavaScript
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

JavaScript
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

  1. OGC standards: WMS, WFS, GML, Filter Encoding
  2. Description of some of the standards - live.osgeo.org
  3. leaflet.wfs-t - found wfst plugin for leaflet, as for minuses — alpha version, building queries(xml) by concatenation, only geojson format.
  4. geoserver.org - one of the open GIS servers with the support of WFST, the sample data is published there.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)