From 51d144ec6b130bc7b7de5492852a98ad171800d3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 2 Oct 2024 11:28:14 +0200 Subject: [PATCH 01/27] new algorithm --- src/analysis/CMakeLists.txt | 1 + .../qgsalgorithmmeshsurfacetopolygon.cpp | 193 ++++++++++++++++++ .../qgsalgorithmmeshsurfacetopolygon.h | 52 +++++ .../processing/qgsnativealgorithms.cpp | 2 + 4 files changed, 248 insertions(+) create mode 100644 src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp create mode 100644 src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 00bf1bc3f481..ed33758aaf94 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -143,6 +143,7 @@ set(QGIS_ANALYSIS_SRCS processing/qgsalgorithmloadlayer.cpp processing/qgsalgorithmmeancoordinates.cpp processing/qgsalgorithmmergelines.cpp + processing/qgsalgorithmmeshsurfacetopolygon.cpp processing/qgsalgorithmmergevector.cpp processing/qgsalgorithmminimumenclosingcircle.cpp processing/qgsalgorithmmultidifference.cpp diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp new file mode 100644 index 000000000000..0782e272bf10 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -0,0 +1,193 @@ +/*************************************************************************** + qgsalgorithmeshconvexhull.cpp + --------------------------- + begin : September 2024 + copyright : (C) 2024 by Jan Caha + email : jan.caha at outlook dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsalgorithmmeshsurfacetopolygon.h" +#include "qgsprocessingparametermeshdataset.h" +#include "qgsmeshcontours.h" +#include "qgsmeshdataset.h" +#include "qgsmeshlayer.h" +#include "qgsmeshlayerutils.h" +#include "qgsmeshlayertemporalproperties.h" +#include "qgsmeshlayerinterpolator.h" +#include "qgspolygon.h" +#include "qgsrasterfilewriter.h" +#include "qgslinestring.h" +#include "qgsgeometrycheckerutils.h" +#include "qgsgeometryengine.h" + +#include + +///@cond PRIVATE + + +QString QgsMeshSurfaceToPolygonAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm exports a polygon file containing mesh layer boundary. It may contain holes and it may be a multi-part polygon." ); +} + +QString QgsMeshSurfaceToPolygonAlgorithm::name() const +{ + return QStringLiteral( "surfacetopolygon" ); +} + +QString QgsMeshSurfaceToPolygonAlgorithm::displayName() const +{ + return QObject::tr( "Surface to polygon" ); +} + +QString QgsMeshSurfaceToPolygonAlgorithm::group() const +{ + return QObject::tr( "Mesh" ); +} + +QString QgsMeshSurfaceToPolygonAlgorithm::groupId() const +{ + return QStringLiteral( "mesh" ); +} + +QgsProcessingAlgorithm *QgsMeshSurfaceToPolygonAlgorithm::createInstance() const +{ + return new QgsMeshSurfaceToPolygonAlgorithm(); +} + +void QgsMeshSurfaceToPolygonAlgorithm::initAlgorithm( const QVariantMap &configuration ) +{ + Q_UNUSED( configuration ); + + addParameter( new QgsProcessingParameterMeshLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input mesh layer" ) ) ); + + addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS_OUTPUT" ), QObject::tr( "Output coordinate system" ), QVariant(), true ) ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Output vector layer" ), Qgis::ProcessingSourceType::VectorPolygon ) ); +} + +bool QgsMeshSurfaceToPolygonAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + QgsMeshLayer *meshLayer = parameterAsMeshLayer( parameters, QStringLiteral( "INPUT" ), context ); + + if ( !meshLayer || !meshLayer->isValid() ) + return false; + + if ( meshLayer->isEditable() ) + throw QgsProcessingException( QObject::tr( "Input mesh layer in edit mode is not supported" ) ); + + QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); + if ( !outputCrs.isValid() ) + outputCrs = meshLayer->crs(); + mTransform = QgsCoordinateTransform( meshLayer->crs(), outputCrs, context.transformContext() ); + if ( !meshLayer->nativeMesh() ) + meshLayer->updateTriangularMesh( mTransform ); //necessary to load the native mesh + + mNativeMesh = *meshLayer->nativeMesh(); + + if ( feedback ) + { + feedback->setProgressText( QObject::tr( "Preparing data" ) ); + } + + return true; +} + + +QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + feedback->setProgress( 0 ); + feedback->setProgressText( QObject::tr( "Creating output vector layer" ) ); + } + + QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); + QString identifier; + std::unique_ptr sink( parameterAsSink( parameters, + QStringLiteral( "OUTPUT" ), + context, + identifier, + QgsFields(), + Qgis::WkbType::MultiPolygon, + outputCrs ) ); + if ( !sink ) + return QVariantMap(); + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + feedback->setProgress( 0 ); + } + + std::unique_ptr< QgsGeometryEngine > geomEngine; + QgsGeometry geom; + + + for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) + { + const QgsMeshFace &face = mNativeMesh.face( i ); + QVector vertices( face.size() ); + for ( int j = 0; j < face.size(); ++j ) + vertices[j] = mNativeMesh.vertex( face.at( j ) ); + + QgsPolygon *polygon = new QgsPolygon(); + polygon->setExteriorRing( new QgsLineString( vertices ) ); + + if ( i == 0 ) + { + geom = QgsGeometry( polygon ); + } + else + { + std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( geom.get(), 0 ); + geom = QgsGeometry( geomEngine->combine( polygon ) ); + } + + feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); + } + + try + { + geom.transform( mTransform ); + } + catch ( QgsCsException & ) + { + if ( feedback ) + feedback->reportError( QObject::tr( "Could not transform point to destination CRS" ) ); + } + + QgsFeature feat; + feat.setGeometry( geom ); + + if ( !sink->addFeature( feat, QgsFeatureSink::FastInsert ) ) + throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) ); + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + + feedback->setProgressText( QObject::tr( "Output vector layer created" ) ); + + QVariantMap ret; + ret[QStringLiteral( "OUTPUT" )] = identifier; + + return ret; +} + + +///@endcond PRIVATE diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h new file mode 100644 index 000000000000..fede2478df4a --- /dev/null +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h @@ -0,0 +1,52 @@ +/*************************************************************************** + qgsalgorithmeshconvexhull.cpp + --------------------------- + begin : September 2024 + copyright : (C) 2024 by Jan Caha + email : jan.caha at outlook dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMMESHCONVEXHULL_H +#define QGSALGORITHMMESHCONVEXHULL_H + +#define SIP_NO_FILE + +#include "qgsprocessingalgorithm.h" +#include "qgsmeshdataset.h" +#include "qgsmeshdataprovider.h" +#include "qgstriangularmesh.h" +#include "qgsmeshrenderersettings.h" + +///@cond PRIVATE + +class QgsMeshSurfaceToPolygonAlgorithm : public QgsProcessingAlgorithm +{ + public: + QgsMeshSurfaceToPolygonAlgorithm() = default; + QString shortHelpString() const override; + QString name() const override; + QString displayName() const override; + QString group() const override; + QString groupId() const override; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QgsProcessingAlgorithm *createInstance() const override SIP_FACTORY; + + private: + QgsMesh mNativeMesh; + QgsCoordinateTransform mTransform; +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMMESHCONVEXHULL_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 46bf4bac5fff..d15c5ceba83e 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -127,6 +127,7 @@ #include "qgsalgorithmmeancoordinates.h" #include "qgsalgorithmmergelines.h" #include "qgsalgorithmmergevector.h" +#include "qgsalgorithmmeshsurfacetopolygon.h" #include "qgsalgorithmminimumenclosingcircle.h" #include "qgsalgorithmmultidifference.h" #include "qgsalgorithmmultiintersection.h" @@ -420,6 +421,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsMeshContoursAlgorithm ); addAlgorithm( new QgsMeshExportCrossSection ); addAlgorithm( new QgsMeshExportTimeSeries ); + addAlgorithm( new QgsMeshSurfaceToPolygonAlgorithm() ); addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() ); addAlgorithm( new QgsMultiDifferenceAlgorithm() ); addAlgorithm( new QgsMultiIntersectionAlgorithm() ); From 6fa1fe8d54c3ae5430c346f9ea7cd28e5a049723 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 2 Oct 2024 13:07:47 +0200 Subject: [PATCH 02/27] update feedback --- .../processing/qgsalgorithmmeshsurfacetopolygon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 0782e272bf10..80b4ca7daab7 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -96,7 +96,7 @@ bool QgsMeshSurfaceToPolygonAlgorithm::prepareAlgorithm( const QVariantMap ¶ if ( feedback ) { - feedback->setProgressText( QObject::tr( "Preparing data" ) ); + feedback->pushInfo( QObject::tr( "Preparing data" ) ); } return true; @@ -110,7 +110,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( feedback->isCanceled() ) return QVariantMap(); feedback->setProgress( 0 ); - feedback->setProgressText( QObject::tr( "Creating output vector layer" ) ); + feedback->pushInfo( QObject::tr( "Creating output vector layer" ) ); } QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); @@ -181,7 +181,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa return QVariantMap(); } - feedback->setProgressText( QObject::tr( "Output vector layer created" ) ); + feedback->pushInfo( QObject::tr( "Output vector layer created" ) ); QVariantMap ret; ret[QStringLiteral( "OUTPUT" )] = identifier; From 46ae5aa239339ddcc609aa6a91a1208eb1fee965 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 2 Oct 2024 13:24:55 +0200 Subject: [PATCH 03/27] allow cancel during mesh face processing --- .../processing/qgsalgorithmmeshsurfacetopolygon.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 80b4ca7daab7..26aeddc01564 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -138,6 +138,12 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) { + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + const QgsMeshFace &face = mNativeMesh.face( i ); QVector vertices( face.size() ); for ( int j = 0; j < face.size(); ++j ) @@ -177,11 +183,12 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( feedback ) { + feedback->pushInfo( QObject::tr( "Output vector layer created" ) ); if ( feedback->isCanceled() ) return QVariantMap(); } - feedback->pushInfo( QObject::tr( "Output vector layer created" ) ); + QVariantMap ret; ret[QStringLiteral( "OUTPUT" )] = identifier; From 2667d2ba4656598b16577d9e33028ec4ba0ab1d3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 2 Oct 2024 13:25:56 +0200 Subject: [PATCH 04/27] allow cancel during mesh faces processing --- .../processing/qgsalgorithmmeshsurfacetopolygon.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 26aeddc01564..067dea1647b8 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -138,11 +138,11 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) { - if ( feedback ) - { - if ( feedback->isCanceled() ) - return QVariantMap(); - } + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } const QgsMeshFace &face = mNativeMesh.face( i ); QVector vertices( face.size() ); @@ -189,7 +189,6 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } - QVariantMap ret; ret[QStringLiteral( "OUTPUT" )] = identifier; From 886d1a006d88555a4e988630c0d82deef405f0b9 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 4 Oct 2024 09:21:24 +0200 Subject: [PATCH 05/27] fix file names --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 067dea1647b8..e3e3761f6be9 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgsalgorithmeshconvexhull.cpp + qgsalgorithmmeshsurfacetopolygon.cpp --------------------------- begin : September 2024 copyright : (C) 2024 by Jan Caha diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h index fede2478df4a..bc2945117afc 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgsalgorithmeshconvexhull.cpp + qgsalgorithmmeshsurfacetopolygon.h --------------------------- begin : September 2024 copyright : (C) 2024 by Jan Caha From 63c7cdfce48ea3f768c66f494033610c02b81f5a Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 4 Oct 2024 18:27:54 +0200 Subject: [PATCH 06/27] fix the speed of the algorithm --- .../qgsalgorithmmeshsurfacetopolygon.cpp | 112 +++++++++++++++--- .../qgsalgorithmmeshsurfacetopolygon.h | 2 - 2 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index e3e3761f6be9..6a454dc3f038 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -28,6 +28,8 @@ #include "qgslinestring.h" #include "qgsgeometrycheckerutils.h" #include "qgsgeometryengine.h" +#include "qgsmultilinestring.h" +#include "qgsmultipolygon.h" #include @@ -132,9 +134,12 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa feedback->setProgress( 0 ); } - std::unique_ptr< QgsGeometryEngine > geomEngine; - QgsGeometry geom; + QgsGeometry lines; + QgsMeshFace face; + QMap, int> edges; // edge as key and count of edge occurence as value + std::pair edge; + feedback->setProgressText( "Parsing mesh faces to extract edges." ); for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) { @@ -144,30 +149,105 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa return QVariantMap(); } - const QgsMeshFace &face = mNativeMesh.face( i ); - QVector vertices( face.size() ); - for ( int j = 0; j < face.size(); ++j ) - vertices[j] = mNativeMesh.vertex( face.at( j ) ); + face = mNativeMesh.face( i ); - QgsPolygon *polygon = new QgsPolygon(); - polygon->setExteriorRing( new QgsLineString( vertices ) ); + for ( int j = 0; j < face.size(); j++ ) + { + int indexEnd; + if ( j == face.size() - 1 ) + indexEnd = 0; + else + indexEnd = j + 1; + int edgeFirstVertex = face.at( j ); + int edgeSecondVertex = face.at( indexEnd ); + + // make vertex sorted to avoid have 1,2 and 2,1 as different keys + if ( edgeSecondVertex < edgeFirstVertex ) + edge = std::make_pair( edgeSecondVertex, edgeFirstVertex ); + else + edge = std::make_pair( edgeFirstVertex, edgeSecondVertex ); + + // if edge exist in map increase its count otherwise set count to 1 + auto it = edges.find( edge ); + if ( it != edges.end() ) + { + int count = edges.take( edge ) + 1; + edges.insert( edge, count ); + } + else + { + edges.insert( edge, 1 ); + } + } + + feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); + } - if ( i == 0 ) + feedback->setProgress( 0 ); + feedback->setProgressText( "Parsing mesh edges." ); + + std::unique_ptr multiLineString( new QgsMultiLineString() ); + + int i = 0; + for ( auto it = edges.begin(); it != edges.end(); it++ ) + { + if ( feedback ) { - geom = QgsGeometry( polygon ); + if ( feedback->isCanceled() ) + return QVariantMap(); } - else + + // only consider edges with count 1 which are on the edge of mesh surface + if ( it.value() == 1 ) { - std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( geom.get(), 0 ); - geom = QgsGeometry( geomEngine->combine( polygon ) ); + std::unique_ptr line( new QgsLineString( mNativeMesh.vertex( it.key().first ), mNativeMesh.vertex( it.key().second ) ) ); + multiLineString->addGeometry( line.release() ); } - feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); + feedback->setProgress( 100 * i / edges.size() ); + i++; } + feedback->setProgressText( "Creating final geometry." ); + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + + // merge lines + QgsGeometry mergedLines = QgsGeometry( multiLineString.release() ); + mergedLines = mergedLines.mergeLines(); + QgsAbstractGeometry *multiLinesAbstract = mergedLines.get(); + + // create resulting multipolygon + std::unique_ptr multiPolygon = std::make_unique(); + + // for every part create polygon and add to resulting multipolygon + for ( int i = 0; i < mergedLines.get()->partCount(); i++ ) + { + for ( auto pit = multiLinesAbstract->const_parts_begin(); pit != multiLinesAbstract->const_parts_end(); ++pit ) + { + std::unique_ptr polygon = std::make_unique(); + polygon->setExteriorRing( qgsgeometry_cast< QgsLineString * >( *pit )->clone() ); + multiPolygon->addGeometry( polygon.release() ); + } + + } + + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + + // create final geom and transform it + QgsGeometry resultGeom = QgsGeometry( multiPolygon.release() ); + try { - geom.transform( mTransform ); + resultGeom.transform( mTransform ); } catch ( QgsCsException & ) { @@ -176,7 +256,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } QgsFeature feat; - feat.setGeometry( geom ); + feat.setGeometry( resultGeom ); if ( !sink->addFeature( feat, QgsFeatureSink::FastInsert ) ) throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) ); diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h index bc2945117afc..482d3c2b8dff 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.h @@ -21,10 +21,8 @@ #define SIP_NO_FILE #include "qgsprocessingalgorithm.h" -#include "qgsmeshdataset.h" #include "qgsmeshdataprovider.h" #include "qgstriangularmesh.h" -#include "qgsmeshrenderersettings.h" ///@cond PRIVATE From 1843f325829165cc01d5d1a0fc03f219d6109704 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Fri, 4 Oct 2024 18:29:06 +0200 Subject: [PATCH 07/27] drop not needed imports --- .../processing/qgsalgorithmmeshsurfacetopolygon.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 6a454dc3f038..e58e7566a79f 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -17,17 +17,9 @@ #include "qgsalgorithmmeshsurfacetopolygon.h" #include "qgsprocessingparametermeshdataset.h" -#include "qgsmeshcontours.h" -#include "qgsmeshdataset.h" #include "qgsmeshlayer.h" -#include "qgsmeshlayerutils.h" -#include "qgsmeshlayertemporalproperties.h" -#include "qgsmeshlayerinterpolator.h" #include "qgspolygon.h" -#include "qgsrasterfilewriter.h" #include "qgslinestring.h" -#include "qgsgeometrycheckerutils.h" -#include "qgsgeometryengine.h" #include "qgsmultilinestring.h" #include "qgsmultipolygon.h" From 62fa703426b652ecbea65dcb04c981a6ba75c170 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Sat, 5 Oct 2024 11:42:46 +0200 Subject: [PATCH 08/27] fix issues --- .../qgsalgorithmmeshsurfacetopolygon.cpp | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index e58e7566a79f..5423af94e7a8 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -128,10 +128,11 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa QgsGeometry lines; QgsMeshFace face; - QMap, int> edges; // edge as key and count of edge occurence as value + QMap, int> edges; // edge as key and count of edge usage as value std::pair edge; - feedback->setProgressText( "Parsing mesh faces to extract edges." ); + if ( feedback ) + feedback->setProgressText( "Parsing mesh faces to extract edges." ); for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) { @@ -172,12 +173,15 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } } - feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); + if ( feedback ) + feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); } - feedback->setProgress( 0 ); - feedback->setProgressText( "Parsing mesh edges." ); - + if ( feedback ) + { + feedback->setProgress( 0 ); + feedback->setProgressText( "Parsing mesh edges." ); + } std::unique_ptr multiLineString( new QgsMultiLineString() ); int i = 0; @@ -195,15 +199,16 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa std::unique_ptr line( new QgsLineString( mNativeMesh.vertex( it.key().first ), mNativeMesh.vertex( it.key().second ) ) ); multiLineString->addGeometry( line.release() ); } + if ( feedback ) + feedback->setProgress( 100 * i / edges.size() ); - feedback->setProgress( 100 * i / edges.size() ); i++; } - feedback->setProgressText( "Creating final geometry." ); if ( feedback ) { + feedback->setProgressText( "Creating final geometry." ); if ( feedback->isCanceled() ) return QVariantMap(); } @@ -221,6 +226,12 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa { for ( auto pit = multiLinesAbstract->const_parts_begin(); pit != multiLinesAbstract->const_parts_end(); ++pit ) { + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + std::unique_ptr polygon = std::make_unique(); polygon->setExteriorRing( qgsgeometry_cast< QgsLineString * >( *pit )->clone() ); multiPolygon->addGeometry( polygon.release() ); @@ -260,7 +271,6 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa return QVariantMap(); } - QVariantMap ret; ret[QStringLiteral( "OUTPUT" )] = identifier; From eae1ba00d9f1570475b3b9d2b552c3c432332eaa Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Sat, 5 Oct 2024 11:44:45 +0200 Subject: [PATCH 09/27] more readable --- .../processing/qgsalgorithmmeshsurfacetopolygon.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 5423af94e7a8..0ea8b792e1f9 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -88,11 +88,6 @@ bool QgsMeshSurfaceToPolygonAlgorithm::prepareAlgorithm( const QVariantMap ¶ mNativeMesh = *meshLayer->nativeMesh(); - if ( feedback ) - { - feedback->pushInfo( QObject::tr( "Preparing data" ) ); - } - return true; } @@ -182,6 +177,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa feedback->setProgress( 0 ); feedback->setProgressText( "Parsing mesh edges." ); } + std::unique_ptr multiLineString( new QgsMultiLineString() ); int i = 0; @@ -199,6 +195,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa std::unique_ptr line( new QgsLineString( mNativeMesh.vertex( it.key().first ), mNativeMesh.vertex( it.key().second ) ) ); multiLineString->addGeometry( line.release() ); } + if ( feedback ) feedback->setProgress( 100 * i / edges.size() ); From 5f56704ac73356de4295ebb8e427140f3cf017d2 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 23 Oct 2024 13:41:40 +0200 Subject: [PATCH 10/27] add mesh layer --- python/plugins/processing/tests/AlgorithmsTestBase.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 3323c5477ac8..72777cc93fa0 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -41,6 +41,7 @@ QgsCoordinateReferenceSystem, QgsFeatureRequest, QgsMapLayer, + QgsMeshLayer, QgsProject, QgsApplication, QgsProcessingContext, @@ -145,6 +146,9 @@ def check_algorithm(self, name, defs): print('Running alg: "{}"'.format(defs['algorithm'])) alg = QgsApplication.processingRegistry().createAlgorithmById(defs['algorithm']) + if alg is None: + print('Algorithm not found: {}'.format(defs['algorithm'])) + return parameters = {} if isinstance(params, list): @@ -217,7 +221,7 @@ def load_param(self, param, id=None): parameter based on its key `type` and return the appropriate parameter to pass to the algorithm. """ try: - if param['type'] in ('vector', 'raster', 'table'): + if param['type'] in ('vector', 'raster', 'table', 'mesh'): return self.load_layer(id, param).id() elif param['type'] == 'vrtlayers': vals = [] @@ -320,6 +324,8 @@ def load_layer(self, id, param): options = QgsRasterLayer.LayerOptions() options.loadDefaultStyle = False lyr = QgsRasterLayer(filepath, param['name'], 'gdal', options) + elif param['type'] == 'mesh': + lyr = QgsMeshLayer(filepath, param['name'], "mdal") self.assertTrue(lyr.isValid(), f'Could not load layer "{filepath}" from param {param}') QgsProject.instance().addMapLayer(lyr) From fcfccece11ad3e31d57129c26c324b1153b3ca50 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 23 Oct 2024 13:41:51 +0200 Subject: [PATCH 11/27] test case --- .../processing/tests/AlgorithmsTestBase.py | 3 --- .../expected/mesh_surface_to_polygon.gml | 16 ++++++++++++++++ .../tests/testdata/qgis_algorithm_tests5.yaml | 8 ++++++++ .../tests/testdata/quad_and_triangle.2dm | 8 ++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml create mode 100644 python/plugins/processing/tests/testdata/quad_and_triangle.2dm diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 72777cc93fa0..6ebaf0e2ed16 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -146,9 +146,6 @@ def check_algorithm(self, name, defs): print('Running alg: "{}"'.format(defs['algorithm'])) alg = QgsApplication.processingRegistry().createAlgorithmById(defs['algorithm']) - if alg is None: - print('Algorithm not found: {}'.format(defs['algorithm'])) - return parameters = {} if isinstance(params, list): diff --git a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml new file mode 100644 index 000000000000..6cca9568f6d5 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml @@ -0,0 +1,16 @@ + + + 1000 20003000 3000 + + + + 1000 20003000 3000 + 1000 2000 2000 2000 3000 2000 2000 3000 1000 3000 1000 2000 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml index 7939ff6ccd80..26bd4a93e5ad 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml @@ -247,3 +247,11 @@ tests: name: expected/create_grid_negative_overlay.gml type: vector + - algorithm: native:surfacetopolygon + name: Create polygon from surface of Mesh layer + params: + INPUT: 2DM:"quad_and_triangle.2dm" + results: + OUTPUT: + name: expected/mesh_surface_to_polygon.gml + type: vector diff --git a/python/plugins/processing/tests/testdata/quad_and_triangle.2dm b/python/plugins/processing/tests/testdata/quad_and_triangle.2dm new file mode 100644 index 000000000000..01a1aeaa95b0 --- /dev/null +++ b/python/plugins/processing/tests/testdata/quad_and_triangle.2dm @@ -0,0 +1,8 @@ +MESH2D 1000.000 2000.000 0.000000 200 300 1.000 1.000 +ND 1 1000.000 2000.000 20.000 +ND 2 2000.000 2000.000 30.000 +ND 3 3000.000 2000.000 40.000 +ND 4 2000.000 3000.000 50.000 +ND 5 1000.000 3000.000 10.000 +E4Q 1 1 2 4 5 1 +E3T 2 2 3 4 1 From 6f138878ae4ceb81afa239e27267013ba4668ad4 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 23 Oct 2024 14:12:40 +0200 Subject: [PATCH 12/27] simplify to avoid warning --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 0ea8b792e1f9..3e33ac5d051b 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -69,7 +69,7 @@ void QgsMeshSurfaceToPolygonAlgorithm::initAlgorithm( const QVariantMap &configu addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Output vector layer" ), Qgis::ProcessingSourceType::VectorPolygon ) ); } -bool QgsMeshSurfaceToPolygonAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +bool QgsMeshSurfaceToPolygonAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { QgsMeshLayer *meshLayer = parameterAsMeshLayer( parameters, QStringLiteral( "INPUT" ), context ); From 639e36686a1f9af01ecc432f9f944ecf9426da75 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 23 Oct 2024 16:49:19 +0200 Subject: [PATCH 13/27] fix issue --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 3e33ac5d051b..911ea7b6b5f5 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -169,7 +169,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } if ( feedback ) - feedback->setProgress( 100 * i / mNativeMesh.faceCount() ); + feedback->setProgress( 100.0 * static_cast( i ) / mNativeMesh.faceCount() ); } if ( feedback ) @@ -197,7 +197,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } if ( feedback ) - feedback->setProgress( 100 * i / edges.size() ); + feedback->setProgress( 100.0 * static_cast( i ) / edges.size() ); i++; } From f9ab5f9bb2540ebd0e4b76c1d69ae2ffc1b926f3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 24 Oct 2024 11:14:30 +0200 Subject: [PATCH 14/27] fix data call --- .../processing/tests/testdata/qgis_algorithm_tests5.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml index 26bd4a93e5ad..86c115e650f7 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml @@ -250,7 +250,9 @@ tests: - algorithm: native:surfacetopolygon name: Create polygon from surface of Mesh layer params: - INPUT: 2DM:"quad_and_triangle.2dm" + INPUT: + name: "quad_and_triangle.2dm" + type: mesh results: OUTPUT: name: expected/mesh_surface_to_polygon.gml From c183c081fd4c6462fa7c25e3413966d0ba8e1f7b Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 24 Oct 2024 12:52:39 +0200 Subject: [PATCH 15/27] fix call --- .../processing/tests/testdata/qgis_algorithm_tests5.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml index 86c115e650f7..16578e37b9a6 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml @@ -251,7 +251,7 @@ tests: name: Create polygon from surface of Mesh layer params: INPUT: - name: "quad_and_triangle.2dm" + name: quad_and_triangle.2dm type: mesh results: OUTPUT: From 235dfa9007da87dbdb59f350cfcd1fab07f145e0 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 24 Oct 2024 12:52:50 +0200 Subject: [PATCH 16/27] fix expected data --- .../expected/mesh_surface_to_polygon.gml | 8 ++-- .../expected/mesh_surface_to_polygon.xsd | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.xsd diff --git a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml index 6cca9568f6d5..b15b731e4923 100644 --- a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml +++ b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.gml @@ -2,15 +2,15 @@ 1000 20003000 3000 - + 1000 20003000 3000 - 1000 2000 2000 2000 3000 2000 2000 3000 1000 3000 1000 2000 - + 1000 2000 2000 2000 3000 2000 2000 3000 1000 3000 1000 2000 + diff --git a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.xsd b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.xsd new file mode 100644 index 000000000000..21f8960b3e6f --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon.xsd @@ -0,0 +1,47 @@ + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From dc25026b312364d503990f294faa2ff795d42ffb Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 27 Nov 2024 13:28:19 +0100 Subject: [PATCH 17/27] handle meshes with holes --- .../qgsalgorithmmeshsurfacetopolygon.cpp | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 911ea7b6b5f5..a21c57b742ec 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -22,6 +22,7 @@ #include "qgslinestring.h" #include "qgsmultilinestring.h" #include "qgsmultipolygon.h" +#include "qgsgeometryengine.h" #include @@ -202,7 +203,6 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa i++; } - if ( feedback ) { feedback->setProgressText( "Creating final geometry." ); @@ -215,27 +215,65 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa mergedLines = mergedLines.mergeLines(); QgsAbstractGeometry *multiLinesAbstract = mergedLines.get(); - // create resulting multipolygon - std::unique_ptr multiPolygon = std::make_unique(); + // set of polygons to consturct result + QVector polygons; // for every part create polygon and add to resulting multipolygon - for ( int i = 0; i < mergedLines.get()->partCount(); i++ ) + for ( auto pit = multiLinesAbstract->const_parts_begin(); pit != multiLinesAbstract->const_parts_end(); ++pit ) { - for ( auto pit = multiLinesAbstract->const_parts_begin(); pit != multiLinesAbstract->const_parts_end(); ++pit ) + if ( feedback ) + { + if ( feedback->isCanceled() ) + return QVariantMap(); + } + + // individula polygon - can be either polygon or hole in polygon + QgsPolygon *polygon = new QgsPolygon(); + polygon->setExteriorRing( qgsgeometry_cast< QgsLineString * >( *pit )->clone() ); + + // add first polygon, no need to check anything + if ( polygons.empty() ) { - if ( feedback ) + polygons.push_back( polygon ); + continue; + } + + // engine for spatial relations + std::unique_ptr engine( QgsGeometry::createGeometryEngine( polygon ) ); + + // need to check if polygon is not either contained (hole) or covering (main polygon) with another + // this solves meshes with holes + bool isHole = false; + + for ( int i = 0; i < polygons.count(); i++ ) + { + QgsPolygon *p = qgsgeometry_cast ( polygons.at( i ) ); + + // polygon covers another, turn contained polygon into interior ring + if ( engine->contains( p ) ) { - if ( feedback->isCanceled() ) - return QVariantMap(); + polygons.removeAt( i ); + polygon->addInteriorRing( p->exteriorRing() ); + break; + } + // polygon is within another, make it interior rind and do not add it + else if ( engine->within( p ) ) + { + p->addInteriorRing( polygon->exteriorRing() ); + isHole = true; + break; } - - std::unique_ptr polygon = std::make_unique(); - polygon->setExteriorRing( qgsgeometry_cast< QgsLineString * >( *pit )->clone() ); - multiPolygon->addGeometry( polygon.release() ); } + // if is not a hole polygon add it to the vector of polygons + if ( ! isHole ) + polygons.append( polygon ); } + // create resulting multipolygon + std::unique_ptr multiPolygon = std::make_unique(); + multiPolygon->addGeometries( polygons ); + if ( feedback ) { if ( feedback->isCanceled() ) @@ -274,5 +312,4 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa return ret; } - ///@endcond PRIVATE From 23a9c724646458b172afd8c247835aadb4c470e3 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 27 Nov 2024 13:28:31 +0100 Subject: [PATCH 18/27] add test case --- ...h_surface_to_polygon_complex_mesh_hole.gml | 16 +++++ ...h_surface_to_polygon_complex_mesh_hole.xsd | 47 +++++++++++++ .../testdata/mesh_two_part_with_hole.2dm | 68 +++++++++++++++++++ .../tests/testdata/qgis_algorithm_tests5.yaml | 11 +++ 4 files changed, 142 insertions(+) create mode 100644 python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.gml create mode 100644 python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.xsd create mode 100644 python/plugins/processing/tests/testdata/mesh_two_part_with_hole.2dm diff --git a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.gml b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.gml new file mode 100644 index 000000000000..4653ded25f57 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.gml @@ -0,0 +1,16 @@ + + + -0.28285357 -10.3031914941.54679257 15.32259074 + + + + -0.28285357 -10.3031914941.54679257 15.32259074 + -0.28285357 -5.15175219 0 0 0.0 7.5 0 10 5.0 9.5 5.10513141 15.27002503 22.95118899 15.32259074 28.07634543 15.24374218 26.65707134 9.80319149 25.57947434 -1.10419274 21.66332916 -5.91395494 15.80225282 -10.30319149 10.25657071 -10.25062578 -0.28285357 -5.1517521910 0 10 10 15.53942428 9.96088861 16.42244099 7.57792771 15.01376721 -0.18429287 10 032.85840157 5.894131 37.37097674 6.83705716 41.54679257 12.62931782 34.20543893 11.48433606 32.85840157 5.894131 + + + diff --git a/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.xsd b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.xsd new file mode 100644 index 000000000000..58c91b2bda13 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/mesh_surface_to_polygon_complex_mesh_hole.xsd @@ -0,0 +1,47 @@ + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/mesh_two_part_with_hole.2dm b/python/plugins/processing/tests/testdata/mesh_two_part_with_hole.2dm new file mode 100644 index 000000000000..238373d02c20 --- /dev/null +++ b/python/plugins/processing/tests/testdata/mesh_two_part_with_hole.2dm @@ -0,0 +1,68 @@ +MESH2D +ND 1 0 0 0 +ND 2 10 0 0 +ND 3 10 10 0 +ND 4 5 4 0 +ND 5 5 2 0 +ND 6 0 7.5 0 +ND 7 0 10 0 +ND 8 5 9.5 0 +ND 9 5 6.75 0 +ND 10 15.53942428 9.96088861 0 +ND 11 15.01376721 -0.18429287 0 +ND 12 12.38548185 14.61295369 0 +ND 13 22.29411765 9.8557572 0 +ND 14 5.10513141 15.27002503 0 +ND 15 22.95118899 15.32259074 0 +ND 16 10.17772215 -5.23060075 0 +ND 17 -0.28285357 -5.15175219 0 +ND 18 14.90863579 -5.41458073 0 +ND 19 10.25657071 -10.25062578 0 +ND 20 15.80225282 -10.30319149 0 +ND 21 21.47934919 -1.07790989 0 +ND 22 21.66332916 -5.91395494 0 +ND 23 26.65707134 9.80319149 0 +ND 24 25.57947434 -1.10419274 0 +ND 25 28.07634543 15.24374218 0 +ND 26 16.42244099 7.57792771 0 +ND 27 15.93402616 -0.31148313 0 +ND 28 32.85840157 5.894131 0 +ND 29 34.20543893 11.48433606 0 +ND 30 37.37097674 6.83705716 0 +ND 31 41.54679257 12.62931782 0 +E3T 1 3 8 2 +E3T 2 8 9 2 +E3T 3 9 4 2 +E3T 4 4 5 2 +E3T 5 5 1 2 +E3T 6 5 6 1 +E3T 7 6 5 7 +E3T 8 10 12 3 +E3T 9 12 8 3 +E3T 10 13 12 10 +E3T 11 12 14 8 +E3T 12 13 15 12 +E3T 13 12 15 14 +E3T 14 7 5 4 +E3T 15 9 7 4 +E3T 16 9 8 7 +E3T 17 1 16 2 +E3T 18 1 17 16 +E4Q 19 16 18 11 2 +E3T 20 17 19 16 +E4Q 21 19 20 18 16 +E3T 22 18 22 21 +E3T 23 20 22 18 +E3T 24 13 23 15 +E3T 25 13 21 23 +E3T 26 21 24 23 +E3T 27 21 22 24 +E3T 28 23 25 15 +E3T 29 26 11 13 +E3T 30 26 13 10 +E3T 31 11 18 27 +E3T 32 27 18 21 +E3T 33 11 27 13 +E3T 34 27 21 13 +E3T 35 28 30 29 +E3T 36 30 31 29 diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml index 16578e37b9a6..06f8a70b5663 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests5.yaml @@ -257,3 +257,14 @@ tests: OUTPUT: name: expected/mesh_surface_to_polygon.gml type: vector + + - algorithm: native:surfacetopolygon + name: Create polygon from surface of Mesh layer - multiple parts with hole + params: + INPUT: + name: mesh_two_part_with_hole.2dm + type: mesh + results: + OUTPUT: + name: expected/mesh_surface_to_polygon_complex_mesh_hole.gml + type: vector From 73b0c3167f96d22e92a8c0b2aac7c0d315fb33a7 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Thu, 5 Dec 2024 09:16:49 +0100 Subject: [PATCH 19/27] merge from master --- .pre-commit-config.yaml | 55 +++ .../processing/tests/AlgorithmsTestBase.py | 362 +++++++++++------- 2 files changed, 275 insertions(+), 142 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000000..79f313670df9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +exclude: | + (?x)^( + python/.*/auto_\w+/.*.py| + external/.*| + tests/testdata/script_with_error.py + )$ +fail_fast: false + +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v19.1.3 + # if adding directory handled by clang-format + # you need to adapt prepare_commit and verify_indentation scripts accordingly + hooks: + - id: clang-format + types_or: [c++, c, c#] + exclude: | + (?x)^( + src/core/.*| + src/gui/.*| + src/analysis/.*| + src/3d/.*| + src/server/.*| + tests/code_layout/sipify/sipifyheader.h + )$ + + - repo: https://github.com/asottile/pyupgrade + rev: v3.19.0 + hooks: + - id: pyupgrade + args: [--py39-plus] + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black", "--filter-files"] + + - repo: local + hooks: + - id: prepare_commit + name: prepare_commit + entry: ./scripts/prepare_commit.sh + language: system + always_run: true + pass_filenames: false + +ci: + autofix_prs: true + autoupdate_schedule: quarterly diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 6ebaf0e2ed16..08637ac8186d 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -15,9 +15,9 @@ *************************************************************************** """ -__author__ = 'Matthias Kuhn' -__date__ = 'January 2016' -__copyright__ = '(C) 2016, Matthias Kuhn' +__author__ = "Matthias Kuhn" +__date__ = "January 2016" +__copyright__ = "(C) 2016, Matthias Kuhn" import os @@ -35,22 +35,22 @@ from copy import deepcopy from qgis.PyQt.QtCore import QT_VERSION -from qgis.core import (Qgis, - QgsVectorLayer, - QgsRasterLayer, - QgsCoordinateReferenceSystem, - QgsFeatureRequest, - QgsMapLayer, - QgsMeshLayer, - QgsProject, - QgsApplication, - QgsProcessingContext, - QgsProcessingUtils, - QgsProcessingFeedback) -from qgis.analysis import (QgsNativeAlgorithms) -from qgis.testing import (_UnexpectedSuccess, - QgisTestCase, - start_app) +from qgis.core import ( + Qgis, + QgsVectorLayer, + QgsRasterLayer, + QgsCoordinateReferenceSystem, + QgsFeatureRequest, + QgsMapLayer, + QgsMeshLayer, + QgsProject, + QgsApplication, + QgsProcessingContext, + QgsProcessingUtils, + QgsProcessingFeedback, +) +from qgis.analysis import QgsNativeAlgorithms +from qgis.testing import _UnexpectedSuccess, QgisTestCase, start_app from utilities import unitTestDataPath import processing @@ -59,11 +59,11 @@ def GDAL_COMPUTE_VERSION(maj, min, rev): - return ((maj) * 1000000 + (min) * 10000 + (rev) * 100) + return (maj) * 1000000 + (min) * 10000 + (rev) * 100 def processingTestDataPath(): - return os.path.join(os.path.dirname(__file__), 'testdata') + return os.path.join(os.path.dirname(__file__), "testdata") class AlgorithmsTest: @@ -72,51 +72,95 @@ def test_algorithms(self): """ This is the main test function. All others will be executed based on the definitions in testdata/algorithm_tests.yaml """ - with open(os.path.join(processingTestDataPath(), self.definition_file())) as stream: + with open( + os.path.join(processingTestDataPath(), self.definition_file()) + ) as stream: algorithm_tests = yaml.load(stream, Loader=yaml.SafeLoader) - if 'tests' in algorithm_tests and algorithm_tests['tests'] is not None: - for idx, algtest in enumerate(algorithm_tests['tests']): - condition = algtest.get('condition') + if "tests" in algorithm_tests and algorithm_tests["tests"] is not None: + for idx, algtest in enumerate(algorithm_tests["tests"]): + condition = algtest.get("condition") if condition: - geos_condition = condition.get('geos') + geos_condition = condition.get("geos") if geos_condition: - less_than_condition = geos_condition.get('less_than') + less_than_condition = geos_condition.get("less_than") if less_than_condition: if Qgis.geosVersionInt() >= less_than_condition: - print('!!! Skipping {}, requires GEOS < {}, have version {}'.format(algtest['name'], less_than_condition, Qgis.geosVersionInt())) + print( + "!!! Skipping {}, requires GEOS < {}, have version {}".format( + algtest["name"], + less_than_condition, + Qgis.geosVersionInt(), + ) + ) continue - at_least_condition = geos_condition.get('at_least') + at_least_condition = geos_condition.get("at_least") if at_least_condition: if Qgis.geosVersionInt() < at_least_condition: - print('!!! Skipping {}, requires GEOS >= {}, have version {}'.format(algtest['name'], at_least_condition, Qgis.geosVersionInt())) + print( + "!!! Skipping {}, requires GEOS >= {}, have version {}".format( + algtest["name"], + at_least_condition, + Qgis.geosVersionInt(), + ) + ) continue - gdal_condition = condition.get('gdal') + gdal_condition = condition.get("gdal") if gdal_condition: - less_than_condition = gdal_condition.get('less_than') + less_than_condition = gdal_condition.get("less_than") if less_than_condition: - if int(gdal.VersionInfo('VERSION_NUM')) >= less_than_condition: - print('!!! Skipping {}, requires GDAL < {}, have version {}'.format(algtest['name'], less_than_condition, gdal.VersionInfo('VERSION_NUM'))) + if ( + int(gdal.VersionInfo("VERSION_NUM")) + >= less_than_condition + ): + print( + "!!! Skipping {}, requires GDAL < {}, have version {}".format( + algtest["name"], + less_than_condition, + gdal.VersionInfo("VERSION_NUM"), + ) + ) continue - at_least_condition = gdal_condition.get('at_least') + at_least_condition = gdal_condition.get("at_least") if at_least_condition: - if int(gdal.VersionInfo('VERSION_NUM')) < at_least_condition: - print('!!! Skipping {}, requires GDAL >= {}, have version {}'.format(algtest['name'], at_least_condition, gdal.VersionInfo('VERSION_NUM'))) + if ( + int(gdal.VersionInfo("VERSION_NUM")) + < at_least_condition + ): + print( + "!!! Skipping {}, requires GDAL >= {}, have version {}".format( + algtest["name"], + at_least_condition, + gdal.VersionInfo("VERSION_NUM"), + ) + ) continue - qt_condition = condition.get('qt') + qt_condition = condition.get("qt") if qt_condition: - less_than_condition = qt_condition.get('less_than') + less_than_condition = qt_condition.get("less_than") if less_than_condition: if QT_VERSION >= less_than_condition: - print('!!! Skipping {}, requires Qt < {}, have version {}'.format(algtest['name'], less_than_condition, QT_VERSION)) + print( + "!!! Skipping {}, requires Qt < {}, have version {}".format( + algtest["name"], less_than_condition, QT_VERSION + ) + ) continue - at_least_condition = qt_condition.get('at_least') + at_least_condition = qt_condition.get("at_least") if at_least_condition: if QT_VERSION < at_least_condition: - print('!!! Skipping {}, requires Qt >= {}, have version {}'.format(algtest['name'], at_least_condition, QT_VERSION)) + print( + "!!! Skipping {}, requires Qt >= {}, have version {}".format( + algtest["name"], at_least_condition, QT_VERSION + ) + ) continue - print('About to start {} of {}: "{}"'.format(idx, len(algorithm_tests['tests']), algtest['name'])) - yield self.check_algorithm, algtest['name'], algtest + print( + 'About to start {} of {}: "{}"'.format( + idx, len(algorithm_tests["tests"]), algtest["name"] + ) + ) + yield self.check_algorithm, algtest["name"], algtest def check_algorithm(self, name, defs): """ @@ -127,25 +171,29 @@ def check_algorithm(self, name, defs): self.vector_layer_params = {} QgsProject.instance().clear() - if 'project' in defs: - full_project_path = os.path.join(processingTestDataPath(), defs['project']) + if "project" in defs: + full_project_path = os.path.join(processingTestDataPath(), defs["project"]) project_read_success = QgsProject.instance().read(full_project_path) - self.assertTrue(project_read_success, 'Failed to load project file: ' + defs['project']) - - if 'project_crs' in defs: - QgsProject.instance().setCrs(QgsCoordinateReferenceSystem(defs['project_crs'])) + self.assertTrue( + project_read_success, "Failed to load project file: " + defs["project"] + ) + + if "project_crs" in defs: + QgsProject.instance().setCrs( + QgsCoordinateReferenceSystem(defs["project_crs"]) + ) else: QgsProject.instance().setCrs(QgsCoordinateReferenceSystem()) - if 'ellipsoid' in defs: - QgsProject.instance().setEllipsoid(defs['ellipsoid']) + if "ellipsoid" in defs: + QgsProject.instance().setEllipsoid(defs["ellipsoid"]) else: - QgsProject.instance().setEllipsoid('') + QgsProject.instance().setEllipsoid("") - params = self.load_params(defs['params']) + params = self.load_params(defs["params"]) - print('Running alg: "{}"'.format(defs['algorithm'])) - alg = QgsApplication.processingRegistry().createAlgorithmById(defs['algorithm']) + print('Running alg: "{}"'.format(defs["algorithm"])) + alg = QgsApplication.processingRegistry().createAlgorithmById(defs["algorithm"]) parameters = {} if isinstance(params, list): @@ -155,51 +203,53 @@ def check_algorithm(self, name, defs): for k, p in params.items(): parameters[k] = p - for r, p in list(defs['results'].items()): - if 'in_place_result' not in p or not p['in_place_result']: + for r, p in list(defs["results"].items()): + if "in_place_result" not in p or not p["in_place_result"]: parameters[r] = self.load_result_param(p) expectFailure = False - if 'expectedFailure' in defs: - exec(('\n'.join(defs['expectedFailure'][:-1])), globals(), locals()) - expectFailure = eval(defs['expectedFailure'][-1]) + if "expectedFailure" in defs: + exec(("\n".join(defs["expectedFailure"][:-1])), globals(), locals()) + expectFailure = eval(defs["expectedFailure"][-1]) - if 'expectedException' in defs: + if "expectedException" in defs: expectFailure = True # ignore user setting for invalid geometry handling context = QgsProcessingContext() context.setProject(QgsProject.instance()) - if 'ellipsoid' in defs: + if "ellipsoid" in defs: # depending on the project settings, we can't always rely # on QgsProject.ellipsoid() returning the same ellipsoid as was # specified in the test definition. So just force ensure that the # context's ellipsoid is the desired one - context.setEllipsoid(defs['ellipsoid']) + context.setEllipsoid(defs["ellipsoid"]) - if 'skipInvalid' in defs and defs['skipInvalid']: - context.setInvalidGeometryCheck(QgsFeatureRequest.InvalidGeometryCheck.GeometrySkipInvalid) + if "skipInvalid" in defs and defs["skipInvalid"]: + context.setInvalidGeometryCheck( + QgsFeatureRequest.InvalidGeometryCheck.GeometrySkipInvalid + ) feedback = QgsProcessingFeedback() - print(f'Algorithm parameters are {parameters}') + print(f"Algorithm parameters are {parameters}") # first check that algorithm accepts the parameters we pass... ok, msg = alg.checkParameterValues(parameters, context) - self.assertTrue(ok, f'Algorithm failed checkParameterValues with result {msg}') + self.assertTrue(ok, f"Algorithm failed checkParameterValues with result {msg}") if expectFailure: try: results, ok = alg.run(parameters, context, feedback) - self.check_results(results, context, parameters, defs['results']) + self.check_results(results, context, parameters, defs["results"]) if ok: raise _UnexpectedSuccess except Exception: pass else: results, ok = alg.run(parameters, context, feedback) - self.assertTrue(ok, f'params: {parameters}, results: {results}') - self.check_results(results, context, parameters, defs['results']) + self.assertTrue(ok, f"params: {parameters}, results: {results}") + self.check_results(results, context, parameters, defs["results"]) def load_params(self, params): """ @@ -218,68 +268,75 @@ def load_param(self, param, id=None): parameter based on its key `type` and return the appropriate parameter to pass to the algorithm. """ try: - if param['type'] in ('vector', 'raster', 'table', 'mesh'): + if param["type"] in ("vector", "raster", "table", "mesh"): return self.load_layer(id, param).id() - elif param['type'] == 'vrtlayers': + elif param["type"] == "vrtlayers": vals = [] - for p in param['params']: - p['layer'] = self.load_layer(None, {'type': 'vector', 'name': p['layer']}) + for p in param["params"]: + p["layer"] = self.load_layer( + None, {"type": "vector", "name": p["layer"]} + ) vals.append(p) return vals - elif param['type'] == 'multi': - return [self.load_param(p) for p in param['params']] - elif param['type'] == 'file': + elif param["type"] == "multi": + return [self.load_param(p) for p in param["params"]] + elif param["type"] == "file": return self.filepath_from_param(param) - elif param['type'] == 'interpolation': + elif param["type"] == "interpolation": prefix = processingTestDataPath() - tmp = '' - for r in param['name'].split('::|::'): - v = r.split('::~::') - tmp += '{}::~::{}::~::{}::~::{};'.format(os.path.join(prefix, v[0]), - v[1], v[2], v[3]) + tmp = "" + for r in param["name"].split("::|::"): + v = r.split("::~::") + tmp += "{}::~::{}::~::{}::~::{};".format( + os.path.join(prefix, v[0]), v[1], v[2], v[3] + ) return tmp[:-1] except TypeError: # No type specified, use whatever is there return param - raise KeyError("Unknown type '{}' specified for parameter".format(param['type'])) + raise KeyError( + "Unknown type '{}' specified for parameter".format(param["type"]) + ) def load_result_param(self, param): """ Loads a result parameter. Creates a temporary destination where the result should go to and returns this location so it can be sent to the algorithm as parameter. """ - if param['type'] in ['vector', 'file', 'table', 'regex']: + if param["type"] in ["vector", "file", "table", "regex"]: outdir = tempfile.mkdtemp() self.cleanup_paths.append(outdir) - if isinstance(param['name'], str): - basename = os.path.basename(param['name']) + if isinstance(param["name"], str): + basename = os.path.basename(param["name"]) else: - basename = os.path.basename(param['name'][0]) + basename = os.path.basename(param["name"][0]) filepath = self.uri_path_join(outdir, basename) return filepath - elif param['type'] == 'rasterhash': + elif param["type"] == "rasterhash": outdir = tempfile.mkdtemp() self.cleanup_paths.append(outdir) - basename = 'raster.tif' + basename = "raster.tif" filepath = os.path.join(outdir, basename) return filepath - elif param['type'] == 'directory': + elif param["type"] == "directory": outdir = tempfile.mkdtemp() return outdir - raise KeyError("Unknown type '{}' specified for parameter".format(param['type'])) + raise KeyError( + "Unknown type '{}' specified for parameter".format(param["type"]) + ) def load_layers(self, id, param): layers = [] - if param['type'] in ('vector', 'table'): - if isinstance(param['name'], str) or 'uri' in param: + if param["type"] in ("vector", "table"): + if isinstance(param["name"], str) or "uri" in param: layers.append(self.load_layer(id, param)) else: - for n in param['name']: + for n in param["name"]: layer_param = deepcopy(param) - layer_param['name'] = n + layer_param["name"] = n layers.append(self.load_layer(id, layer_param)) else: layers.append(self.load_layer(id, param)) @@ -292,39 +349,41 @@ def load_layer(self, id, param): filepath = self.filepath_from_param(param) - if 'in_place' in param and param['in_place']: + if "in_place" in param and param["in_place"]: # check if alg modifies layer in place tmpdir = tempfile.mkdtemp() self.cleanup_paths.append(tmpdir) path, file_name = os.path.split(filepath) base, ext = os.path.splitext(file_name) - for file in glob.glob(os.path.join(path, f'{base}.*')): + for file in glob.glob(os.path.join(path, f"{base}.*")): shutil.copy(os.path.join(path, file), tmpdir) filepath = os.path.join(tmpdir, file_name) self.in_place_layers[id] = filepath - if param['type'] in ('vector', 'table'): - gmlrex = r'\.gml\b' + if param["type"] in ("vector", "table"): + gmlrex = r"\.gml\b" if re.search(gmlrex, filepath, re.IGNORECASE): # ewwwww - we have to force SRS detection for GML files, otherwise they'll be loaded # with no srs - filepath += '|option:FORCE_SRS_DETECTION=YES' + filepath += "|option:FORCE_SRS_DETECTION=YES" if filepath in self.vector_layer_params: return self.vector_layer_params[filepath] options = QgsVectorLayer.LayerOptions() options.loadDefaultStyle = False - lyr = QgsVectorLayer(filepath, param['name'], 'ogr', options) + lyr = QgsVectorLayer(filepath, param["name"], "ogr", options) self.vector_layer_params[filepath] = lyr - elif param['type'] == 'raster': + elif param["type"] == "raster": options = QgsRasterLayer.LayerOptions() options.loadDefaultStyle = False - lyr = QgsRasterLayer(filepath, param['name'], 'gdal', options) - elif param['type'] == 'mesh': - lyr = QgsMeshLayer(filepath, param['name'], "mdal") + lyr = QgsRasterLayer(filepath, param["name"], "gdal", options) + elif param["type"] == "mesh": + lyr = QgsMeshLayer(filepath, param["name"], "mdal") - self.assertTrue(lyr.isValid(), f'Could not load layer "{filepath}" from param {param}') + self.assertTrue( + lyr.isValid(), f'Could not load layer "{filepath}" from param {param}' + ) QgsProject.instance().addMapLayer(lyr) return lyr @@ -333,13 +392,13 @@ def filepath_from_param(self, param): Creates a filepath from a param """ prefix = processingTestDataPath() - if 'location' in param and param['location'] == 'qgs': + if "location" in param and param["location"] == "qgs": prefix = unitTestDataPath() - if 'uri' in param: - path = param['uri'] + if "uri" in param: + path = param["uri"] else: - path = param['name'] + path = param["name"] if not path: return None @@ -347,7 +406,7 @@ def filepath_from_param(self, param): return self.uri_path_join(prefix, path) def uri_path_join(self, prefix, filepath): - if filepath.startswith('ogr:'): + if filepath.startswith("ogr:"): if not prefix[-1] == os.path.sep: prefix += os.path.sep filepath = re.sub(r"dbname='", f"dbname='{prefix}", filepath) @@ -361,88 +420,105 @@ def check_results(self, results, context, params, expected): Checks if result produced by an algorithm matches with the expected specification. """ for id, expected_result in expected.items(): - if expected_result['type'] in ('vector', 'table'): - if 'compare' in expected_result and not expected_result['compare']: + if expected_result["type"] in ("vector", "table"): + if "compare" in expected_result and not expected_result["compare"]: # skipping the comparison, so just make sure output is valid if isinstance(results[id], QgsMapLayer): result_lyr = results[id] else: - result_lyr = QgsProcessingUtils.mapLayerFromString(results[id], context) + result_lyr = QgsProcessingUtils.mapLayerFromString( + results[id], context + ) self.assertTrue(result_lyr.isValid()) continue expected_lyrs = self.load_layers(id, expected_result) - if 'in_place_result' in expected_result: - result_lyr = QgsProcessingUtils.mapLayerFromString(self.in_place_layers[id], context) + if "in_place_result" in expected_result: + result_lyr = QgsProcessingUtils.mapLayerFromString( + self.in_place_layers[id], context + ) self.assertTrue(result_lyr.isValid(), self.in_place_layers[id]) else: try: results[id] except KeyError as e: - raise KeyError(f'Expected result {str(e)} does not exist in {list(results.keys())}') + raise KeyError( + f"Expected result {str(e)} does not exist in {list(results.keys())}" + ) if isinstance(results[id], QgsMapLayer): result_lyr = results[id] else: string = results[id] - gmlrex = r'\.gml\b' + gmlrex = r"\.gml\b" if re.search(gmlrex, string, re.IGNORECASE): # ewwwww - we have to force SRS detection for GML files, otherwise they'll be loaded # with no srs - string += '|option:FORCE_SRS_DETECTION=YES' + string += "|option:FORCE_SRS_DETECTION=YES" - result_lyr = QgsProcessingUtils.mapLayerFromString(string, context) + result_lyr = QgsProcessingUtils.mapLayerFromString( + string, context + ) self.assertTrue(result_lyr, results[id]) - compare = expected_result.get('compare', {}) - pk = expected_result.get('pk', None) + compare = expected_result.get("compare", {}) + pk = expected_result.get("pk", None) if len(expected_lyrs) == 1: - self.assertLayersEqual(expected_lyrs[0], result_lyr, compare=compare, pk=pk) + self.assertLayersEqual( + expected_lyrs[0], result_lyr, compare=compare, pk=pk + ) else: res = False for l in expected_lyrs: if self.checkLayersEqual(l, result_lyr, compare=compare, pk=pk): res = True break - self.assertTrue(res, 'Could not find matching layer in expected results') + self.assertTrue( + res, "Could not find matching layer in expected results" + ) - elif 'rasterhash' == expected_result['type']: + elif "rasterhash" == expected_result["type"]: print(f"id:{id} result:{results[id]}") - self.assertTrue(os.path.exists(results[id]), f'File does not exist: {results[id]}, {params}') + self.assertTrue( + os.path.exists(results[id]), + f"File does not exist: {results[id]}, {params}", + ) dataset = gdal.Open(results[id], GA_ReadOnly) dataArray = nan_to_num(dataset.ReadAsArray(0)) strhash = hashlib.sha224(dataArray.data).hexdigest() - if not isinstance(expected_result['hash'], str): - self.assertIn(strhash, expected_result['hash']) + if not isinstance(expected_result["hash"], str): + self.assertIn(strhash, expected_result["hash"]) else: - self.assertEqual(strhash, expected_result['hash']) - elif 'file' == expected_result['type']: + self.assertEqual(strhash, expected_result["hash"]) + elif "file" == expected_result["type"]: result_filepath = results[id] - if isinstance(expected_result.get('name'), list): + if isinstance(expected_result.get("name"), list): # test to see if any match expected - for path in expected_result['name']: - expected_filepath = self.filepath_from_param({'name': path}) + for path in expected_result["name"]: + expected_filepath = self.filepath_from_param({"name": path}) if self.checkFilesEqual(expected_filepath, result_filepath): break else: - expected_filepath = self.filepath_from_param({'name': expected_result['name'][0]}) + expected_filepath = self.filepath_from_param( + {"name": expected_result["name"][0]} + ) else: expected_filepath = self.filepath_from_param(expected_result) self.assertFilesEqual(expected_filepath, result_filepath) - elif 'directory' == expected_result['type']: + elif "directory" == expected_result["type"]: expected_dirpath = self.filepath_from_param(expected_result) result_dirpath = results[id] self.assertDirectoriesEqual(expected_dirpath, result_dirpath) - elif 'regex' == expected_result['type']: + elif "regex" == expected_result["type"]: with open(results[id]) as file: data = file.read() - for rule in expected_result.get('rules', []): + for rule in expected_result.get("rules", []): self.assertRegex(data, rule) @@ -455,21 +531,23 @@ class GenericAlgorithmsTest(QgisTestCase): def setUpClass(cls): start_app() from processing.core.Processing import Processing + Processing.initialize() cls.cleanup_paths = [] @classmethod def tearDownClass(cls): from processing.core.Processing import Processing + Processing.deinitialize() for path in cls.cleanup_paths: shutil.rmtree(path) def testAlgorithmCompliance(self): for p in QgsApplication.processingRegistry().providers(): - print(f'testing provider {p.id()}') + print(f"testing provider {p.id()}") for a in p.algorithms(): - print(f'testing algorithm {a.id()}') + print(f"testing algorithm {a.id()}") self.check_algorithm(a) def check_algorithm(self, alg): @@ -477,5 +555,5 @@ def check_algorithm(self, alg): alg.helpUrl() -if __name__ == '__main__': +if __name__ == "__main__": nose2.main() From 370c034ec053c342044e1b6d00484d33289e2ce5 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 08:52:22 +0100 Subject: [PATCH 20/27] Update src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp fix translation Co-authored-by: Stefanos Natsis --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index a21c57b742ec..2a4e49e65600 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -128,7 +128,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa std::pair edge; if ( feedback ) - feedback->setProgressText( "Parsing mesh faces to extract edges." ); + feedback->setProgressText( QObject::tr( "Parsing mesh faces to extract edges." ) ); for ( int i = 0; i < mNativeMesh.faceCount(); i++ ) { From 37adda588d2ed41321103bdad2b33661ca7c0525 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 08:52:28 +0100 Subject: [PATCH 21/27] Update src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp fix translation Co-authored-by: Stefanos Natsis --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 2a4e49e65600..0b97c2dd691d 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -176,7 +176,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( feedback ) { feedback->setProgress( 0 ); - feedback->setProgressText( "Parsing mesh edges." ); + feedback->setProgressText( QObject::tr( "Parsing mesh edges." ) ); } std::unique_ptr multiLineString( new QgsMultiLineString() ); From 3b4ae07e6e34746f86612affff15d8c6a41dde9c Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 08:52:34 +0100 Subject: [PATCH 22/27] Update src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp fix translation Co-authored-by: Stefanos Natsis --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 0b97c2dd691d..c700eb0e118d 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -205,7 +205,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( feedback ) { - feedback->setProgressText( "Creating final geometry." ); + feedback->setProgressText( QObject::( "Creating final geometry." ) ); if ( feedback->isCanceled() ) return QVariantMap(); } From 558fa1790f09afed9aed169553bb063184f0b30b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:54:27 +0000 Subject: [PATCH 23/27] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../qgsalgorithmmeshsurfacetopolygon.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index c700eb0e118d..c0c11b4c739a 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -105,13 +105,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa QgsCoordinateReferenceSystem outputCrs = parameterAsCrs( parameters, QStringLiteral( "CRS_OUTPUT" ), context ); QString identifier; - std::unique_ptr sink( parameterAsSink( parameters, - QStringLiteral( "OUTPUT" ), - context, - identifier, - QgsFields(), - Qgis::WkbType::MultiPolygon, - outputCrs ) ); + std::unique_ptr sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, identifier, QgsFields(), Qgis::WkbType::MultiPolygon, outputCrs ) ); if ( !sink ) return QVariantMap(); @@ -229,7 +223,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa // individula polygon - can be either polygon or hole in polygon QgsPolygon *polygon = new QgsPolygon(); - polygon->setExteriorRing( qgsgeometry_cast< QgsLineString * >( *pit )->clone() ); + polygon->setExteriorRing( qgsgeometry_cast( *pit )->clone() ); // add first polygon, no need to check anything if ( polygons.empty() ) @@ -247,7 +241,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa for ( int i = 0; i < polygons.count(); i++ ) { - QgsPolygon *p = qgsgeometry_cast ( polygons.at( i ) ); + QgsPolygon *p = qgsgeometry_cast( polygons.at( i ) ); // polygon covers another, turn contained polygon into interior ring if ( engine->contains( p ) ) @@ -266,7 +260,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa } // if is not a hole polygon add it to the vector of polygons - if ( ! isHole ) + if ( !isHole ) polygons.append( polygon ); } From 4c488a73977eae24b91f3aeacc3474024e7222cd Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 09:11:22 +0100 Subject: [PATCH 24/27] fix translation --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index c0c11b4c739a..3fa459c9380e 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -199,7 +199,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( feedback ) { - feedback->setProgressText( QObject::( "Creating final geometry." ) ); + feedback->setProgressText( QObject::tr( "Creating final geometry." ) ); if ( feedback->isCanceled() ) return QVariantMap(); } From 19db67ae27672c3bf401aa35ae3aa3f92fa1f9e5 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 09:11:50 +0100 Subject: [PATCH 25/27] typo --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 3fa459c9380e..eb244e0c601b 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -209,7 +209,7 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa mergedLines = mergedLines.mergeLines(); QgsAbstractGeometry *multiLinesAbstract = mergedLines.get(); - // set of polygons to consturct result + // set of polygons to construct result QVector polygons; // for every part create polygon and add to resulting multipolygon From 50de8a2b3067c125f24083082e8355a1f8f2b8ec Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 09:12:05 +0100 Subject: [PATCH 26/27] clone --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index eb244e0c601b..875063384cca 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -247,13 +247,13 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa if ( engine->contains( p ) ) { polygons.removeAt( i ); - polygon->addInteriorRing( p->exteriorRing() ); + polygon->addInteriorRing( p->exteriorRing()->clone() ); break; } // polygon is within another, make it interior rind and do not add it else if ( engine->within( p ) ) { - p->addInteriorRing( polygon->exteriorRing() ); + p->addInteriorRing( polygon->exteriorRing()->clone() ); isHole = true; break; } From 7198d787bb985cf534b7742efacdd437d35ad6c8 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 11 Dec 2024 09:13:52 +0100 Subject: [PATCH 27/27] delete to avoid memory leak --- src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp index 875063384cca..7f1d0a1b8589 100644 --- a/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp +++ b/src/analysis/processing/qgsalgorithmmeshsurfacetopolygon.cpp @@ -262,6 +262,9 @@ QVariantMap QgsMeshSurfaceToPolygonAlgorithm::processAlgorithm( const QVariantMa // if is not a hole polygon add it to the vector of polygons if ( !isHole ) polygons.append( polygon ); + else + // polygon was used as a hole, it is not needed anymore, delete it to avoid memory leak + delete polygon; } // create resulting multipolygon