Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fix pasting linestring into polygon layer should auto convert to polygon
(And polygon->lines, lines->points, polygon->points, etc)

Fixes #21213
  • Loading branch information
nyalldawson committed Feb 15, 2019
1 parent 9ed5b3f commit 71bdc31
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 2 deletions.
73 changes: 73 additions & 0 deletions src/core/qgsvectorlayerutils.cpp
Expand Up @@ -27,6 +27,12 @@
#include "qgsthreadingutils.h"
#include "qgsgeometrycollection.h"
#include "qgsexpressioncontextutils.h"
#include "qgsmultisurface.h"
#include "qgsgeometryfactory.h"
#include "qgscurvepolygon.h"
#include "qgspolygon.h"
#include "qgslinestring.h"
#include "qgsmultipoint.h"

QgsFeatureIterator QgsVectorLayerUtils::getValuesIterator( const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly )
{
Expand Down Expand Up @@ -592,6 +598,72 @@ QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &fea
// 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 );
}

// 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( ) )
{
Expand Down Expand Up @@ -635,6 +707,7 @@ QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &fea
{
attrMap[j] = newF.attribute( j );
}
resultFeatures.reserve( parts->partCount() );
for ( int i = 0; i < parts->partCount( ); i++ )
{
QgsGeometry g( parts->geometryN( i )->clone() );
Expand Down
97 changes: 95 additions & 2 deletions tests/src/python/test_qgsprocessinginplace.py
Expand Up @@ -204,8 +204,11 @@ def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
def test_QgsVectorLayerUtilsmakeFeaturesCompatible(self):
"""Test fixer function"""
# Test failure
with self.assertRaises(AssertionError):
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Polygon')
self._make_compatible_tester('Polygon((1 1, 2 2, 1 2, 1 1))', 'Point')
self._make_compatible_tester('Polygon((1 1, 2 2, 1 2, 1 1))', 'LineString')

self._make_compatible_tester('Point(1 1)', 'Point')
self._make_compatible_tester('Point(1 1)', 'Point', [1, 'nope'])
self._make_compatible_tester('Point z (1 1 3)', 'Point')
Expand Down Expand Up @@ -277,6 +280,96 @@ def test_QgsVectorLayerUtilsmakeFeaturesCompatible(self):
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 2 2, 3 3, 1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'LineString (10 1, 20 2, 30 3, 10 1)')

# line -> points
l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
self.assertEqual(len(f), 3)
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'Point (2 2)')
self.assertEqual(f[2].geometry().asWkt(), 'Point (3 3)')

l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'MultiPoint')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(2 2),(3 3))')

l, f = self._make_compatible_tester('MultiLineString ((1 1, 2 2),(4 4, 3 3))', 'Point')
self.assertEqual(len(f), 4)
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'Point (2 2)')
self.assertEqual(f[2].geometry().asWkt(), 'Point (4 4)')
self.assertEqual(f[3].geometry().asWkt(), 'Point (3 3)')

l, f = self._make_compatible_tester('MultiLineString ((1 1, 2 2),(4 4, 3 3))', 'MultiPoint')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(2 2),(4 4),(3 3))')

# line -> polygon
l, f = self._make_compatible_tester('LineString (1 1, 1 2, 2 2)', 'Polygon')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'Polygon ((1 1, 1 2, 2 2, 1 1))')

l, f = self._make_compatible_tester('LineString (1 1, 1 2, 2 2)', 'MultiPolygon')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPolygon (((1 1, 1 2, 2 2, 1 1)))')

l, f = self._make_compatible_tester('MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4))', 'Polygon')
self.assertEqual(len(f), 2)
self.assertEqual(f[0].geometry().asWkt(), 'Polygon ((1 1, 1 2, 2 2, 1 1))')
self.assertEqual(f[1].geometry().asWkt(), 'Polygon ((3 3, 4 3, 4 4, 3 3))')

l, f = self._make_compatible_tester('MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4))', 'MultiPolygon')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))')

l, f = self._make_compatible_tester('CircularString (1 1, 1 2, 2 2, 2 0, 1 1)', 'CurvePolygon')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'CurvePolygon (CircularString (1 1, 1 2, 2 2, 2 0, 1 1))')

l, f = self._make_compatible_tester('CircularString (1 1, 1 2, 2 2, 2 0, 1 1)', 'Polygon')
self.assertEqual(len(f), 1)
self.assertTrue(f[0].geometry().asWkt(2).startswith('Polygon ((1 1, 0.99 1.01, 0.98 1.02'))

# polygon -> points
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'Point')
self.assertEqual(len(f), 3)
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'Point (1 2)')
self.assertEqual(f[2].geometry().asWkt(), 'Point (2 2)')

l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'MultiPoint')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(1 2),(2 2))')

l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'Point')
self.assertEqual(len(f), 6)
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'Point (1 2)')
self.assertEqual(f[2].geometry().asWkt(), 'Point (2 2)')
self.assertEqual(f[3].geometry().asWkt(), 'Point (3 3)')
self.assertEqual(f[4].geometry().asWkt(), 'Point (4 3)')
self.assertEqual(f[5].geometry().asWkt(), 'Point (4 4)')

l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'MultiPoint')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(1 2),(2 2),(3 3),(4 3),(4 4))')

# polygon -> lines
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'LineString')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 1 2, 2 2, 1 1)')

l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'MultiLineString')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiLineString ((1 1, 1 2, 2 2, 1 1))')

l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'LineString')
self.assertEqual(len(f), 2)
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 1 2, 2 2, 1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'LineString (3 3, 4 3, 4 4, 3 3)')

l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'MultiLineString')
self.assertEqual(len(f), 1)
self.assertEqual(f[0].geometry().asWkt(), 'MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4, 3 3))')

def test_make_features_compatible_attributes(self):
"""Test corner cases for attributes"""

Expand Down

0 comments on commit 71bdc31

Please sign in to comment.