diff --git a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in index 1e27e7ce63a..6d36a471efd 100644 --- a/python/core/auto_generated/geometry/qgscurvepolygon.sip.in +++ b/python/core/auto_generated/geometry/qgscurvepolygon.sip.in @@ -91,6 +91,7 @@ Returns the curve polygon's exterior ring. %End + SIP_PYOBJECT interiorRing( int i ) /HoldGIL,TypeHint="QgsCurve"/; %Docstring Retrieves an interior ring from the curve polygon. The first interior ring has index 0. diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index c668a06bf21..cfef172c64e 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -727,6 +727,16 @@ Deletes the vertex at the given position number and item object to help make the distinction?) %End + bool convertVertex( int atVertex ); +%Docstring +Converts the vertex at the given position from/to circular + +:return: ``False`` if atVertex does not correspond to a valid vertex + on this geometry (including if this geometry is a Point), + or if the specified vertex can't be converted (e.g. start/end points). + +.. versionadded:: 3.20 +%End QgsPoint vertexAt( int atVertex ) const; %Docstring diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index ad7ee2a43b9..85bd2e7abc1 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -2564,8 +2564,6 @@ void QgsVertexTool::deleteVertex() void QgsVertexTool::toggleVertexCurve() { - std::cout << "test"; - QgsMessageLog::logMessage( "test", "DEBUG" ); Vertex toConvert = Vertex( nullptr, -1, -1 ); if ( mSelectedVertices.size() == 1 ) @@ -2578,47 +2576,27 @@ void QgsVertexTool::toggleVertexCurve() } else { - // We only support converting one vertex at a time + // TODO support more than just 1 vertex QgsMessageLog::logMessage( "Need exactly 1 selected/editted vertex", "DEBUG" ); return; } - QgsVectorLayer *layer = toConvert.layer; - QgsFeatureId fId = toConvert.fid; - int vNr = toConvert.vertexId; - QgsVertexId vId; - QgsFeature feature = layer->getFeature( fId ); - QgsGeometry geom = feature.geometry(); - geom.vertexIdFromVertexNr( vNr, vId ); if ( ! QgsWkbTypes::isCurvedType( layer->wkbType() ) ) { - // The layer is not a curved type QgsMessageLog::logMessage( "Layer is not curved", "DEBUG" ); return; } layer->beginEditCommand( tr( "Converting vertex type" ) ); - - - // TODO : implement convertVertex on QgsGeometry instead of following block, like this : - // bool success = geom.convertVertex(vId ); - QgsAbstractGeometry *geomTmp = geom.constGet()->clone(); - if ( ! geomTmp->convertTo( QgsWkbTypes::CompoundCurve ) ) - { - QgsMessageLog::logMessage( "Could not convert " + geomTmp->wktTypeStr() + " to CompoundCurve", "DEBUG" ); - return; - } - QgsCompoundCurve *cpdCurve = ( QgsCompoundCurve * )geomTmp; - bool success = cpdCurve->convertVertex( vId ); - + QgsGeometry geom = layer->getFeature( toConvert.fid ).geometry(); + bool success = geom.convertVertex( toConvert.vertexId ); if ( success ) { QgsMessageLog::logMessage( "Should be OK", "DEBUG" ); - geom.set( cpdCurve ); - layer->changeGeometry( fId, geom ); + layer->changeGeometry( toConvert.fid, geom ); layer->endEditCommand(); layer->triggerRepaint(); } diff --git a/src/core/geometry/qgscompoundcurve.cpp b/src/core/geometry/qgscompoundcurve.cpp index 621b5d0db62..df10e4596dc 100644 --- a/src/core/geometry/qgscompoundcurve.cpp +++ b/src/core/geometry/qgscompoundcurve.cpp @@ -1011,28 +1011,7 @@ bool QgsCompoundCurve::convertVertex( QgsVertexId position ) } // We merge consecutive LineStrings - // TODO ? : move this to a new QgsCompoundCurve::mergeConsecutiveLineStrings() method; - QgsLineString *lastLineString = nullptr; - QVector newCurves; - for ( int i = 0; i < mCurves.size(); ++i ) - { - QgsCurve *curve = mCurves.at( i ); - QgsLineString *curveAsLineString = dynamic_cast( curve ); - - if ( curveAsLineString != nullptr && lastLineString != nullptr ) - { - // We append to previous - lastLineString->append( curveAsLineString ); - } - else - { - // We keep as is - newCurves.append( curve ); - lastLineString = curveAsLineString; - } - } - - mCurves = newCurves; + condenseCurves(); clearCache(); return true; diff --git a/src/core/geometry/qgscurvepolygon.h b/src/core/geometry/qgscurvepolygon.h index 2b7ce32fe17..371acfdce03 100644 --- a/src/core/geometry/qgscurvepolygon.h +++ b/src/core/geometry/qgscurvepolygon.h @@ -91,6 +91,18 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface return mExteriorRing.get(); } + /** + * Returns the curve polygon's exterior ring. + * + * \see interiorRing() + * \note Not available in Python. + * \since QGIS 3.20 + */ + QgsCurve *exteriorRing() SIP_SKIP + { + return mExteriorRing.get(); + } + #ifndef SIP_RUN /** @@ -107,6 +119,23 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface } return mInteriorRings.at( i ); } + + /** + * Retrieves an interior ring from the curve polygon. The first interior ring has index 0. + * + * \see numInteriorRings() + * \see exteriorRing() + * \note Not available in Python. + * \since QGIS 3.20 + */ + QgsCurve *interiorRing( int i ) SIP_SKIP + { + if ( i < 0 || i >= mInteriorRings.size() ) + { + return nullptr; + } + return mInteriorRings.at( i ); + } #else /** diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index ae006af57dd..c302cd1a411 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -538,41 +538,115 @@ bool QgsGeometry::deleteVertex( int atVertex ) return d->geometry->deleteVertex( id ); } -// bool QgsGeometry::convertVertex( int atVertex ) -// { -// if ( !d->geometry ) -// { -// return false; -// } +bool QgsGeometry::convertVertex( int atVertex ) +{ -// QgsVertexId id; -// if ( !vertexIdFromVertexNr( atVertex, id ) ) -// { -// return false; -// } + if ( !d->geometry ) + return false; -// QgsAbstractGeometry* geom = d->geometry.get(); + QgsVertexId id; + if ( !vertexIdFromVertexNr( atVertex, id ) ) + return false; + detach(); -// // If the geom is a compound curve, we take it as is -// if( QgsCompoundCurve *cpdCurve = dynamic_cast( geom ) ) -// { -// return cpdCurve->convertVertex(id); -// } + QgsAbstractGeometry *geom = d->geometry.get(); -// // If the geom is a linestring or cirularstring, we convert to compound curve -// if( dynamic_cast( geom ) != nullptr || dynamic_cast( geom ) != nullptr ){ -// QgsCompoundCurve *cpdCurve = new QgsCompoundCurve(); -// cpdCurve->addCurve(((QgsCurve*)geom)->clone()); -// return cpdCurve->convertVertex(id); -// } + // If the geom is a collection, we get the concerned part, otherwise, the part is just the whole geom + QgsAbstractGeometry *part = nullptr; + QgsGeometryCollection *owningCollection = dynamic_cast( geom ); + if ( owningCollection != nullptr ) + part = owningCollection->geometryN( id.part ); + else + part = geom; -// // TODO other cases (multi-geoms, polygons...) + // If the part is a polygon, we get the concerned ring, otherwise, the ring is just the whole part + QgsAbstractGeometry *ring = nullptr; + QgsCurvePolygon *owningPolygon = dynamic_cast( part ); + if ( owningPolygon != nullptr ) + ring = ( id.ring == 0 ) ? owningPolygon->exteriorRing() : owningPolygon->interiorRing( id.ring - 1 ); + else + ring = part; + // If the ring is not a curve, we're probably on a point geometry + QgsCurve *curve = dynamic_cast( ring ); // TODO dynamic_cast -> geom_cast + if ( curve == nullptr ) + { + QgsMessageLog::logMessage( "Cannot execute convertVertex on " + geom->wktTypeStr(), "DEBUG" ); + return false; + } -// // Otherwise, it failed -// return false -// } + bool success = false; + QgsCompoundCurve *cpdCurve = dynamic_cast( curve ); + if ( cpdCurve != nullptr ) + { + QgsMessageLog::logMessage( "Already compound", "DEBUG" ); + // If the geom is a already compound curve, we convert inplace, and we're done + success = cpdCurve->convertVertex( id ); + + // // This doesn't work... Not sure how to get the geometry actuall update ? // <- REVIEW PLZ + // if ( success ) + // if ( owningCollection != nullptr ) + // reset( std::make_unique( *owningCollection ) ); // <- REVIEW PLZ + // else if ( owningPolygon != nullptr ) + // reset( std::make_unique( *owningPolygon ) ); // <- REVIEW PLZ + // else + // reset( std::make_unique( *cpdCurve ) ); // <- REVIEW PLZ + + } + else + { + // TODO : move this block before the above, so we call convertVertex only in one place + + QgsMessageLog::logMessage( "Convert to compound", "DEBUG" ); + // If the geom is a linestring or cirularstring, we create a compound curve + QgsCompoundCurve *cpdCurve = new QgsCompoundCurve(); + cpdCurve->addCurve( curve->clone() ); + success = cpdCurve->convertVertex( QgsVertexId( -1, -1, id.vertex ) ); + + // In that case, we must also reassign the instances + if ( success ) + { + QgsMessageLog::logMessage( "Success", "DEBUG" ); + + if ( owningPolygon == nullptr && owningCollection == nullptr ) + { + // Standalone linestring + QgsMessageLog::logMessage( "case A", "DEBUG" ); + reset( std::make_unique( *cpdCurve ) ); // <- REVIEW PLZ + } + + if ( owningPolygon != nullptr ) + { + // Replace the ring in the owning polygon + QgsMessageLog::logMessage( "case B", "DEBUG" ); + if ( id.ring == 0 ) + { + owningPolygon->setExteriorRing( cpdCurve ); + } + else + { + owningPolygon->removeInteriorRing( id.ring - 1 ); + owningPolygon->addInteriorRing( cpdCurve ); + } + } + else if ( owningCollection != nullptr ) + { + // Replace the curve in the owning collection + QgsMessageLog::logMessage( "case C", "DEBUG" ); + owningCollection->removeGeometry( id.part ); + owningCollection->addGeometry( cpdCurve ); + } + } + else + { + QgsMessageLog::logMessage( "failure ?!", "DEBUG" ); + + } + } + + return success; +} bool QgsGeometry::insertVertex( double x, double y, int beforeVertex ) { diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 45063797e7d..9c8ad8d3843 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -785,14 +785,14 @@ class CORE_EXPORT QgsGeometry */ bool deleteVertex( int atVertex ); - // /** - // * Converts the vertex at the given position from/to circular - // * \returns FALSE if atVertex does not correspond to a valid vertex - // * on this geometry (including if this geometry is a Point), - // * or if the specified vertex can't be converted (e.g. start/end points). - // * \since QGIS 3.20 - // */ - // bool convertVertex( int atVertex ); + /** + * Converts the vertex at the given position from/to circular + * \returns FALSE if atVertex does not correspond to a valid vertex + * on this geometry (including if this geometry is a Point), + * or if the specified vertex can't be converted (e.g. start/end points). + * \since QGIS 3.20 + */ + bool convertVertex( int atVertex ); /** * Returns coordinates of a vertex. diff --git a/test_digicurve.py b/test_digicurve.py index 0c5ad4c9232..3bf6625dd84 100644 --- a/test_digicurve.py +++ b/test_digicurve.py @@ -23,19 +23,19 @@ l2 = QgsVectorLayer("CurvePolygon?crs=epsg:4326", "test layer", "memory") QgsProject.instance().addMapLayer(l2) f1 = QgsFeature() -f1.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 0, 5 5, 10 0, 15 5, 20 0)))")) +f1.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 0, 5 5, 10 0, 15 5, 20 0, 10, -2.5)))")) f2 = QgsFeature() -f2.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 10, 5 15), CIRCULARSTRING(5 15, 10 10, 15 15), (15 15, 20 10)))")) +f2.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 10, 5 15), CIRCULARSTRING(5 15, 10 10, 15 15), (15 15, 20 10), (20 10, 10 7.5, 0 10)))")) f3 = QgsFeature() -f3.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 20, 5 25, 10 20, 15 25, 20 20, 25 25, 30 20)))")) +f3.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 20, 5 25, 10 20, 15 25, 20 20, 25 25, 30 20), (30 20, 15 17.5, 0 20)))")) f4 = QgsFeature() -f4.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 30, 5 35, 10 30), (10 30, 15 35, 20 30)))")) +f4.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 30, 5 35, 10 30), (10 30, 15 35, 20 30, 10 27.5, 0 30)))")) f5 = QgsFeature() -f5.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 50, 5 55), (5 55, 10 50, 15 55, 20 50)))")) +f5.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE((0 50, 5 55), (5 55, 10 50, 15 55, 20 50, 10 47.5, 0 50)))")) f6 = QgsFeature() -f6.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 60, 5 65, 10 60), (10 60, 15 65), CIRCULARSTRING(15 65, 20 60, 25 65)))")) +f6.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 60, 5 65, 10 60), (10 60, 15 65), CIRCULARSTRING(15 65, 20 60, 25 65), (25 65, 17.5 57.5, 0 60))")) f7 = QgsFeature() -f7.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(LINESTRING(0 70, 5 75, 10 70, 15 75, 20 70))")) +f7.setGeometry(QgsGeometry.fromWkt("CURVEPOLYGON(LINESTRING(0 70, 5 75, 10 70, 15 75, 20 70, 10 67.5, 0 70))")) l2.dataProvider().addFeatures([f1, f2, f3, f4, f5, f6, f7]) for f in l.getFeatures(): diff --git a/test_digicurve2.py b/test_digicurve2.py new file mode 100644 index 00000000000..45379c9b251 --- /dev/null +++ b/test_digicurve2.py @@ -0,0 +1,65 @@ +from itertools import count + +setup = { + 'Linestring': { + 'LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0)': 'LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0)', + 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))': 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))', + 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))': 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))', + }, + 'CompoundCurve': { + 'LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0)': 'LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0)', + 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))': 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))', + 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))': 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))', + }, + 'MultiCurve': { + 'MULTICURVE(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(5 15, 10 20, 0 20, 5 15))', + 'MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))', + 'MULTICURVE(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))', + }, + 'Polygon': { + 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))': 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))', + 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))', + 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))', + }, + 'CurvePolygon': { + 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))': 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))', + 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))', + 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))', + }, + 'MultiSurface': { + 'MULTISURFACE(CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3)), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3)), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + }, +} + +for geomtype, wkts in setup.items(): + layer = QgsVectorLayer(f"{geomtype}?crs=epsg:4326", geomtype, "memory") + i = 0 + for wkt_before, wkt_expected in wkts.items(): + feature = QgsFeature() + geom = QgsGeometry.fromWkt(wkt_before) + geom.translate(i * 15, 0) + feature.setGeometry(geom) + layer.dataProvider().addFeature(feature) + i += 1 + QgsProject.instance().addMapLayer(layer) + + +for geomtype, wkts in setup.items(): + layer = QgsVectorLayer(f"{geomtype}?crs=epsg:4326", geomtype, "memory") + i = 0 + for wkt_before, wkt_expected in wkts.items(): + feature = QgsFeature() + + geom = QgsGeometry.fromWkt(wkt_before) + + # Ensure convert has the expected result + geom.convertVertex(geom.closestVertex(QgsPointXY(10, 10))[1]) + assert geom.asWkt() == QgsGeometry.fromWkt(wkt_expected).asWkt(), 'NOT AS EXPECTED' + + # Ensure clicking again revers to previous + geom.convertVertex(geom.closestVertex(QgsPointXY(10, 10))[1]) + assert geom.asWkt() == QgsGeometry.fromWkt(wkt_before).asWkt(), 'NOT IDEMPOTENT' + + QgsProject.instance().addMapLayer(layer) diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index cb5e05f31e5..a008a348920 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -4499,140 +4499,39 @@ class TestQgsGeometry(unittest.TestCase): expected_wkt = "CompoundCurve (CircularString (-1 -1, -1.5 -0.5, -2 0),(-2 0, 2 0),CircularString (2 0, 1.5 -0.5, 1 -1))" self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - def testDeleteVertexCurvePolygon(self): + def testDeleteVertex(self): - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert not geom.deleteVertex(-1) - assert not geom.deleteVertex(4) - assert geom.deleteVertex(0) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) + # WKT BEFORE -> WKT AFTER A CONVERT ON POINT AT 10,10 + test_setup = { + # Curve + 'LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0)': 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))', + 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))': 'COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0))', + 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))': 'COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0))', - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(1) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) + # Multicurve + 'MULTICURVE(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(5 15, 10 20, 0 20, 5 15))', + 'MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))', + 'MULTICURVE(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))': 'MULTICURVE(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), LINESTRING(5 15, 10 20, 0 20, 5 15))', - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(2) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) + # Polygon + 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))': 'CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3))', + 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3)))', + 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))': 'CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3)))', - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(3) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) + # Multipolygon + 'MULTISURFACE(CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3)), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(LINESTRING(0 0, 10 0, 10 10, 0 10, 0 0), LINESTRING(3 3, 7 3, 7 7, 3 7, 3 3)), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0, 10 0, 10 10, 0 10, 0 0)), COMPOUNDCURVE(CIRCULARSTRING(3 3, 7 3, 7 7, 3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))': 'MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((0 0, 10 0), CIRCULARSTRING(10 0, 10 10, 0 10), (0 10, 0 0)), COMPOUNDCURVE((3 3, 7 3), CIRCULARSTRING(7 3, 7 7, 3 7), (3 7, 3 3))), CURVEPOLYGON(LINESTRING(5 15, 10 20, 0 20, 5 15)))', + } - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(0) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (2 0, 1.5 -0.5, 1 -1),(1 -1, 2 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(1) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1.5 -0.5, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(2) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(3) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(4) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 2 0),(2 0, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - def testConvertVertexCircularLine(self): - - wkt = "CircularString (0 0,1 1,2 0)" - geom = QgsGeometry.fromWkt(wkt) - assert geom.convertVertex(1) - expected_wkt = "Linestring (0 0, 1 1, 2 0)" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "Linestring (0 0, 1 1, 2 0)" - geom = QgsGeometry.fromWkt(wkt) - assert geom.convertVertex(1) - expected_wkt = "CircularString (0 0,1 1,2 0)" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CircularString (0 0,1 1,2 0,3 -1,4 0)" - geom = QgsGeometry.fromWkt(wkt) - assert geom.convertVertex(1) - expected_wkt = "CompoundCurve(CircularString (0 0,1 1,2 0), (3 -1,4 0))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CircularString (0 0,1 1,2 0,3 -1,4 0)" - geom = QgsGeometry.fromWkt(wkt) - assert not geom.deleteVertex(-1) - assert not geom.deleteVertex(0) - assert not geom.deleteVertex(4) - assert not geom.deleteVertex(5) - - def testConvertVertexCircularPolygon(self): - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert not geom.deleteVertex(-1) - assert not geom.deleteVertex(4) - assert geom.deleteVertex(0) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(1) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(2) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0),(2 0,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(3) - self.assertEqual(geom.asWkt(), QgsCurvePolygon().asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(0) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (2 0, 1.5 -0.5, 1 -1),(1 -1, 2 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(1) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1.5 -0.5, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(2) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(3) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 1 -1),(1 -1, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) - - wkt = "CurvePolygon (CompoundCurve (CircularString(0 0,1 1,2 0,1.5 -0.5,1 -1),(1 -1,0 0)))" - geom = QgsGeometry.fromWkt(wkt) - assert geom.deleteVertex(4) - expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 2 0),(2 0, 0 0)))" - self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(expected_wkt).asWkt()) + for wkt_before, wkt_expected in test_setup.items(): + geom = QgsGeometry.fromWkt(wkt_before) + # Ensure convert has the expected result + geom.convertVertex(geom.closestVertex(QgsPointXY(10, 10))[1]) + self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(wkt_expected).asWkt(), 'First call to convertVertex did not create expected geometry.') + # Ensure converting again returns back to initial + geom.convertVertex(geom.closestVertex(QgsPointXY(10, 10))[1]) + self.assertEqual(geom.asWkt(), QgsGeometry.fromWkt(wkt_before).asWkt(), 'Second call to convertVertex did not properly revert changes.') def testSingleSidedBuffer(self):