Editing Feature directly via Geoserver
GeoServer, an open-source geospatial server, provides powerful capabilities for serving and managing geospatial data. It following OGC legacy APIs such as WMS, WFS, WCS, GWC, etc. to allow user to access data. It allows various type of data sources to be used as stores including both Vector and Raster Dataset.
If you are new to Geoserver, start by installing geoserver on Ubuntu. Windows or Mac.
One of the key features of GeoServer is its support for the Web Feature Service (WFS) standard, which allows for querying, retrieving, and modifying geospatial data over the web. In this blog, we will focus on using WFS Transaction in GeoServer to update features, which enables you to make changes to your geospatial data in a transactional manner.
? What is WFS Transaction ?
WFS Transaction is a powerful feature that allows you to modify features in a transactional manner. It supports multiple operations such as Insert, Update, Delete, which makes it perfect for updating datasets from either UI or Geoserver REST APIs.
WFS Transaction is particularly useful in scenarios where you need to update multiple features at once or ensure that changes to your data are atomic and consistent.
?? Updating Features in Geoserver
You can get code and datasets on this Github repo
?? Using REST API
Geoserver allows to Insert, Update, Delete features using REST APIs, url is generally https://localhost:8080/geoserver/<workspaces>/ows
Unfortunately at this moment, Body only supports XML format instead of JSON.
Let's understand this by example
We have this feature of ID 573
We'll try to update f_code from BH000 to AS123 to do this, XML will look like this
<wfs:Transaction service="WFS" version="1.0.0"
xmlns:topp="https://www.openplans.org/topp"
xmlns:ogc="https://www.opengis.net/ogc"
xmlns:wfs="https://www.opengis.net/wfs">
<wfs:Update typeName="feature:inwatera_ind" xmlns:feature="editmydata">
<wfs:Property><Name>f_code</Name><Value>AS123</Value></wfs:Property>
<ogc:Filter>
<ogc:FeatureId fid="inwatera_ind.573"/>
</ogc:Filter>
</wfs:Update>
</wfs:Transaction>
We'll make POST request as follows
Status 200 confirms that feature is updated. Go back to Layer Preview to confirm.
??? Using External UI
Setting up Geoserver
For this exercise I have added the water bodies dataset in editmydata workspace under editmyshapefie shape file store
I have create a style to make border blue and thick and fill as blue with opacity around 0.5 , in layer preview it looks like this.
Setting up Openlayers code
As we're going to update data using web application, I'll be using Openlayers for this. If you are new at Openlayers, checkout my video playlist to learn more.
We start by creating simple HTML file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- import openlayers css -->
<link rel="stylesheet" href="ol.css">
<!-- import custom css -->
<link rel="stylesheet" href="style.css">
<title>WFS transact</title>
</head>
<body>
<!-- Map container div -->
<div id="map" class="map"></div>
<!-- Openlayers Popup div -->
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>
<!-- import openlayers javascript -->
<script src="ol.js"></script>
<!-- import openlayers jquery -->
<script src="jQuery.js"></script>
<!-- import openlayers main -->
<script src="main.js"></script>
</body>
</html>
Custom style file will look like
.map {
width: 100%;
height: 90vh;
}
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 280px;
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: '?';
}
div#popup-content {
overflow: auto;
max-height: 200px;
margin-top: 5px;
}
div#popup-content td {
overflow-wrap: anywhere;
}
Now, In Javascript, we'll work in pieces, first we'll create all required layers, such as Base Layer, WMS Layer of Data, Vector layer to add selected feature from Geoserver
// basemap layer
const baseraster = new ol.layer.Tile({
source: new ol.source.OSM(),
name: 'basemap'
});
// Feature selected from WMS will be added here
const selectedFeat = new ol.layer.Vector({
source: new ol.source.Vector({ wrapX: false }),
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'blue',
width: 2,
}),
fill: new ol.style.Fill({
color: 'rgba(255,0,0,0.2)',
}),
}),
zIndex: 100,
});
// Geoserver WMS layer
const myWMSLayer = new ol.layer.Image({
source: new ol.source.ImageWMS({
visible: true,
ratio: 1,
url: "https://localhost:8080/geoserver/editmydata/wms",
params: {
FORMAT: 'image/png',
VERSION: '1.1.0',
LAYERS: 'editmydata:inwatera_ind',
// LAYERS: 'aai:basemap',
singleTile: false,
exceptions: 'application/vnd.ogc.se_inimage',
},
serverType: 'geoserver',
crossOrigin: 'anonymous',
}),
})
Then we'll initialise Map
// Initiate Map
var map = new ol.Map({
target: 'map',
layers: [
baseraster,
myWMSLayer,
selectedFeat
],
view: new ol.View({
center: ?[75.78487994622085, 24.19505879247387],
zoom: 5,
projection: 'EPSG:4326',
}),
});
We'll then add code for adding popup overlay and modify interaction
// Info popup start
/**
* Elements that make up the popup.
*/
const container = document.getElementById('popup');
const content = document.getElementById('popup-content');
const closer = document.getElementById('popup-closer');
/**
* Create an overlay to anchor the popup to the map.
*/
const overlay = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
/**
* Add a click handler to hide the popup.
* @return {boolean} Don't follow the href.
*/
closer.onclick = function () {
overlay.setPosition(undefined);
closer.blur();
return false;
};
const modify = new ol.interaction.Modify({ source: selectedFeat.getSource() });
map.addOverlay(overlay);
map.addInteraction(modify);
So far we have created static map with WMS layer
Now we'll create function to click on WMS link to grab feature, after grabbing feature, we'll use geometry to create vector feature and geometry to populate table which then we'll be shown in popup.
// get feature from WMS , populate selectedFeat layer
const editF = (evt) => {
identifyCord = evt.coordinate;
fetch(
myWMSLayer
.getSource()
.getFeatureInfoUrl(
identifyCord,
map.getView().getResolution(),
'EPSG:4326',
{ INFO_FORMAT: 'application/json' }
)
)
.then(responses => responses.json())
.then(function (data) {
selectedFeat.getSource().clear();
if (data.features.length > 0) {
let feature = new ol.format.GeoJSON().readFeature(data.features[0], {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:4326',
});
feature.set('geometryName', data.features[0].geometry_name);
selectedFeat.getSource().addFeature(feature);
let featureProp = data.features[0].properties;
let tr = '';
Object.keys(featureProp).forEach((x) => {
tr +=
'<tr>' +
'<th>' +
x +
'</th>' +
'<td>' +
'<input for="' +
x +
'" type="text" class="form-control properties" value="' +
featureProp[x] +
'">' +
'</td>' +
'</tr>';
});
let identifytable =
'<table width="100%">' +
'<tbody>' +
'<tr>' +
'<th>Name</th>' +
'<th>Values</th>' +
'</tr>' +
tr +
'</tbody>' +
'</table>' +
'<button class="btn btn-primary" onclick="updateF()">Update</button>';
content.innerHTML = identifytable;
overlay.setPosition(identifyCord);
}
});
}
// Attach function to left click on map
map.on('singleclick', editF);
This will allow you to click on any feature and you'll see it getting selected, and properties in table format
Finally, we'll write a function that we need to execute on click of Update button as we can see in popup.
const updateF = () => {
//get all properties
let elements = document.getElementsByClassName('properties');
//get selected features
let feature = selectedFeat.getSource().getFeatures()[0];
//get properties from feature which will be used later
let properties = feature.getProperties();
// Delete existing geometry and geometry column name
delete properties.geometry;
delete properties.geometryName;
Object.keys(properties).forEach((i) => {
properties[i] = Array.from(elements).find(
(e) => e.getAttribute('for') === i
).value;
});
// clone selected features
let clone = new ol.Feature(feature.getGeometry());
// set id of clone as same as feature
clone.setId(feature.getId());
// set new properties to clone featueres
clone.setProperties(properties);
// update geometry name of clone to same as selected features
clone.setGeometryName(feature.getProperties().geometryName);
let formatGML = new ol.format.GML({
featureNS: 'editmydata',
featureType: 'inwatera_ind',
featurePrefix: 'new',
srsName: 'CRS:84',
formatOptions: {
xy: false,
},
});
// create transaction from feature
// first parameter - for adding new feature
// second parameter - for updating feature
// third parameter - for deleting feature
node = new ol.format.WFS().writeTransaction(null, [clone], null, formatGML);
// Hit Geoserver REST API
let serializer = new XMLSerializer();
$.ajax('https://localhost:8080/geoserver/editmydata/ows', {
service: 'WFS',
type: 'POST',
dataType: 'xml',
processData: false,
contentType: 'text/xml',
data: serializer.serializeToString(node),
}).done((response) => {
//refreshes the tile wms-- in order to get the updated params
myWMSLayer.getSource().updateParams({ ol3_salt: Math.random() });
selectedFeat.getSource().clear();
overlay.setPosition(undefined);
closer.blur();
console.log('edited successfully')
});
}
This is how you can update features. In similar manner we can create function for adding new features and deleting new feature by making following function properly.
For adding completely new feature
node = new ol.format.WFS().writeTransaction([new_feature], null, null, formatGML);
For deleting existing feature
node = new ol.format.WFS().writeTransaction( null, null,[feature_to_delete], formatGML);
??? Geoserver settings to look for
It is quite possible that even after following steps you might get errors, or maybe you are now scared that what if someone get to know about this and they start ?? in your Geoserver.
?? If Transactions are giving errors
Click on Services ?? WFS , here you'll see all settings related to WFS protocol. Look for Service Level , either Transaction or complete should be selected. This allows WFS Transactions
?? Make Transactions secure
Under Security ?? Services ?? add new rule and create following setting.
As per this rule, we are allowing ADMIN, GROUP_ADMIN and ROLE_AUTHENTICATED to allow WFS Transaction.
This way someone who is trying to hit API without any credentials will fail
To make it workable , we'll need to pass username and password to the request.
Project Manager - GIS
1 年??
Map Discovery iOS,Android, & Windows supports OGC WFS-T TRANSACTIONAL EDITING DATA CREATION and GeoJSON creation https://mapdiscovery.techmaven.net/ View attribute table and advanced tools and widgets Easily connect to mapping services.
Software Development Engineer
1 年Much appreciated! Thanks
Senior Web GIS Developer at Odisha Space Application Center(ORSAC) || Freelance Full Stack Developer|| Openlayers|| Cesium Js || ArcGIS JavaScript API || Geoserver ||ARCGIS SERVER|| 2D || 3D GIS || Google Earth Engine
1 年Good one