Skip to content

Commit

Permalink
Move geometry handling guts of QgsVectorLayerUtils::makeFeaturesCompa…
Browse files Browse the repository at this point in the history
…tible

to a new method in QgsGeometry
  • Loading branch information
nyalldawson committed Apr 7, 2020
1 parent c987e50 commit 78c86ef
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 122 deletions.
33 changes: 32 additions & 1 deletion python/core/auto_generated/geometry/qgsgeometry.sip.in
Expand Up @@ -1542,7 +1542,32 @@ Exports the geometry to a GeoJSON string.
%End


QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const /Factory/;
QVector< QgsGeometry > coerceToType( QgsWkbTypes::Type type ) const;
%Docstring
Attempts to coerce this geometry into the specified destination ``type``.

This method will do anything possible to force the current geometry into the specified type. E.g.
- lines or polygons will be converted to points by return either a single multipoint geometry or multiple
single point geometries.
- polygons will be converted to lines by extracting their exterior and interior rings, returning
either a multilinestring or multiple single line strings as dictated by ``type``.
- lines will be converted to polygon rings if ``type`` is a polygon type
- curved geometries will be segmented if ``type`` is non-curved.
- multi geometries will be converted to a list of single geometries
- single geometries will be upgraded to multi geometries
- z or m values will be added or dropped as required.

.. note::

This method is much stricter than convertToType(), as it considers the exact WKB type
of geometries instead of the geometry family (point/line/polygon), and tries more exhaustively
to coerce geometries to the desired ``type``. It also correctly maintains curves and z/m values
wherever appropriate.

.. versionadded:: 3.14
%End

QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const;
%Docstring
Try to convert the geometry to the requested type

Expand All @@ -1551,6 +1576,12 @@ Try to convert the geometry to the requested type

:return: the converted geometry or ``None`` if the conversion fails.

.. note::

The coerceToType() method applies much stricter and more exhaustive attempts to convert
between geometry types, and is recommended instead of this method. This method force drops
curves and any z or m values present in the geometry.

.. versionadded:: 2.2
%End

Expand Down
115 changes: 115 additions & 0 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1310,6 +1310,121 @@ json QgsGeometry::asJsonObject( int precision ) const

}

QVector<QgsGeometry> QgsGeometry::coerceToType( const QgsWkbTypes::Type type ) const
{
QVector< QgsGeometry > res;
if ( wkbType() == type )
{
res << *this;
return res;
}

QgsGeometry newGeom = *this;

// Curved -> straight
if ( !QgsWkbTypes::isCurvedType( type ) && QgsWkbTypes::isCurvedType( newGeom.wkbType() ) )
{
newGeom = QgsGeometry( d->geometry.get()->segmentize() );
}

// polygon -> line
if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::LineGeometry &&
newGeom.type() == QgsWkbTypes::PolygonGeometry )
{
// boundary gives us a (multi)line string of exterior + interior rings
newGeom = QgsGeometry( newGeom.constGet()->boundary() );
}
// line -> polygon
if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::PolygonGeometry &&
newGeom.type() == QgsWkbTypes::LineGeometry )
{
std::unique_ptr< QgsGeometryCollection > gc( QgsGeometryFactory::createCollectionOfType( type ) );
const QgsGeometry source = newGeom;
for ( auto part = source.const_parts_begin(); part != source.const_parts_end(); ++part )
{
std::unique_ptr< QgsAbstractGeometry > exterior( ( *part )->clone() );
if ( QgsCurve *curve = qgsgeometry_cast< QgsCurve * >( exterior.get() ) )
{
if ( QgsWkbTypes::isCurvedType( type ) )
{
std::unique_ptr< QgsCurvePolygon > cp = qgis::make_unique< QgsCurvePolygon >();
cp->setExteriorRing( curve );
exterior.release();
gc->addGeometry( cp.release() );
}
else
{
std::unique_ptr< QgsPolygon > p = qgis::make_unique< QgsPolygon >();
p->setExteriorRing( qgsgeometry_cast< QgsLineString * >( curve ) );
exterior.release();
gc->addGeometry( p.release() );
}
}
}
newGeom = QgsGeometry( std::move( gc ) );
}

// line/polygon -> points
if ( QgsWkbTypes::geometryType( type ) == QgsWkbTypes::PointGeometry &&
( newGeom.type() == QgsWkbTypes::LineGeometry ||
newGeom.type() == QgsWkbTypes::PolygonGeometry ) )
{
// lines/polygons to a point layer, extract all vertices
std::unique_ptr< QgsMultiPoint > mp = qgis::make_unique< QgsMultiPoint >();
const QgsGeometry source = newGeom;
QSet< QgsPoint > added;
for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex )
{
if ( added.contains( *vertex ) )
continue; // avoid duplicate points, e.g. start/end of rings
mp->addGeometry( ( *vertex ).clone() );
added.insert( *vertex );
}
newGeom = QgsGeometry( std::move( mp ) );
}

// Single -> multi
if ( QgsWkbTypes::isMultiType( type ) && ! newGeom.isMultipart( ) )
{
newGeom.convertToMultiType();
}
// Drop Z/M
if ( newGeom.constGet()->is3D() && ! QgsWkbTypes::hasZ( type ) )
{
newGeom.get()->dropZValue();
}
if ( newGeom.constGet()->isMeasure() && ! QgsWkbTypes::hasM( type ) )
{
newGeom.get()->dropMValue();
}
// Add Z/M back, set to 0
if ( ! newGeom.constGet()->is3D() && QgsWkbTypes::hasZ( type ) )
{
newGeom.get()->addZValue( 0.0 );
}
if ( ! newGeom.constGet()->isMeasure() && QgsWkbTypes::hasM( type ) )
{
newGeom.get()->addMValue( 0.0 );
}

// Multi -> single
if ( ! QgsWkbTypes::isMultiType( type ) && newGeom.isMultipart( ) )
{
const QgsGeometryCollection *parts( static_cast< const QgsGeometryCollection * >( newGeom.constGet() ) );
QgsAttributeMap attrMap;
res.reserve( parts->partCount() );
for ( int i = 0; i < parts->partCount( ); i++ )
{
res << QgsGeometry( parts->geometryN( i )->clone() );
}
}
else
{
res << newGeom;
}
return res;
}

QgsGeometry QgsGeometry::convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart ) const
{
switch ( destType )
Expand Down
30 changes: 29 additions & 1 deletion src/core/geometry/qgsgeometry.h
Expand Up @@ -1567,14 +1567,42 @@ class CORE_EXPORT QgsGeometry
*/
virtual json asJsonObject( int precision = 17 ) const SIP_SKIP;

/**
* Attempts to coerce this geometry into the specified destination \a type.
*
* This method will do anything possible to force the current geometry into the specified type. E.g.
* - lines or polygons will be converted to points by return either a single multipoint geometry or multiple
* single point geometries.
* - polygons will be converted to lines by extracting their exterior and interior rings, returning
* either a multilinestring or multiple single line strings as dictated by \a type.
* - lines will be converted to polygon rings if \a type is a polygon type
* - curved geometries will be segmented if \a type is non-curved.
* - multi geometries will be converted to a list of single geometries
* - single geometries will be upgraded to multi geometries
* - z or m values will be added or dropped as required.
*
* \note This method is much stricter than convertToType(), as it considers the exact WKB type
* of geometries instead of the geometry family (point/line/polygon), and tries more exhaustively
* to coerce geometries to the desired \a type. It also correctly maintains curves and z/m values
* wherever appropriate.
*
* \since QGIS 3.14
*/
QVector< QgsGeometry > coerceToType( QgsWkbTypes::Type type ) const;

/**
* Try to convert the geometry to the requested type
* \param destType the geometry type to be converted to
* \param destMultipart determines if the output geometry will be multipart or not
* \returns the converted geometry or NULLPTR if the conversion fails.
*
* \note The coerceToType() method applies much stricter and more exhaustive attempts to convert
* between geometry types, and is recommended instead of this method. This method force drops
* curves and any z or m values present in the geometry.
*
* \since QGIS 2.2
*/
QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const SIP_FACTORY;
QgsGeometry convertToType( QgsWkbTypes::GeometryType destType, bool destMultipart = false ) const;

/* Accessor functions for getting geometry data */

Expand Down
133 changes: 13 additions & 120 deletions src/core/qgsvectorlayerutils.cpp
Expand Up @@ -714,141 +714,34 @@ QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &fea
QgsWkbTypes::Type::NoGeometry &&
inputWkbType != QgsWkbTypes::Type::Unknown;
// Drop geometry if layer is geometry-less
if ( newFHasGeom && ! layerHasGeom )
if ( ( newFHasGeom && !layerHasGeom ) || !newFHasGeom )
{
QgsFeature _f = QgsFeature( layer->fields() );
_f.setAttributes( newF.attributes() );
resultFeatures.append( _f );
}
else
{
// Geometry need fixing
if ( newFHasGeom && layerHasGeom && newF.geometry().wkbType() != inputWkbType )
{
// Curved -> straight
if ( !QgsWkbTypes::isCurvedType( inputWkbType ) && QgsWkbTypes::isCurvedType( newF.geometry().wkbType() ) )
{
QgsGeometry newGeom( newF.geometry().constGet()->segmentize() );
newF.setGeometry( newGeom );
}
// Geometry need fixing?
const QVector< QgsGeometry > geometries = newF.geometry().coerceToType( inputWkbType );

// polygon -> line
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::LineGeometry &&
newF.geometry().type() == QgsWkbTypes::PolygonGeometry )
{
// boundary gives us a (multi)line string of exterior + interior rings
QgsGeometry newGeom( newF.geometry().constGet()->boundary() );
newF.setGeometry( newGeom );
}
// line -> polygon
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::PolygonGeometry &&
newF.geometry().type() == QgsWkbTypes::LineGeometry )
{
std::unique_ptr< QgsGeometryCollection > gc( QgsGeometryFactory::createCollectionOfType( inputWkbType ) );
const QgsGeometry source = newF.geometry();
for ( auto part = source.const_parts_begin(); part != source.const_parts_end(); ++part )
{
std::unique_ptr< QgsAbstractGeometry > exterior( ( *part )->clone() );
if ( QgsCurve *curve = qgsgeometry_cast< QgsCurve * >( exterior.get() ) )
{
if ( QgsWkbTypes::isCurvedType( inputWkbType ) )
{
std::unique_ptr< QgsCurvePolygon > cp = qgis::make_unique< QgsCurvePolygon >();
cp->setExteriorRing( curve );
exterior.release();
gc->addGeometry( cp.release() );
}
else
{
std::unique_ptr< QgsPolygon > p = qgis::make_unique< QgsPolygon >();
p->setExteriorRing( qgsgeometry_cast< QgsLineString * >( curve ) );
exterior.release();
gc->addGeometry( p.release() );
}
}
}
QgsGeometry newGeom( std::move( gc ) );
newF.setGeometry( newGeom );
}

// line/polygon -> points
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::PointGeometry &&
( newF.geometry().type() == QgsWkbTypes::LineGeometry ||
newF.geometry().type() == QgsWkbTypes::PolygonGeometry ) )
{
// lines/polygons to a point layer, extract all vertices
std::unique_ptr< QgsMultiPoint > mp = qgis::make_unique< QgsMultiPoint >();
const QgsGeometry source = newF.geometry();
QSet< QgsPoint > added;
for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex )
{
if ( added.contains( *vertex ) )
continue; // avoid duplicate points, e.g. start/end of rings
mp->addGeometry( ( *vertex ).clone() );
added.insert( *vertex );
}
QgsGeometry newGeom( std::move( mp ) );
newF.setGeometry( newGeom );
}

// Single -> multi
if ( QgsWkbTypes::isMultiType( inputWkbType ) && ! newF.geometry().isMultipart( ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.convertToMultiType();
newF.setGeometry( newGeom );
}
// Drop Z/M
if ( newF.geometry().constGet()->is3D() && ! QgsWkbTypes::hasZ( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->dropZValue();
newF.setGeometry( newGeom );
}
if ( newF.geometry().constGet()->isMeasure() && ! QgsWkbTypes::hasM( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->dropMValue();
newF.setGeometry( newGeom );
}
// Add Z/M back, set to 0
if ( ! newF.geometry().constGet()->is3D() && QgsWkbTypes::hasZ( inputWkbType ) )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->addZValue( 0.0 );
newF.setGeometry( newGeom );
}
if ( ! newF.geometry().constGet()->isMeasure() && QgsWkbTypes::hasM( inputWkbType ) )
if ( geometries.count() != 1 )
{
QgsAttributeMap attrMap;
for ( int j = 0; j < newF.fields().count(); j++ )
{
QgsGeometry newGeom( newF.geometry( ) );
newGeom.get()->addMValue( 0.0 );
newF.setGeometry( newGeom );
attrMap[j] = newF.attribute( j );
}
// Multi -> single
if ( ! QgsWkbTypes::isMultiType( inputWkbType ) && newF.geometry().isMultipart( ) )
{
QgsGeometry newGeom( newF.geometry( ) );
const QgsGeometryCollection *parts( static_cast< const QgsGeometryCollection * >( newGeom.constGet() ) );
QgsAttributeMap attrMap;
for ( int j = 0; j < newF.fields().count(); j++ )
{
attrMap[j] = newF.attribute( j );
}
resultFeatures.reserve( parts->partCount() );
for ( int i = 0; i < parts->partCount( ); i++ )
{
QgsGeometry g( parts->geometryN( i )->clone() );
QgsFeature _f( createFeature( layer, g, attrMap ) );
resultFeatures.append( _f );
}
}
else
resultFeatures.reserve( geometries.size() );
for ( const QgsGeometry &geometry : geometries )
{
resultFeatures.append( newF );
QgsFeature _f( createFeature( layer, geometry, attrMap ) );
resultFeatures.append( _f );
}
}
else
{
newF.setGeometry( geometries.at( 0 ) );
resultFeatures.append( newF );
}
}
Expand Down

0 comments on commit 78c86ef

Please sign in to comment.