Merge pull request #62394 from elpaso/server-wfs-3d-z-gml-part-2-server

[server] Server WFS Z gml transactional support
This commit is contained in:
Alessandro Pasotti 2025-06-26 09:19:40 +02:00 committed by GitHub
commit c637e42516
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 194 additions and 1 deletions

View File

@ -234,30 +234,38 @@ QString QgsWfsDescribeFeatureTypeGml::getGmlGeometryType( const QgsVectorLayer *
case QgsWfsParameters::Format::GML2:
switch ( wkbType )
{
case Qgis::WkbType::PointZ:
case Qgis::WkbType::Point25D:
case Qgis::WkbType::Point:
return QStringLiteral( "gml:PointPropertyType" );
case Qgis::WkbType::LineStringZ:
case Qgis::WkbType::LineString25D:
case Qgis::WkbType::LineString:
return QStringLiteral( "gml:LineStringPropertyType" );
case Qgis::WkbType::PolygonZ:
case Qgis::WkbType::Polygon25D:
case Qgis::WkbType::Polygon:
return QStringLiteral( "gml:PolygonPropertyType" );
case Qgis::WkbType::MultiPointZ:
case Qgis::WkbType::MultiPoint25D:
case Qgis::WkbType::MultiPoint:
return QStringLiteral( "gml:MultiPointPropertyType" );
case Qgis::WkbType::MultiCurveZ:
case Qgis::WkbType::MultiCurve:
case Qgis::WkbType::MultiLineString25D:
case Qgis::WkbType::MultiLineStringZ:
case Qgis::WkbType::MultiLineString:
return QStringLiteral( "gml:MultiLineStringPropertyType" );
case Qgis::WkbType::MultiSurfaceZ:
case Qgis::WkbType::MultiSurface:
case Qgis::WkbType::MultiPolygon25D:
case Qgis::WkbType::MultiPolygon:
case Qgis::WkbType::MultiPolygonZ:
return QStringLiteral( "gml:MultiPolygonPropertyType" );
default:
@ -266,16 +274,19 @@ QString QgsWfsDescribeFeatureTypeGml::getGmlGeometryType( const QgsVectorLayer *
case QgsWfsParameters::Format::GML3:
switch ( wkbType )
{
case Qgis::WkbType::PointZ:
case Qgis::WkbType::Point25D:
case Qgis::WkbType::Point:
return QStringLiteral( "gml:PointPropertyType" );
case Qgis::WkbType::LineString25D:
case Qgis::WkbType::LineString:
case Qgis::WkbType::LineStringZ:
return QStringLiteral( "gml:LineStringPropertyType" );
case Qgis::WkbType::Polygon25D:
case Qgis::WkbType::Polygon:
case Qgis::WkbType::PolygonZ:
return QStringLiteral( "gml:PolygonPropertyType" );
case Qgis::WkbType::MultiPoint25D:
@ -283,13 +294,16 @@ QString QgsWfsDescribeFeatureTypeGml::getGmlGeometryType( const QgsVectorLayer *
return QStringLiteral( "gml:MultiPointPropertyType" );
case Qgis::WkbType::MultiCurve:
case Qgis::WkbType::MultiCurveZ:
case Qgis::WkbType::MultiLineString25D:
case Qgis::WkbType::MultiLineString:
case Qgis::WkbType::MultiLineStringZ:
return QStringLiteral( "gml:MultiCurvePropertyType" );
case Qgis::WkbType::MultiSurface:
case Qgis::WkbType::MultiPolygon25D:
case Qgis::WkbType::MultiPolygon:
case Qgis::WkbType::MultiPolygonZ:
return QStringLiteral( "gml:MultiSurfacePropertyType" );
default:

View File

@ -13,8 +13,17 @@ __date__ = "28/08/2015"
__copyright__ = "Copyright 2015, The QGIS Project"
from qgis.testing import unittest
from qgis.core import QgsProject, QgsVectorLayer, QgsGeometry, QgsOgcUtils
from qgis.server import (
QgsBufferServerRequest,
QgsBufferServerResponse,
QgsServer,
QgsServerFilter,
QgsServerRequest,
)
from test_qgsserver_accesscontrol import XML_NS, TestQgsServerAccessControl
from test_qgsserver import QgsServerTestBase
from osgeo import ogr
WFS_TRANSACTION_INSERT = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:Transaction {xml_ns}>
@ -32,6 +41,43 @@ WFS_TRANSACTION_INSERT = """<?xml version="1.0" encoding="UTF-8"?>
</wfs:Insert>
</wfs:Transaction>"""
WFS_TRANSACTION_Z_INSERT = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:Transaction {xml_ns}>
<wfs:Insert idgen="GenerateNew">
<qgs:{layer_name}>
<qgs:geometry>
{gml}
</qgs:geometry>
<qgs:gid>1</qgs:gid>
<qgs:name>name</qgs:name>
<qgs:color>black</qgs:color>
</qgs:{layer_name}>
</wfs:Insert>
</wfs:Transaction>"""
WFS_TRANSACTION_Z_UPDATE = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:Transaction {xml_ns}>
<wfs:Update typeName="{layer_name}">
<wfs:Property>
<wfs:Name>color</wfs:Name>
<wfs:Value>red</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>name</wfs:Name>
<wfs:Value>new_name</wfs:Value>
</wfs:Property>
<wfs:Property>
<wfs:Name>geometry</wfs:Name>
<wfs:Value>
{gml}
</wfs:Value>
</wfs:Property>
<ogc:Filter>
<ogc:FeatureId fid="{id}"/>
</ogc:Filter>
</wfs:Update>
</wfs:Transaction>"""
WFS_TRANSACTION_UPDATE = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:Transaction {xml_ns}>
<wfs:Update typeName="db_point">
@ -252,5 +298,138 @@ class TestQgsServerAccessControlWFSTransactional(TestQgsServerAccessControl):
)
class TestQgsServerAccessControlWFSTransactionalZ(QgsServerTestBase):
"""Test transactions with Z coordinate support."""
project = None
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create a GPKG data and project with point, linestring and polygon layers
# that supports Z coordinates
drv = ogr.GetDriverByName("GPKG")
ds = drv.CreateDataSource(cls.temporary_path + "/test_z.gpkg")
layer_names = []
for wkb_type_name in ["Point", "LineString", "Polygon"]:
for prefix in ["", "Multi"]:
wkb_type_name = prefix + wkb_type_name
wkb_type = getattr(ogr, "wkb" + wkb_type_name + "25D", None)
layer_name = "test_" + wkb_type_name.lower()
layer_names.append(layer_name)
layer = ds.CreateLayer(
layer_name,
geom_type=wkb_type,
options=["GEOMETRY_NAME=geom"],
)
layer.CreateField(ogr.FieldDefn("name", ogr.OFTString))
layer.CreateField(ogr.FieldDefn("color", ogr.OFTString))
layer.CreateField(ogr.FieldDefn("gid", ogr.OFTInteger))
del ds
cls.project = QgsProject()
for layer_name in layer_names:
layer = QgsVectorLayer(
cls.temporary_path + "/test_z.gpkg|layername=" + layer_name,
layer_name,
"ogr",
)
assert cls.project.addMapLayer(layer)
# Enable WFS-T support for the layer project
layer_ids = [layer.id() for layer in cls.project.mapLayers().values()]
cls.project.writeEntry("WFSLayers", "/", layer_ids)
for method in ["Insert", "Update", "Delete"]:
cls.project.writeEntry("WFSTLayers", method, layer_ids)
def do_operation(self, operation, layer_name, wkt):
geometry = ogr.Geometry(wkt=wkt)
xml = operation.format(
layer_name=layer_name, gml=geometry.ExportToGML(), xml_ns=XML_NS, id=1
)
request = QgsBufferServerRequest(
f"http://server.qgis.org/?SERVICE=WFS&REQUEST=Transaction",
QgsBufferServerRequest.PostMethod,
{"Content-Type": "application/xml"},
xml.encode("utf-8"),
)
response = QgsBufferServerResponse()
self.server.handleRequest(request, response, self.project)
return response
def do_insert(self, layer_name, wkt):
geometry = QgsGeometry.fromWkt(wkt)
geom_wkt = geometry.asWkt().upper()
response = self.do_operation(WFS_TRANSACTION_Z_INSERT, layer_name, geom_wkt)
# Check layer
layer = self.project.mapLayersByName(layer_name)[0]
self.assertEqual(layer.featureCount(), 1)
feature = next(layer.getFeatures())
self.assertEqual(feature["name"], "name")
self.assertEqual(feature["color"], "black")
self.assertEqual(feature["gid"], 1)
self.assertEqual(feature.geometry().asWkt().upper(), geom_wkt)
def do_update(self, layer_name, wkt):
geometry = QgsGeometry.fromWkt(wkt)
geom_wkt = geometry.asWkt().upper()
response = self.do_operation(WFS_TRANSACTION_Z_UPDATE, layer_name, geom_wkt)
# Check layer
layer = self.project.mapLayersByName(layer_name)[0]
self.assertEqual(layer.featureCount(), 1)
feature = next(layer.getFeatures())
self.assertEqual(feature["color"], "red")
self.assertEqual(feature["name"], "new_name")
self.assertEqual(feature.geometry().asWkt().upper(), geom_wkt)
def testGeometries(self):
self.do_insert("test_point", "POINT Z (1 2 3)")
self.do_update("test_point", "POINT Z (4 5 6)")
self.do_insert("test_linestring", "LINESTRING Z (1 2 3, 4 5 6, 7 8 9)")
self.do_update("test_linestring", "LINESTRING Z (10 11 12, 13 14 15, 16 17 18)")
self.do_insert("test_polygon", "POLYGON Z ((1 2 3, 4 5 6, 7 8 9, 1 2 3))")
self.do_update(
"test_polygon", "POLYGON Z ((10 11 12, 13 14 15, 16 17 18, 10 11 12))"
)
self.do_insert("test_multipoint", "MULTIPOINT Z (1 2 3, 4 5 6, 7 8 9)")
self.do_update("test_multipoint", "MULTIPOINT Z (10 11 12, 13 14 15, 16 17 18)")
self.do_insert(
"test_multilinestring",
"MULTILINESTRING Z ((1 2 3, 4 5 6, 7 8 9), (10 11 12, 13 14 15, 16 17 18))",
)
self.do_update(
"test_multilinestring",
"MULTILINESTRING Z ((19 20 21, 22 23 24, 25 26 27), (28 29 30, 31 32 33, 34 35 36))",
)
self.do_insert(
"test_multipolygon",
"MULTIPOLYGON Z (((1 2 3, 4 5 6, 7 8 9, 1 2 3)), ((10 11 12, 13 14 15, 16 17 18, 10 11 12)))",
)
self.do_update(
"test_multipolygon",
"MULTIPOLYGON Z (((10 11 12, 13 14 15, 16 17 18, 10 11 12)), ((19 20 21, 22 23 24, 25 26 27, 19 20 21)))",
)
if __name__ == "__main__":
unittest.main()