2
votes

I'm workin on an OL3 app, where the user will be able to draw, modify and delete polygons and save the changes to GeoServer via WFS-T.

For starting point I've used the solution from here: wfs-t example app I've changed the code a little bit to use a polygon layer from my GeoServer. The draw, modify and delete polygon functions are working great, if I modify or delete a polygon it's also saved, but the new polygon creation is not saved and I can not figure out why. The original app is working without any problem.

I hope somebody also tried to use this app as a starting point and solved this problem. Could someone give me any idea what is the problem?

Tha most important part of the code:

    var dirty = {};
var formatWFS = new ol.format.WFS();
var formatGML = new ol.format.GML({
    featureNS: 'http://www.openplans.org/topp',
    featureType: 'poly',
    srsName: 'EPSG:3857'
    });
var transactWFS = function(p,f) {
    switch(p) {
    case 'insert':
        node = formatWFS.writeTransaction([f],null,null,formatGML);
        break;
    case 'update':
        node = formatWFS.writeTransaction(null,[f],null,formatGML);
        break;
    case 'delete':
        node = formatWFS.writeTransaction(null,null,[f],formatGML);
        break;
    }
    s = new XMLSerializer();
    str = s.serializeToString(node);
    $.ajax('http://localhost:8080/geoserver/wfs',{
        type: 'POST',
        dataType: 'xml',
        processData: false,
        contentType: 'text/xml',
        data: str
        }).done();
}

$('.btn-floating').hover(
        function() {
            $(this).addClass('darken-2');},
        function() {
            $(this).removeClass('darken-2');}
        );

$('.btnMenu').on('click', function(event) {
    $('.btnMenu').removeClass('orange');
    $(this).addClass('orange');
    map.removeInteraction(interaction);
    select.getFeatures().clear();
    map.removeInteraction(select);
    switch($(this).attr('id')) {

    case 'btnSelect':
        interaction = new ol.interaction.Select({
            style: new ol.style.Style({
                stroke: new ol.style.Stroke({color: '#f50057', width: 2})
                })
        });
        map.addInteraction(interaction);
        interaction.getFeatures().on('add', function(e) {
            props = e.element.getProperties();
            if (props.status){$('#popup-status').html(props.status);}else{$('#popup-status').html('n/a');}
            if (props.tiendas){$('#popup-tiendas').html(props.tiendas);}else{$('#popup-tiendas').html('n/a');}
            coord = $('.ol-mouse-position').html().split(',');
            overlayPopup.setPosition(coord);
            });
        break;

    case 'btnEdit':
        map.addInteraction(select);
        interaction = new ol.interaction.Modify({
            features: select.getFeatures()
            });
        map.addInteraction(interaction);

        snap = new ol.interaction.Snap({
            source: layerVector.getSource()
            });
        map.addInteraction(snap);

        dirty = {};
        select.getFeatures().on('add', function(e) {
            e.element.on('change', function(e) {
                dirty[e.target.getId()] = true;
                });
            });
        select.getFeatures().on('remove', function(e) {
            f = e.element;
            if (dirty[f.getId()]){
                delete dirty[f.getId()];
                featureProperties = f.getProperties();
                delete featureProperties.boundedBy;
                var clone = new ol.Feature(featureProperties);
                clone.setId(f.getId());
                transactWFS('update',clone);
                }
            });
        break;

    case 'btnDrawPoly':
        interaction = new ol.interaction.Draw({
            type: 'Polygon',
            source: layerVector.getSource()
        });
        map.addInteraction(interaction);
        interaction.on('drawend', function(e) {
            transactWFS('insert',e.feature);
        });
        break;

    case 'btnDelete':
        interaction = new ol.interaction.Select();
        map.addInteraction(interaction);
        interaction.getFeatures().on('change:length', function(e) {
            transactWFS('delete',e.target.item(0));
            interaction.getFeatures().clear();
            selectPointerMove.getFeatures().clear();
        });
        break;

    default:
        break;
    }
    });

I'm using a single shape file as data store.

The request to the GeoServer after finishing a polygon:

 <Transaction xmlns="http://www.opengis.net/wfs" service="WFS" version="1.1.0" xsi:schemaLocation="http
    ://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" xmlns:xsi="http://www.w3.org/2001
    /XMLSchema-instance"><Insert><poly xmlns="http://www.openplans.org/topp"><geometry><Polygon xmlns="http
    ://www.opengis.net/gml"><exterior><LinearRing><posList>2274170.418847337 5923526.286802612 2329612.7433635183
     5979783.939620501 2373640.4716557795 5936979.203780803 2330835.735816081 5891728.483035979 2274170.418847337
     5923526.286802612</posList></LinearRing></exterior></Polygon></geometry></poly></Insert></Transaction
    >

The response from GeoServer:

<?xml version="1.0" encoding="UTF-8"?><wfs:TransactionResponse xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:sf="http://www.openplans.org/spearfish" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http
://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows"
 xmlns:tiger="http://www.census.gov" xmlns:topp="http://www.openplans.org/topp" xmlns:xlink="http://www
.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1.0" xsi:schemaLocation
="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd"><wfs:TransactionSummary
><wfs:totalInserted>1</wfs:totalInserted><wfs:totalUpdated>0</wfs:totalUpdated><wfs:totalDeleted>0</wfs
:totalDeleted></wfs:TransactionSummary><wfs:TransactionResults/><wfs:InsertResults><wfs:Feature><ogc
:FeatureId fid="new0"/></wfs:Feature></wfs:InsertResults></wfs:TransactionResponse>
2
To answer this we need to know at least the XML sent to the server for the Inserts, what type of datastore you are using and what error is logged in the GeoServer log file.Ian Turton
I've added more detials to the description.János Dolleschall

2 Answers

3
votes

That code sample looks familiar... I wrote this ages ago and surprised this is still working and how many people have used it since. I am pretty certain that the problem is that the geometry column is not called geometry. I believe this must always be called geometry. I have update the example since to work with Geoserver 2.8 and OpenLayers 3.16.

In my setup I use a simple geometry field in the Postgis table. This is not a production environment but allows you to have all types of geometry (point, line, polygon) in the same table. I do not define the EPSG code here but I always use EPSG:3857 during development. If you set the table like this you can test whether the WFS-T posts work.

CREATE TABLE wfs_geom
(
  id bigint NOT NULL,
  geometry geometry,
  CONSTRAINT wfs_geom_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE wfs_geom
  OWNER TO geoserver;

This is in a new jsfiddle but I can also put this in a stackoverflow code snippet.

https://jsfiddle.net/goldrydigital/13Lwsfmf/

var formatWFS = new ol.format.WFS();

var formatGML = new ol.format.GML({
    featureNS: 'https://geolytix.net/wfs',
    featureType: 'wfs_geom',
    srsName: 'EPSG:3857'
});

var xs = new XMLSerializer();

var sourceWFS = new ol.source.Vector({
    loader: function (extent) {
        $.ajax('https://maps.geolytix.net/geoserver/geolytix.wfs/wfs', {
            type: 'GET',
            data: {
                service: 'WFS',
                version: '1.1.0',
                request: 'GetFeature',
                typename: 'wfs_geom',
                srsname: 'EPSG:3857',
                bbox: extent.join(',') + ',EPSG:3857'
            }
        }).done(function (response) {
            sourceWFS.addFeatures(formatWFS.readFeatures(response));
        });
    },
    //strategy: ol.loadingstrategy.tile(ol.tilegrid.createXYZ()),
    strategy: ol.loadingstrategy.bbox,
    projection: 'EPSG:3857'
});

var layerWFS = new ol.layer.Vector({
    source: sourceWFS
});

var interaction;

var interactionSelectPointerMove = new ol.interaction.Select({
    condition: ol.events.condition.pointerMove
});

var interactionSelect = new ol.interaction.Select({
    style: new ol.style.Style({
        stroke: new ol.style.Stroke({
            color: '#FF2828'
        })
    })
});

var interactionSnap = new ol.interaction.Snap({
    source: layerWFS.getSource()
});

var map = new ol.Map({
    target: 'map',
    controls: [],
    interactions: [
        interactionSelectPointerMove,
        new ol.interaction.MouseWheelZoom(),
        new ol.interaction.DragPan()
    ],
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM({
                url: 'https://cartodb-basemaps-{a-d}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}.png',
                opaque: false,
                attributions: []
            })
        }),
        layerWFS
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([-1.7, 53.2]),
        zoom: 6
    })
});

//wfs-t
var dirty = {};
var transactWFS = function (mode, f) {
    var node;
    switch (mode) {
        case 'insert':
            node = formatWFS.writeTransaction([f], null, null, formatGML);
            break;
        case 'update':
            node = formatWFS.writeTransaction(null, [f], null, formatGML);
            break;
        case 'delete':
            node = formatWFS.writeTransaction(null, null, [f], formatGML);
            break;
    }
    var payload = xs.serializeToString(node);
    $.ajax('https://maps.geolytix.net/geoserver/geolytix.wfs/wfs', {
        type: 'POST',
        dataType: 'xml',
        processData: false,
        contentType: 'text/xml',
        data: payload
    }).done(function() {
        sourceWFS.clear();
    });
};

$('button').click(function () {
    $(this).siblings().removeClass('btn-active');
    $(this).addClass('btn-active');
    map.removeInteraction(interaction);
    interactionSelect.getFeatures().clear();
    map.removeInteraction(interactionSelect);

    switch ($(this).attr('id')) {

        case 'btnEdit':
            map.addInteraction(interactionSelect);
            interaction = new ol.interaction.Modify({
                features: interactionSelect.getFeatures()
            });
            map.addInteraction(interaction);
            map.addInteraction(interactionSnap);
            dirty = {};
            interactionSelect.getFeatures().on('add', function (e) {
                e.element.on('change', function (e) {
                    dirty[e.target.getId()] = true;
                });
            });
            interactionSelect.getFeatures().on('remove', function (e) {
                var f = e.element;
                if (dirty[f.getId()]) {
                    delete dirty[f.getId()];
                    var featureProperties = f.getProperties();
                    delete featureProperties.boundedBy;
                    var clone = new ol.Feature(featureProperties);
                    clone.setId(f.getId());
                    transactWFS('update', clone);
                }
            });
            break;

        case 'btnPoint':
            interaction = new ol.interaction.Draw({
                type: 'Point',
                source: layerWFS.getSource()
            });
            map.addInteraction(interaction);
            interaction.on('drawend', function (e) {
                transactWFS('insert', e.feature);
            });
            break;

        case 'btnLine':
            interaction = new ol.interaction.Draw({
                type: 'LineString',
                source: layerWFS.getSource()
            });
            map.addInteraction(interaction);
            interaction.on('drawend', function (e) {
                transactWFS('insert', e.feature);
            });
            break;

        case 'btnArea':
            interaction = new ol.interaction.Draw({
                type: 'Polygon',
                source: layerWFS.getSource()
            });
            interaction.on('drawend', function (e) {
                transactWFS('insert', e.feature);
            });
            map.addInteraction(interaction);
            break;

        case 'btnDelete':
            interaction = new ol.interaction.Select();
            interaction.getFeatures().on('add', function (e) {
                transactWFS('delete', e.target.item(0));
                interactionSelectPointerMove.getFeatures().clear();
                interaction.getFeatures().clear();
            });
            map.addInteraction(interaction);
            break;

        default:
            break;
    }
});
html,
body {
  height: 100%;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
}

.map {
  height: 100%;
  width: 100%;
}

#btnPoint {
  position: absolute;
  top: 10px;
  left: 10px;
}

#btnLine {
  position: absolute;
  top: 10px;
  left: 80px;
}

#btnArea {
  position: absolute;
  top: 10px;
  left: 150px;
}

#btnEdit {
  position: absolute;
  top: 10px;
  left: 220px;
}

#btnDelete {
  position: absolute;
  top: 10px;
  left: 290px;
}

.btn-active {
  background-color: #0d47a1 !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.16.0/ol.css" type="text/css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.indigo-pink.min.css">
    <script src="https://code.getmdl.io/1.1.3/material.min.js"></script>
</head>
<body>
    <div id="map" class="map"></div>
    <button id="btnPoint" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
      <i class="material-icons">add_location</i>
    </button>
    <button id="btnLine" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
      <i class="material-icons">timeline</i>
    </button>
    <button id="btnArea" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
      <i class="material-icons">signal_cellular_null</i>
    </button>
    <button id="btnEdit" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
      <i class="material-icons">build</i>
    </button>
    <button id="btnDelete" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
      <i class="material-icons">delete</i>
    </button>
</body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.14/proj4.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.16.0/ol.js"></script>
</html>
1
votes

GeoServer is actually correctly processing your request, the problem is that what you are trying to do is not allowed by your underlying datastore. Shapefiles must have a geometry attribute called the_geom so when you send a geometry called poly the shapefile writer ignores it when writing the feature. If you were using a database (e.g. PostGIS) then everything would be fine.