Skip to content

Commit b31f516

Browse files
rouaultnyalldawson
authored andcommittedMay 31, 2019
[OGR provider] Map GeometryCollection of TIN coming from multipart shapefiles to MultiPolygonZ (fixes #29376)
1 parent 04e8f5e commit b31f516

File tree

10 files changed

+144
-13
lines changed

10 files changed

+144
-13
lines changed
 

‎src/core/qgsogrutils.cpp

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,100 @@ std::unique_ptr< QgsLineString > ogrGeometryToQgsLineString( OGRGeometryH geom )
307307
return qgis::make_unique< QgsLineString>( x, y, z, m, wkbType == QgsWkbTypes::LineString25D );
308308
}
309309

310+
QgsWkbTypes::Type QgsOgrUtils::ogrGeometryTypeToQgsWkbType( OGRwkbGeometryType ogrGeomType )
311+
{
312+
switch ( ogrGeomType )
313+
{
314+
case wkbUnknown: return QgsWkbTypes::Type::Unknown;
315+
case wkbPoint: return QgsWkbTypes::Type::Point;
316+
case wkbLineString: return QgsWkbTypes::Type::LineString;
317+
case wkbPolygon: return QgsWkbTypes::Type::Polygon;
318+
case wkbMultiPoint: return QgsWkbTypes::Type::MultiPoint;
319+
case wkbMultiLineString: return QgsWkbTypes::Type::MultiLineString;
320+
case wkbMultiPolygon: return QgsWkbTypes::Type::MultiPolygon;
321+
case wkbGeometryCollection: return QgsWkbTypes::Type::GeometryCollection;
322+
case wkbCircularString: return QgsWkbTypes::Type::CircularString;
323+
case wkbCompoundCurve: return QgsWkbTypes::Type::CompoundCurve;
324+
case wkbCurvePolygon: return QgsWkbTypes::Type::CurvePolygon;
325+
case wkbMultiCurve: return QgsWkbTypes::Type::MultiCurve;
326+
case wkbMultiSurface: return QgsWkbTypes::Type::MultiSurface;
327+
case wkbCurve: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
328+
case wkbSurface: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
329+
case wkbPolyhedralSurface: return QgsWkbTypes::Type::Unknown; // no actual matching
330+
case wkbTIN: return QgsWkbTypes::Type::Unknown; // no actual matching
331+
case wkbTriangle: return QgsWkbTypes::Type::Triangle;
332+
333+
case wkbNone: return QgsWkbTypes::Type::NoGeometry;
334+
case wkbLinearRing: return QgsWkbTypes::Type::LineString; // approximate match
335+
336+
case wkbCircularStringZ: return QgsWkbTypes::Type::CircularStringZ;
337+
case wkbCompoundCurveZ: return QgsWkbTypes::Type::CompoundCurveZ;
338+
case wkbCurvePolygonZ: return QgsWkbTypes::Type::CurvePolygonZ;
339+
case wkbMultiCurveZ: return QgsWkbTypes::Type::MultiCurveZ;
340+
case wkbMultiSurfaceZ: return QgsWkbTypes::Type::MultiSurfaceZ;
341+
case wkbCurveZ: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
342+
case wkbSurfaceZ: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
343+
case wkbPolyhedralSurfaceZ: return QgsWkbTypes::Type::Unknown; // no actual matching
344+
case wkbTINZ: return QgsWkbTypes::Type::Unknown; // no actual matching
345+
case wkbTriangleZ: return QgsWkbTypes::Type::TriangleZ;
346+
347+
case wkbPointM: return QgsWkbTypes::Type::PointM;
348+
case wkbLineStringM: return QgsWkbTypes::Type::LineStringM;
349+
case wkbPolygonM: return QgsWkbTypes::Type::PolygonM;
350+
case wkbMultiPointM: return QgsWkbTypes::Type::MultiPointM;
351+
case wkbMultiLineStringM: return QgsWkbTypes::Type::MultiLineStringM;
352+
case wkbMultiPolygonM: return QgsWkbTypes::Type::MultiPolygonM;
353+
case wkbGeometryCollectionM: return QgsWkbTypes::Type::GeometryCollectionM;
354+
case wkbCircularStringM: return QgsWkbTypes::Type::CircularStringM;
355+
case wkbCompoundCurveM: return QgsWkbTypes::Type::CompoundCurveM;
356+
case wkbCurvePolygonM: return QgsWkbTypes::Type::CurvePolygonM;
357+
case wkbMultiCurveM: return QgsWkbTypes::Type::MultiCurveM;
358+
case wkbMultiSurfaceM: return QgsWkbTypes::Type::MultiSurfaceM;
359+
case wkbCurveM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
360+
case wkbSurfaceM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
361+
case wkbPolyhedralSurfaceM: return QgsWkbTypes::Type::Unknown; // no actual matching
362+
case wkbTINM: return QgsWkbTypes::Type::Unknown; // no actual matching
363+
case wkbTriangleM: return QgsWkbTypes::Type::TriangleM;
364+
365+
case wkbPointZM: return QgsWkbTypes::Type::PointZM;
366+
case wkbLineStringZM: return QgsWkbTypes::Type::LineStringZM;
367+
case wkbPolygonZM: return QgsWkbTypes::Type::PolygonZM;
368+
case wkbMultiPointZM: return QgsWkbTypes::Type::MultiPointZM;
369+
case wkbMultiLineStringZM: return QgsWkbTypes::Type::MultiLineStringZM;
370+
case wkbMultiPolygonZM: return QgsWkbTypes::Type::MultiPolygonZM;
371+
case wkbGeometryCollectionZM: return QgsWkbTypes::Type::GeometryCollectionZM;
372+
case wkbCircularStringZM: return QgsWkbTypes::Type::CircularStringZM;
373+
case wkbCompoundCurveZM: return QgsWkbTypes::Type::CompoundCurveZM;
374+
case wkbCurvePolygonZM: return QgsWkbTypes::Type::CurvePolygonZM;
375+
case wkbMultiCurveZM: return QgsWkbTypes::Type::MultiCurveZM;
376+
case wkbMultiSurfaceZM: return QgsWkbTypes::Type::MultiSurfaceZM;
377+
case wkbCurveZM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
378+
case wkbSurfaceZM: return QgsWkbTypes::Type::Unknown; // not an actual concrete type
379+
case wkbPolyhedralSurfaceZM: return QgsWkbTypes::Type::Unknown; // no actual matching
380+
case wkbTINZM: return QgsWkbTypes::Type::Unknown; // no actual matching
381+
case wkbTriangleZM: return QgsWkbTypes::Type::TriangleZM;
382+
383+
case wkbPoint25D: return QgsWkbTypes::Type::PointZ;
384+
case wkbLineString25D: return QgsWkbTypes::Type::LineStringZ;
385+
case wkbPolygon25D: return QgsWkbTypes::Type::PolygonZ;
386+
case wkbMultiPoint25D: return QgsWkbTypes::Type::MultiPointZ;
387+
case wkbMultiLineString25D: return QgsWkbTypes::Type::MultiLineStringZ;
388+
case wkbMultiPolygon25D: return QgsWkbTypes::Type::MultiPolygonZ;
389+
case wkbGeometryCollection25D: return QgsWkbTypes::Type::GeometryCollectionZ;
390+
}
391+
392+
// should not reach that point normally
393+
return QgsWkbTypes::Type::Unknown;
394+
}
395+
310396
QgsGeometry QgsOgrUtils::ogrGeometryToQgsGeometry( OGRGeometryH geom )
311397
{
312398
if ( !geom )
313399
return QgsGeometry();
314400

315-
QgsWkbTypes::Type wkbType = static_cast<QgsWkbTypes::Type>( OGR_G_GetGeometryType( geom ) );
401+
const auto ogrGeomType = OGR_G_GetGeometryType( geom );
402+
QgsWkbTypes::Type wkbType = ogrGeometryTypeToQgsWkbType( ogrGeomType );
403+
316404
// optimised case for some geometry classes, avoiding wkb conversion on OGR/QGIS sides
317405
// TODO - extend to other classes!
318406
switch ( QgsWkbTypes::flatType( wkbType ) )
@@ -334,6 +422,19 @@ QgsGeometry QgsOgrUtils::ogrGeometryToQgsGeometry( OGRGeometryH geom )
334422

335423
// Fallback to inefficient WKB conversions
336424

425+
if ( wkbFlatten( wkbType ) == wkbGeometryCollection )
426+
{
427+
// Shapefile MultiPatch can be reported as GeometryCollectionZ of TINZ
428+
if ( OGR_G_GetGeometryCount( geom ) >= 1 &&
429+
wkbFlatten( OGR_G_GetGeometryType( OGR_G_GetGeometryRef( geom, 0 ) ) ) == wkbTIN )
430+
{
431+
auto newGeom = OGR_G_ForceToMultiPolygon( OGR_G_Clone( geom ) );
432+
auto ret = ogrGeometryToQgsGeometry( newGeom );
433+
OGR_G_DestroyGeometry( newGeom );
434+
return ret;
435+
}
436+
}
437+
337438
// get the wkb representation
338439
int memorySize = OGR_G_WkbSize( geom );
339440
unsigned char *wkb = new unsigned char[memorySize];

‎src/core/qgsogrutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,13 @@ class CORE_EXPORT QgsOgrUtils
247247
* \since QGIS 3.2
248248
*/
249249
static QStringList cStringListToQStringList( char **stringList );
250+
251+
/**
252+
* Converts a OGRwkbGeometryType to QgsWkbTypes::Type
253+
*
254+
* \since QGIS 3.4.9
255+
*/
256+
static QgsWkbTypes::Type ogrGeometryTypeToQgsWkbType( OGRwkbGeometryType ogrGeomType );
250257
};
251258

252259
#endif // QGSOGRUTILS_H

‎src/providers/ogr/qgsogrdataitems.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,9 @@ static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name,
374374
OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
375375

376376
QgsLayerItem::LayerType layerType = QgsLayerItem::Vector;
377-
OGRwkbGeometryType ogrType = QgsOgrProvider::getOgrGeomType( hLayer );
377+
GDALDriverH hDriver = GDALGetDatasetDriver( hDataSource );
378+
QString driverName = QString::fromUtf8( GDALGetDriverShortName( hDriver ) );
379+
OGRwkbGeometryType ogrType = QgsOgrProvider::getOgrGeomType( driverName, hLayer );
378380
QgsWkbTypes::Type wkbType = QgsOgrProviderUtils::qgisTypeFromOgrType( ogrType );
379381
switch ( QgsWkbTypes::geometryType( wkbType ) )
380382
{

‎src/providers/ogr/qgsogrprovider.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ void QgsOgrProvider::setEncoding( const QString &e )
905905
}
906906

907907
// This is reused by dataItem
908-
OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( OGRLayerH ogrLayer )
908+
OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( const QString &driverName, OGRLayerH ogrLayer )
909909
{
910910
OGRFeatureDefnH fdef = OGR_L_GetLayerDefn( ogrLayer );
911911
OGRwkbGeometryType geomType = wkbUnknown;
@@ -936,6 +936,15 @@ OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( OGRLayerH ogrLayer )
936936
if ( geometry )
937937
{
938938
geomType = OGR_G_GetGeometryType( geometry );
939+
940+
// Shapefile MultiPatch can be reported as GeometryCollectionZ of TINZ
941+
if ( wkbFlatten( geomType ) == wkbGeometryCollection &&
942+
driverName == QLatin1String( "ESRI Shapefile" ) &&
943+
OGR_G_GetGeometryCount( geometry ) >= 1 &&
944+
wkbFlatten( OGR_G_GetGeometryType( OGR_G_GetGeometryRef( geometry, 0 ) ) ) == wkbTIN )
945+
{
946+
geomType = wkbMultiPolygon25D;
947+
}
939948
}
940949
if ( geomType != wkbNone )
941950
break;
@@ -964,7 +973,7 @@ void QgsOgrProvider::loadFields()
964973
QMutex *mutex = nullptr;
965974
OGRLayerH ogrLayer = mOgrLayer->getHandleAndMutex( mutex );
966975
QMutexLocker locker( mutex );
967-
mOGRGeomType = getOgrGeomType( ogrLayer );
976+
mOGRGeomType = getOgrGeomType( mGDALDriverName, ogrLayer );
968977
}
969978
QgsOgrFeatureDefn &fdef = mOgrLayer->GetLayerDefn();
970979

@@ -1306,18 +1315,18 @@ size_t QgsOgrProvider::layerCount() const
13061315
*/
13071316
QgsWkbTypes::Type QgsOgrProvider::wkbType() const
13081317
{
1309-
QgsWkbTypes::Type wkb = static_cast<QgsWkbTypes::Type>( mOGRGeomType );
1318+
QgsWkbTypes::Type wkb = QgsOgrUtils::ogrGeometryTypeToQgsWkbType( mOGRGeomType );
13101319
if ( mGDALDriverName == QLatin1String( "ESRI Shapefile" ) && ( wkb == QgsWkbTypes::LineString || wkb == QgsWkbTypes::Polygon ) )
13111320
{
13121321
wkb = QgsWkbTypes::multiType( wkb );
13131322
}
1314-
if ( wkb % 1000 == 15 ) // is PolyhedralSurface, PolyhedralSurfaceZ, PolyhedralSurfaceM or PolyhedralSurfaceZM => map to MultiPolygon
1323+
if ( mOGRGeomType % 1000 == wkbPolyhedralSurface ) // is PolyhedralSurface, PolyhedralSurfaceZ, PolyhedralSurfaceM or PolyhedralSurfaceZM => map to MultiPolygon
13151324
{
1316-
wkb = static_cast<QgsWkbTypes::Type>( wkb - 9 );
1325+
wkb = static_cast<QgsWkbTypes::Type>( mOGRGeomType - ( wkbPolyhedralSurface - wkbMultiPolygon ) );
13171326
}
1318-
else if ( wkb % 1000 == 16 ) // is TIN, TINZ, TINM or TINZM => map to MultiPolygon
1327+
else if ( mOGRGeomType % 1000 == wkbTIN ) // is TIN, TINZ, TINM or TINZM => map to MultiPolygon
13191328
{
1320-
wkb = static_cast<QgsWkbTypes::Type>( wkb - 10 );
1329+
wkb = static_cast<QgsWkbTypes::Type>( mOGRGeomType - ( wkbTIN - wkbMultiPolygon ) );
13211330
}
13221331
return wkb;
13231332
}

‎src/providers/ogr/qgsogrprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
151151
bool doesStrictFeatureTypeCheck() const override;
152152

153153
//! Returns OGR geometry type
154-
static OGRwkbGeometryType getOgrGeomType( OGRLayerH ogrLayer );
154+
static OGRwkbGeometryType getOgrGeomType( const QString &driverName, OGRLayerH ogrLayer );
155155

156156
//! Gets single flatten geometry type
157157
static OGRwkbGeometryType ogrWkbSingleFlatten( OGRwkbGeometryType type );

‎tests/src/analysis/testqgsprocessing.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,14 +1652,14 @@ void TestQgsProcessing::createFeatureSink()
16521652
// no extension, should default to shp
16531653
destination = QDir::tempPath() + "/create_feature_sink2";
16541654
prevDest = QDir::tempPath() + "/create_feature_sink2.gpkg";
1655-
sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::Point25D, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
1655+
sink.reset( QgsProcessingUtils::createFeatureSink( destination, context, fields, QgsWkbTypes::PointZ, QgsCoordinateReferenceSystem::fromEpsgId( 3111 ) ) );
16561656
QVERIFY( sink.get() );
16571657
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PointZ(1 2 3)" ) ) );
16581658
QVERIFY( sink->addFeature( f ) );
16591659
QCOMPARE( destination, prevDest );
16601660
sink.reset( nullptr );
16611661
layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( destination, context, true ) );
1662-
QCOMPARE( layer->wkbType(), QgsWkbTypes::Point25D );
1662+
QCOMPARE( layer->wkbType(), QgsWkbTypes::PointZ );
16631663
QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
16641664
QCOMPARE( layer->fields().size(), 2 );
16651665
QCOMPARE( layer->fields().at( 0 ).name(), QStringLiteral( "fid" ) );

‎tests/src/python/test_provider_shapefile.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import osgeo.ogr
2222
import sys
2323

24-
from qgis.core import QgsSettings, QgsFeature, QgsField, QgsGeometry, QgsVectorLayer, QgsFeatureRequest, QgsVectorDataProvider
24+
from qgis.core import QgsSettings, QgsFeature, QgsField, QgsGeometry, QgsVectorLayer, QgsFeatureRequest, QgsVectorDataProvider, QgsWkbTypes
2525
from qgis.PyQt.QtCore import QVariant
2626
from qgis.testing import start_app, unittest
2727
from utilities import unitTestDataPath
@@ -703,6 +703,18 @@ def _lessdigits(s):
703703
self.assertEqual(_lessdigits(subSet_vl.extent().toString()), filtered_extent)
704704
self.assertNotEqual(_lessdigits(subSet_vl.extent().toString()), unfiltered_extent)
705705

706+
def testMultipatch(self):
707+
"""Check that we can deal with multipatch shapefiles, returned natively by OGR as GeometryCollection of TIN"""
708+
709+
testPath = TEST_DATA_DIR + '/' + 'multipatch.shp'
710+
vl = QgsVectorLayer(testPath, 'test', 'ogr')
711+
self.assertTrue(vl.isValid())
712+
self.assertEqual(vl.wkbType(), QgsWkbTypes.MultiPolygonZ)
713+
f = next(vl.getFeatures())
714+
self.assertEqual(f.geometry().wkbType(), QgsWkbTypes.MultiPolygonZ)
715+
self.assertEqual(f.geometry().constGet().asWkt(),
716+
'MultiPolygonZ (((0 0 0, 0 1 0, 1 1 0, 0 0 0)),((0 0 0, 1 1 0, 1 0 0, 0 0 0)),((0 0 0, 0 -1 0, 1 -1 0, 0 0 0)),((0 0 0, 1 -1 0, 1 0 0, 0 0 0)))')
717+
706718

707719
if __name__ == '__main__':
708720
unittest.main()

‎tests/testdata/multipatch.shp

376 Bytes
Binary file not shown.

‎tests/testdata/multipatch.shx

108 Bytes
Binary file not shown.

‎tests/testdata/points_gpkg.gpkg

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.