Skip to content

Commit 71bdc31

Browse files
committed
Fix pasting linestring into polygon layer should auto convert to polygon
(And polygon->lines, lines->points, polygon->points, etc) Fixes #21213
1 parent 9ed5b3f commit 71bdc31

File tree

2 files changed

+168
-2
lines changed

2 files changed

+168
-2
lines changed

src/core/qgsvectorlayerutils.cpp

+73
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
#include "qgsthreadingutils.h"
2828
#include "qgsgeometrycollection.h"
2929
#include "qgsexpressioncontextutils.h"
30+
#include "qgsmultisurface.h"
31+
#include "qgsgeometryfactory.h"
32+
#include "qgscurvepolygon.h"
33+
#include "qgspolygon.h"
34+
#include "qgslinestring.h"
35+
#include "qgsmultipoint.h"
3036

3137
QgsFeatureIterator QgsVectorLayerUtils::getValuesIterator( const QgsVectorLayer *layer, const QString &fieldOrExpression, bool &ok, bool selectedOnly )
3238
{
@@ -592,6 +598,72 @@ QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &fea
592598
// Geometry need fixing
593599
if ( newFHasGeom && layerHasGeom && newF.geometry().wkbType() != inputWkbType )
594600
{
601+
// Curved -> straight
602+
if ( !QgsWkbTypes::isCurvedType( inputWkbType ) && QgsWkbTypes::isCurvedType( newF.geometry().wkbType() ) )
603+
{
604+
QgsGeometry newGeom( newF.geometry().constGet()->segmentize() );
605+
newF.setGeometry( newGeom );
606+
}
607+
608+
// polygon -> line
609+
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::LineGeometry &&
610+
newF.geometry().type() == QgsWkbTypes::PolygonGeometry )
611+
{
612+
// boundary gives us a (multi)line string of exterior + interior rings
613+
QgsGeometry newGeom( newF.geometry().constGet()->boundary() );
614+
newF.setGeometry( newGeom );
615+
}
616+
// line -> polygon
617+
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::PolygonGeometry &&
618+
newF.geometry().type() == QgsWkbTypes::LineGeometry )
619+
{
620+
std::unique_ptr< QgsGeometryCollection > gc( QgsGeometryFactory::createCollectionOfType( inputWkbType ) );
621+
const QgsGeometry source = newF.geometry();
622+
for ( auto part = source.const_parts_begin(); part != source.const_parts_end(); ++part )
623+
{
624+
std::unique_ptr< QgsAbstractGeometry > exterior( ( *part )->clone() );
625+
if ( QgsCurve *curve = qgsgeometry_cast< QgsCurve * >( exterior.get() ) )
626+
{
627+
if ( QgsWkbTypes::isCurvedType( inputWkbType ) )
628+
{
629+
std::unique_ptr< QgsCurvePolygon > cp = qgis::make_unique< QgsCurvePolygon >();
630+
cp->setExteriorRing( curve );
631+
exterior.release();
632+
gc->addGeometry( cp.release() );
633+
}
634+
else
635+
{
636+
std::unique_ptr< QgsPolygon > p = qgis::make_unique< QgsPolygon >();
637+
p->setExteriorRing( qgsgeometry_cast< QgsLineString * >( curve ) );
638+
exterior.release();
639+
gc->addGeometry( p.release() );
640+
}
641+
}
642+
}
643+
QgsGeometry newGeom( std::move( gc ) );
644+
newF.setGeometry( newGeom );
645+
}
646+
647+
// line/polygon -> points
648+
if ( QgsWkbTypes::geometryType( inputWkbType ) == QgsWkbTypes::PointGeometry &&
649+
( newF.geometry().type() == QgsWkbTypes::LineGeometry ||
650+
newF.geometry().type() == QgsWkbTypes::PolygonGeometry ) )
651+
{
652+
// lines/polygons to a point layer, extract all vertices
653+
std::unique_ptr< QgsMultiPoint > mp = qgis::make_unique< QgsMultiPoint >();
654+
const QgsGeometry source = newF.geometry();
655+
QSet< QgsPoint > added;
656+
for ( auto vertex = source.vertices_begin(); vertex != source.vertices_end(); ++vertex )
657+
{
658+
if ( added.contains( *vertex ) )
659+
continue; // avoid duplicate points, e.g. start/end of rings
660+
mp->addGeometry( ( *vertex ).clone() );
661+
added.insert( *vertex );
662+
}
663+
QgsGeometry newGeom( std::move( mp ) );
664+
newF.setGeometry( newGeom );
665+
}
666+
595667
// Single -> multi
596668
if ( QgsWkbTypes::isMultiType( inputWkbType ) && ! newF.geometry().isMultipart( ) )
597669
{
@@ -635,6 +707,7 @@ QgsFeatureList QgsVectorLayerUtils::makeFeatureCompatible( const QgsFeature &fea
635707
{
636708
attrMap[j] = newF.attribute( j );
637709
}
710+
resultFeatures.reserve( parts->partCount() );
638711
for ( int i = 0; i < parts->partCount( ); i++ )
639712
{
640713
QgsGeometry g( parts->geometryN( i )->clone() );

tests/src/python/test_qgsprocessinginplace.py

+95-2
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,11 @@ def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
204204
def test_QgsVectorLayerUtilsmakeFeaturesCompatible(self):
205205
"""Test fixer function"""
206206
# Test failure
207-
with self.assertRaises(AssertionError):
208-
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
207+
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
208+
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Polygon')
209+
self._make_compatible_tester('Polygon((1 1, 2 2, 1 2, 1 1))', 'Point')
210+
self._make_compatible_tester('Polygon((1 1, 2 2, 1 2, 1 1))', 'LineString')
211+
209212
self._make_compatible_tester('Point(1 1)', 'Point')
210213
self._make_compatible_tester('Point(1 1)', 'Point', [1, 'nope'])
211214
self._make_compatible_tester('Point z (1 1 3)', 'Point')
@@ -277,6 +280,96 @@ def test_QgsVectorLayerUtilsmakeFeaturesCompatible(self):
277280
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 2 2, 3 3, 1 1)')
278281
self.assertEqual(f[1].geometry().asWkt(), 'LineString (10 1, 20 2, 30 3, 10 1)')
279282

283+
# line -> points
284+
l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
285+
self.assertEqual(len(f), 3)
286+
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
287+
self.assertEqual(f[1].geometry().asWkt(), 'Point (2 2)')
288+
self.assertEqual(f[2].geometry().asWkt(), 'Point (3 3)')
289+
290+
l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'MultiPoint')
291+
self.assertEqual(len(f), 1)
292+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(2 2),(3 3))')
293+
294+
l, f = self._make_compatible_tester('MultiLineString ((1 1, 2 2),(4 4, 3 3))', 'Point')
295+
self.assertEqual(len(f), 4)
296+
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
297+
self.assertEqual(f[1].geometry().asWkt(), 'Point (2 2)')
298+
self.assertEqual(f[2].geometry().asWkt(), 'Point (4 4)')
299+
self.assertEqual(f[3].geometry().asWkt(), 'Point (3 3)')
300+
301+
l, f = self._make_compatible_tester('MultiLineString ((1 1, 2 2),(4 4, 3 3))', 'MultiPoint')
302+
self.assertEqual(len(f), 1)
303+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(2 2),(4 4),(3 3))')
304+
305+
# line -> polygon
306+
l, f = self._make_compatible_tester('LineString (1 1, 1 2, 2 2)', 'Polygon')
307+
self.assertEqual(len(f), 1)
308+
self.assertEqual(f[0].geometry().asWkt(), 'Polygon ((1 1, 1 2, 2 2, 1 1))')
309+
310+
l, f = self._make_compatible_tester('LineString (1 1, 1 2, 2 2)', 'MultiPolygon')
311+
self.assertEqual(len(f), 1)
312+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPolygon (((1 1, 1 2, 2 2, 1 1)))')
313+
314+
l, f = self._make_compatible_tester('MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4))', 'Polygon')
315+
self.assertEqual(len(f), 2)
316+
self.assertEqual(f[0].geometry().asWkt(), 'Polygon ((1 1, 1 2, 2 2, 1 1))')
317+
self.assertEqual(f[1].geometry().asWkt(), 'Polygon ((3 3, 4 3, 4 4, 3 3))')
318+
319+
l, f = self._make_compatible_tester('MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4))', 'MultiPolygon')
320+
self.assertEqual(len(f), 1)
321+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))')
322+
323+
l, f = self._make_compatible_tester('CircularString (1 1, 1 2, 2 2, 2 0, 1 1)', 'CurvePolygon')
324+
self.assertEqual(len(f), 1)
325+
self.assertEqual(f[0].geometry().asWkt(), 'CurvePolygon (CircularString (1 1, 1 2, 2 2, 2 0, 1 1))')
326+
327+
l, f = self._make_compatible_tester('CircularString (1 1, 1 2, 2 2, 2 0, 1 1)', 'Polygon')
328+
self.assertEqual(len(f), 1)
329+
self.assertTrue(f[0].geometry().asWkt(2).startswith('Polygon ((1 1, 0.99 1.01, 0.98 1.02'))
330+
331+
# polygon -> points
332+
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'Point')
333+
self.assertEqual(len(f), 3)
334+
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
335+
self.assertEqual(f[1].geometry().asWkt(), 'Point (1 2)')
336+
self.assertEqual(f[2].geometry().asWkt(), 'Point (2 2)')
337+
338+
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'MultiPoint')
339+
self.assertEqual(len(f), 1)
340+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(1 2),(2 2))')
341+
342+
l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'Point')
343+
self.assertEqual(len(f), 6)
344+
self.assertEqual(f[0].geometry().asWkt(), 'Point (1 1)')
345+
self.assertEqual(f[1].geometry().asWkt(), 'Point (1 2)')
346+
self.assertEqual(f[2].geometry().asWkt(), 'Point (2 2)')
347+
self.assertEqual(f[3].geometry().asWkt(), 'Point (3 3)')
348+
self.assertEqual(f[4].geometry().asWkt(), 'Point (4 3)')
349+
self.assertEqual(f[5].geometry().asWkt(), 'Point (4 4)')
350+
351+
l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'MultiPoint')
352+
self.assertEqual(len(f), 1)
353+
self.assertEqual(f[0].geometry().asWkt(), 'MultiPoint ((1 1),(1 2),(2 2),(3 3),(4 3),(4 4))')
354+
355+
# polygon -> lines
356+
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'LineString')
357+
self.assertEqual(len(f), 1)
358+
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 1 2, 2 2, 1 1)')
359+
360+
l, f = self._make_compatible_tester('Polygon ((1 1, 1 2, 2 2, 1 1))', 'MultiLineString')
361+
self.assertEqual(len(f), 1)
362+
self.assertEqual(f[0].geometry().asWkt(), 'MultiLineString ((1 1, 1 2, 2 2, 1 1))')
363+
364+
l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'LineString')
365+
self.assertEqual(len(f), 2)
366+
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 1 2, 2 2, 1 1)')
367+
self.assertEqual(f[1].geometry().asWkt(), 'LineString (3 3, 4 3, 4 4, 3 3)')
368+
369+
l, f = self._make_compatible_tester('MultiPolygon (((1 1, 1 2, 2 2, 1 1)),((3 3, 4 3, 4 4, 3 3)))', 'MultiLineString')
370+
self.assertEqual(len(f), 1)
371+
self.assertEqual(f[0].geometry().asWkt(), 'MultiLineString ((1 1, 1 2, 2 2, 1 1),(3 3, 4 3, 4 4, 3 3))')
372+
280373
def test_make_features_compatible_attributes(self):
281374
"""Test corner cases for attributes"""
282375

0 commit comments

Comments
 (0)