Editing Feature directly via Geoserver

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

No alt text provided for this image

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

No alt text provided for this image

Status 200 confirms that feature is updated. Go back to Layer Preview to confirm.

No alt text provided for this image

??? Using External UI

Setting up Geoserver

For this exercise I have added the water bodies dataset in editmydata workspace under editmyshapefie shape file store

No alt text provided for this image

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.

No alt text provided for this image

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

No alt text provided for this image

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

No alt text provided for this image

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')
    });
  }        
No alt text provided for this image

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.

No alt text provided for this image

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

No alt text provided for this image


To make it workable , we'll need to pass username and password to the request.


Tanish Brown

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.

回复
Ibrahim Anastas

Software Development Engineer

1 年

Much appreciated! Thanks

回复
BIKASH RANJAN MALLICK

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

回复

要查看或添加评论,请登录

Krishna Lodha的更多文章

  • How to Generate Contour on the fly on GeoServer

    How to Generate Contour on the fly on GeoServer

    Digital Elevation Models (DEM) are crucial for terrain analysis, and contour lines are one of the best ways to…

    1 条评论
  • 5 Python Libraries for Earth Observation

    5 Python Libraries for Earth Observation

    Earth observation has become a cornerstone of the GIS industry, driven by the exponential growth in satellite missions…

    2 条评论
  • Digital Terrain Models in GIS: A Practitioner’s Guide to DSM, DTM, and DEM

    Digital Terrain Models in GIS: A Practitioner’s Guide to DSM, DTM, and DEM

    Picture yourself flying over a city in a helicopter. Looking down, you see buildings piercing the sky, trees dotting…

    3 条评论
  • Get Sentinel Data within seconds in?Python

    Get Sentinel Data within seconds in?Python

    With the development of STAC specification, accessing Satellite datasets have become easy and standard approach. This…

    1 条评论
  • The Ultimate Guide of Downloading OSM Data using ChatGPT

    The Ultimate Guide of Downloading OSM Data using ChatGPT

    Open Street Map is a collaborative mapping project that aims to create a free and editable map of the world. Unlike…

    7 条评论
  • Simple way of authentication for Geoserver

    Simple way of authentication for Geoserver

    Geoserver is an amazing tool which allows users to share spatial data of vector and raster type using OGC services like…

    1 条评论
  • Extending Openlayers capabilites

    Extending Openlayers capabilites

    If you have used openlayers in the past, you know it's capabilities very well. Openlayers allows users to put…

    10 条评论
  • Custom popups in Geoserver

    Custom popups in Geoserver

    When we upload any layer to Geoserver, we can check the data on click by going in to Layer preview and display layer as…

    1 条评论
  • Install Geoserver on Mac OS

    Install Geoserver on Mac OS

    I use mac for most of my development purpose, and it’s already lil scary trying to install executables and libraries…

    3 条评论
  • Install Geoserver on Windows

    Install Geoserver on Windows

    I was recently trying to install Geoserver on a windows machine, until now I was using version 2.15, which had a…

    6 条评论