diff --git a/python/PyQt6/core/auto_additions/qgsvectorfilewriter.py b/python/PyQt6/core/auto_additions/qgsvectorfilewriter.py index b6086598335..5e5a1f1d74b 100644 --- a/python/PyQt6/core/auto_additions/qgsvectorfilewriter.py +++ b/python/PyQt6/core/auto_additions/qgsvectorfilewriter.py @@ -49,7 +49,7 @@ try: except (NameError, AttributeError): pass try: - QgsVectorFileWriter.SaveVectorOptions.__attribute_docs__ = {'driverName': 'OGR driver to use', 'layerName': 'Layer name. If let empty, it will be derived from the filename', 'actionOnExistingFile': 'Action on existing file', 'fileEncoding': 'Encoding to use', 'ct': 'Transform to reproject exported geometries with, or invalid transform\nfor no transformation', 'onlySelectedFeatures': 'Write only selected features of layer', 'datasourceOptions': 'List of OGR data source creation options', 'layerOptions': 'List of OGR layer creation options', 'skipAttributeCreation': 'Only write geometries', 'attributes': 'Attributes to export (empty means all unless skipAttributeCreation is set)', 'attributesExportNames': 'Attributes export names', 'symbologyExport': 'Symbology to export', 'symbologyScale': 'Scale of symbology', 'filterExtent': 'If not empty, only features intersecting the extent will be saved', 'overrideGeometryType': 'Set to a valid geometry type to override the default geometry type for the layer. This parameter\nallows for conversion of geometryless tables to null geometries, etc.', 'forceMulti': 'Sets to ``True`` to force creation of multipart geometries', 'includeZ': 'Sets to ``True`` to include z dimension in output. This option is only valid if overrideGeometryType is set', 'fieldValueConverter': 'Field value converter.\n\nOwnership is not transferred and callers must ensure that the lifetime of fieldValueConverter\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.', 'feedback': 'Optional feedback object allowing cancellation of layer save', 'fieldNameSource': 'Source for exported field names.\n\n.. versionadded:: 3.18', 'saveMetadata': 'Set to ``True`` to save layer metadata for the exported vector file.\n\n.. seealso:: :py:func:`layerMetadata`\n\n.. versionadded:: 3.20', 'layerMetadata': 'Layer metadata to save for the exported vector file. This will only be used if saveMetadata is ``True``.\n\n.. seealso:: :py:func:`saveMetadata`\n\n.. versionadded:: 3.20', 'includeConstraints': 'Set to ``True`` to transfer field constraints to the exported vector file.\n\nSupport for field constraints depends on the output file format.\n\n.. versionadded:: 3.34', 'setFieldDomains': 'Set to ``True`` to transfer field domains to the exported vector file.\n\nSupport for field domains depends on the output file format.\n\n.. note::\n\n Only available in builds based on GDAL 3.5 or later\n\n.. versionadded:: 3.36', 'sourceDatabaseProviderConnection': 'Source database provider connection, for field domains.\n\nOwnership is not transferred and callers must ensure that the lifetime of sourceDatabaseProviderConnection\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.\n\n.. versionadded:: 3.36'} + QgsVectorFileWriter.SaveVectorOptions.__attribute_docs__ = {'driverName': 'OGR driver to use', 'layerName': 'Layer name. If let empty, it will be derived from the filename', 'actionOnExistingFile': 'Action on existing file', 'fileEncoding': 'Encoding to use', 'ct': 'Transform to reproject exported geometries with, or invalid transform\nfor no transformation', 'onlySelectedFeatures': 'Write only selected features of layer', 'datasourceOptions': 'List of OGR data source creation options', 'layerOptions': 'List of OGR layer creation options', 'skipAttributeCreation': 'Only write geometries', 'attributes': 'Attributes to export (empty means all unless skipAttributeCreation is set)', 'attributesExportNames': 'Attributes export names', 'symbologyExport': 'Symbology to export', 'symbologyScale': 'Scale of symbology', 'filterExtent': "If not empty, only features intersecting the extent will be saved.\n\nThe filter extent should be in the destination CRS (if transforming), or the layer's\nCRS if no valid transform is set.", 'overrideGeometryType': 'Set to a valid geometry type to override the default geometry type for the layer. This parameter\nallows for conversion of geometryless tables to null geometries, etc.', 'forceMulti': 'Sets to ``True`` to force creation of multipart geometries', 'includeZ': 'Sets to ``True`` to include z dimension in output. This option is only valid if overrideGeometryType is set', 'fieldValueConverter': 'Field value converter.\n\nOwnership is not transferred and callers must ensure that the lifetime of fieldValueConverter\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.', 'feedback': 'Optional feedback object allowing cancellation of layer save', 'fieldNameSource': 'Source for exported field names.\n\n.. versionadded:: 3.18', 'saveMetadata': 'Set to ``True`` to save layer metadata for the exported vector file.\n\n.. seealso:: :py:func:`layerMetadata`\n\n.. versionadded:: 3.20', 'layerMetadata': 'Layer metadata to save for the exported vector file. This will only be used if saveMetadata is ``True``.\n\n.. seealso:: :py:func:`saveMetadata`\n\n.. versionadded:: 3.20', 'includeConstraints': 'Set to ``True`` to transfer field constraints to the exported vector file.\n\nSupport for field constraints depends on the output file format.\n\n.. versionadded:: 3.34', 'setFieldDomains': 'Set to ``True`` to transfer field domains to the exported vector file.\n\nSupport for field domains depends on the output file format.\n\n.. note::\n\n Only available in builds based on GDAL 3.5 or later\n\n.. versionadded:: 3.36', 'sourceDatabaseProviderConnection': 'Source database provider connection, for field domains.\n\nOwnership is not transferred and callers must ensure that the lifetime of sourceDatabaseProviderConnection\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.\n\n.. versionadded:: 3.36'} QgsVectorFileWriter.SaveVectorOptions.__annotations__ = {'driverName': str, 'layerName': str, 'actionOnExistingFile': 'QgsVectorFileWriter.ActionOnExistingFile', 'fileEncoding': str, 'ct': 'QgsCoordinateTransform', 'onlySelectedFeatures': bool, 'datasourceOptions': 'List[str]', 'layerOptions': 'List[str]', 'skipAttributeCreation': bool, 'attributes': 'QgsAttributeList', 'attributesExportNames': 'List[str]', 'symbologyExport': 'Qgis.FeatureSymbologyExport', 'symbologyScale': float, 'filterExtent': 'QgsRectangle', 'overrideGeometryType': 'Qgis.WkbType', 'forceMulti': bool, 'includeZ': bool, 'fieldValueConverter': 'QgsVectorFileWriter.FieldValueConverter', 'feedback': 'QgsFeedback', 'fieldNameSource': 'QgsVectorFileWriter.FieldNameSource', 'saveMetadata': bool, 'layerMetadata': 'QgsLayerMetadata', 'includeConstraints': bool, 'setFieldDomains': bool, 'sourceDatabaseProviderConnection': 'QgsAbstractDatabaseProviderConnection'} except (NameError, AttributeError): pass diff --git a/python/core/auto_additions/qgsvectorfilewriter.py b/python/core/auto_additions/qgsvectorfilewriter.py index 59fb2e9f286..cda61806637 100644 --- a/python/core/auto_additions/qgsvectorfilewriter.py +++ b/python/core/auto_additions/qgsvectorfilewriter.py @@ -5,7 +5,7 @@ try: except (NameError, AttributeError): pass try: - QgsVectorFileWriter.SaveVectorOptions.__attribute_docs__ = {'driverName': 'OGR driver to use', 'layerName': 'Layer name. If let empty, it will be derived from the filename', 'actionOnExistingFile': 'Action on existing file', 'fileEncoding': 'Encoding to use', 'ct': 'Transform to reproject exported geometries with, or invalid transform\nfor no transformation', 'onlySelectedFeatures': 'Write only selected features of layer', 'datasourceOptions': 'List of OGR data source creation options', 'layerOptions': 'List of OGR layer creation options', 'skipAttributeCreation': 'Only write geometries', 'attributes': 'Attributes to export (empty means all unless skipAttributeCreation is set)', 'attributesExportNames': 'Attributes export names', 'symbologyExport': 'Symbology to export', 'symbologyScale': 'Scale of symbology', 'filterExtent': 'If not empty, only features intersecting the extent will be saved', 'overrideGeometryType': 'Set to a valid geometry type to override the default geometry type for the layer. This parameter\nallows for conversion of geometryless tables to null geometries, etc.', 'forceMulti': 'Sets to ``True`` to force creation of multipart geometries', 'includeZ': 'Sets to ``True`` to include z dimension in output. This option is only valid if overrideGeometryType is set', 'fieldValueConverter': 'Field value converter.\n\nOwnership is not transferred and callers must ensure that the lifetime of fieldValueConverter\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.', 'feedback': 'Optional feedback object allowing cancellation of layer save', 'fieldNameSource': 'Source for exported field names.\n\n.. versionadded:: 3.18', 'saveMetadata': 'Set to ``True`` to save layer metadata for the exported vector file.\n\n.. seealso:: :py:func:`layerMetadata`\n\n.. versionadded:: 3.20', 'layerMetadata': 'Layer metadata to save for the exported vector file. This will only be used if saveMetadata is ``True``.\n\n.. seealso:: :py:func:`saveMetadata`\n\n.. versionadded:: 3.20', 'includeConstraints': 'Set to ``True`` to transfer field constraints to the exported vector file.\n\nSupport for field constraints depends on the output file format.\n\n.. versionadded:: 3.34', 'setFieldDomains': 'Set to ``True`` to transfer field domains to the exported vector file.\n\nSupport for field domains depends on the output file format.\n\n.. note::\n\n Only available in builds based on GDAL 3.5 or later\n\n.. versionadded:: 3.36', 'sourceDatabaseProviderConnection': 'Source database provider connection, for field domains.\n\nOwnership is not transferred and callers must ensure that the lifetime of sourceDatabaseProviderConnection\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.\n\n.. versionadded:: 3.36'} + QgsVectorFileWriter.SaveVectorOptions.__attribute_docs__ = {'driverName': 'OGR driver to use', 'layerName': 'Layer name. If let empty, it will be derived from the filename', 'actionOnExistingFile': 'Action on existing file', 'fileEncoding': 'Encoding to use', 'ct': 'Transform to reproject exported geometries with, or invalid transform\nfor no transformation', 'onlySelectedFeatures': 'Write only selected features of layer', 'datasourceOptions': 'List of OGR data source creation options', 'layerOptions': 'List of OGR layer creation options', 'skipAttributeCreation': 'Only write geometries', 'attributes': 'Attributes to export (empty means all unless skipAttributeCreation is set)', 'attributesExportNames': 'Attributes export names', 'symbologyExport': 'Symbology to export', 'symbologyScale': 'Scale of symbology', 'filterExtent': "If not empty, only features intersecting the extent will be saved.\n\nThe filter extent should be in the destination CRS (if transforming), or the layer's\nCRS if no valid transform is set.", 'overrideGeometryType': 'Set to a valid geometry type to override the default geometry type for the layer. This parameter\nallows for conversion of geometryless tables to null geometries, etc.', 'forceMulti': 'Sets to ``True`` to force creation of multipart geometries', 'includeZ': 'Sets to ``True`` to include z dimension in output. This option is only valid if overrideGeometryType is set', 'fieldValueConverter': 'Field value converter.\n\nOwnership is not transferred and callers must ensure that the lifetime of fieldValueConverter\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.', 'feedback': 'Optional feedback object allowing cancellation of layer save', 'fieldNameSource': 'Source for exported field names.\n\n.. versionadded:: 3.18', 'saveMetadata': 'Set to ``True`` to save layer metadata for the exported vector file.\n\n.. seealso:: :py:func:`layerMetadata`\n\n.. versionadded:: 3.20', 'layerMetadata': 'Layer metadata to save for the exported vector file. This will only be used if saveMetadata is ``True``.\n\n.. seealso:: :py:func:`saveMetadata`\n\n.. versionadded:: 3.20', 'includeConstraints': 'Set to ``True`` to transfer field constraints to the exported vector file.\n\nSupport for field constraints depends on the output file format.\n\n.. versionadded:: 3.34', 'setFieldDomains': 'Set to ``True`` to transfer field domains to the exported vector file.\n\nSupport for field domains depends on the output file format.\n\n.. note::\n\n Only available in builds based on GDAL 3.5 or later\n\n.. versionadded:: 3.36', 'sourceDatabaseProviderConnection': 'Source database provider connection, for field domains.\n\nOwnership is not transferred and callers must ensure that the lifetime of sourceDatabaseProviderConnection\nexceeds the lifetime of the :py:class:`QgsVectorFileWriter` object.\n\n.. versionadded:: 3.36'} QgsVectorFileWriter.SaveVectorOptions.__annotations__ = {'driverName': str, 'layerName': str, 'actionOnExistingFile': 'QgsVectorFileWriter.ActionOnExistingFile', 'fileEncoding': str, 'ct': 'QgsCoordinateTransform', 'onlySelectedFeatures': bool, 'datasourceOptions': 'List[str]', 'layerOptions': 'List[str]', 'skipAttributeCreation': bool, 'attributes': 'QgsAttributeList', 'attributesExportNames': 'List[str]', 'symbologyExport': 'Qgis.FeatureSymbologyExport', 'symbologyScale': float, 'filterExtent': 'QgsRectangle', 'overrideGeometryType': 'Qgis.WkbType', 'forceMulti': bool, 'includeZ': bool, 'fieldValueConverter': 'QgsVectorFileWriter.FieldValueConverter', 'feedback': 'QgsFeedback', 'fieldNameSource': 'QgsVectorFileWriter.FieldNameSource', 'saveMetadata': bool, 'layerMetadata': 'QgsLayerMetadata', 'includeConstraints': bool, 'setFieldDomains': bool, 'sourceDatabaseProviderConnection': 'QgsAbstractDatabaseProviderConnection'} except (NameError, AttributeError): pass diff --git a/src/analysis/processing/qgsalgorithmpackage.cpp b/src/analysis/processing/qgsalgorithmpackage.cpp index c3f63e2f764..e59fc404751 100644 --- a/src/analysis/processing/qgsalgorithmpackage.cpp +++ b/src/analysis/processing/qgsalgorithmpackage.cpp @@ -64,6 +64,12 @@ void QgsPackageAlgorithm::initAlgorithm( const QVariantMap & ) auto extentParam = std::make_unique( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ), QVariant(), true ); extentParam->setHelp( QObject::tr( "Limit exported features to those with geometries intersecting the provided extent" ) ); addParameter( extentParam.release() ); + + auto crsParam = std::make_unique< QgsProcessingParameterCrs >( QStringLiteral( "CRS" ), QObject::tr( "Destination CRS" ), QVariant(), true ); + crsParam->setFlags( crsParam->flags() | Qgis::ProcessingParameterFlag::Advanced ); + crsParam->setHelp( QObject::tr( "If set, all layers will be transformed to the destination CRS during packaging." ) ); + addParameter( std::move( crsParam ) ); + addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) ); } @@ -98,6 +104,8 @@ bool QgsPackageAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsPr feedback->reportError( QObject::tr( "No layers selected, geopackage will be empty" ), false ); } + mDestinationCrs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context ); + return true; } @@ -354,7 +362,7 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap ¶meters feedback->pushWarning( QObject::tr( "No spatial index exists for layer %1, performance will be severely degraded" ).arg( vectorLayer->name() ) ); } - extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, layer->crs() ); + extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, mDestinationCrs.isValid() ? mDestinationCrs : layer->crs() ); } if ( !packageVectorLayer( vectorLayer, packagePath, context, &multiStepFeedback, saveStyles, saveMetadata, selectedFeaturesOnly, extent ) ) @@ -440,6 +448,11 @@ bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QStri options.saveMetadata = true; } + if ( mDestinationCrs.isValid() ) + { + options.ct = QgsCoordinateTransform( layer->crs(), mDestinationCrs, context.transformContext() ); + } + // Check FID compatibility with GPKG and remove any existing FID field if not compatible, // let this be completely recreated since many layer sources have fid fields which are // not compatible with gpkg requirements diff --git a/src/analysis/processing/qgsalgorithmpackage.h b/src/analysis/processing/qgsalgorithmpackage.h index 97e787ee774..59eb627b93e 100644 --- a/src/analysis/processing/qgsalgorithmpackage.h +++ b/src/analysis/processing/qgsalgorithmpackage.h @@ -53,6 +53,7 @@ class QgsPackageAlgorithm : public QgsProcessingAlgorithm std::vector> mLayers; QMap mClonedLayerIds; + QgsCoordinateReferenceSystem mDestinationCrs; }; ///@endcond PRIVATE diff --git a/src/core/qgsvectorfilewriter.h b/src/core/qgsvectorfilewriter.h index 1fef4e1ea26..d4c0fa69ca0 100644 --- a/src/core/qgsvectorfilewriter.h +++ b/src/core/qgsvectorfilewriter.h @@ -501,7 +501,12 @@ class CORE_EXPORT QgsVectorFileWriter : public QgsFeatureSink //! Scale of symbology double symbologyScale = 1.0; - //! If not empty, only features intersecting the extent will be saved + /** + * If not empty, only features intersecting the extent will be saved. + * + * The filter extent should be in the destination CRS (if transforming), or the layer's + * CRS if no valid transform is set. + */ QgsRectangle filterExtent; /** diff --git a/tests/src/python/test_processing_packagelayers.py b/tests/src/python/test_processing_packagelayers.py index b17abeddeb5..4ac4953c89d 100644 --- a/tests/src/python/test_processing_packagelayers.py +++ b/tests/src/python/test_processing_packagelayers.py @@ -25,6 +25,9 @@ from qgis.core import ( QgsRelation, QgsSettings, QgsVectorLayer, + QgsReferencedRectangle, + QgsCoordinateReferenceSystem, + QgsRectangle, ) import unittest from qgis.testing import start_app, QgisTestCase @@ -110,11 +113,11 @@ class TestPackageLayers(QgisTestCase): f = ogr.Feature(lyr.GetLayerDefn()) f["name"] = "region one" outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2580000, 1220500) - outring.AddPoint(2581000, 1220500) - outring.AddPoint(2581000, 1221500) - outring.AddPoint(2580000, 1221500) - outring.AddPoint(2580000, 1220500) + outring.AddPoint_2D(2580000, 1220500) + outring.AddPoint_2D(2581000, 1220500) + outring.AddPoint_2D(2581000, 1221500) + outring.AddPoint_2D(2580000, 1221500) + outring.AddPoint_2D(2580000, 1220500) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -122,11 +125,11 @@ class TestPackageLayers(QgisTestCase): f = ogr.Feature(lyr.GetLayerDefn()) f["name"] = "region two" outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2581000, 1220000) - outring.AddPoint(2582000, 1220000) - outring.AddPoint(2582000, 1221000) - outring.AddPoint(2581000, 1221000) - outring.AddPoint(2581000, 1220000) + outring.AddPoint_2D(2581000, 1220000) + outring.AddPoint_2D(2582000, 1220000) + outring.AddPoint_2D(2582000, 1221000) + outring.AddPoint_2D(2581000, 1221000) + outring.AddPoint_2D(2581000, 1220000) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -139,11 +142,11 @@ class TestPackageLayers(QgisTestCase): f["name"] = "province one" f["region"] = 1 outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2580000, 1220500) - outring.AddPoint(2580500, 1220500) - outring.AddPoint(2580500, 1221500) - outring.AddPoint(2580000, 1221500) - outring.AddPoint(2580000, 1220500) + outring.AddPoint_2D(2580000, 1220500) + outring.AddPoint_2D(2580500, 1220500) + outring.AddPoint_2D(2580500, 1221500) + outring.AddPoint_2D(2580000, 1221500) + outring.AddPoint_2D(2580000, 1220500) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -152,11 +155,11 @@ class TestPackageLayers(QgisTestCase): f["name"] = "province two" f["region"] = 1 outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2580500, 1220500) - outring.AddPoint(2581000, 1220500) - outring.AddPoint(2581000, 1221500) - outring.AddPoint(2580500, 1221500) - outring.AddPoint(2580500, 1220500) + outring.AddPoint_2D(2580500, 1220500) + outring.AddPoint_2D(2581000, 1220500) + outring.AddPoint_2D(2581000, 1221500) + outring.AddPoint_2D(2580500, 1221500) + outring.AddPoint_2D(2580500, 1220500) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -165,11 +168,11 @@ class TestPackageLayers(QgisTestCase): f["name"] = "province three" f["region"] = 2 outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2581000, 1220000) - outring.AddPoint(2582000, 1220000) - outring.AddPoint(2582000, 1220500) - outring.AddPoint(2581000, 1220500) - outring.AddPoint(2581000, 1220000) + outring.AddPoint_2D(2581000, 1220000) + outring.AddPoint_2D(2582000, 1220000) + outring.AddPoint_2D(2582000, 1220500) + outring.AddPoint_2D(2581000, 1220500) + outring.AddPoint_2D(2581000, 1220000) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -178,11 +181,11 @@ class TestPackageLayers(QgisTestCase): f["name"] = "province four" f["region"] = 2 outring = ogr.Geometry(ogr.wkbLinearRing) - outring.AddPoint(2581000, 1220500) - outring.AddPoint(2582000, 1220500) - outring.AddPoint(2582000, 1221000) - outring.AddPoint(2581000, 1221000) - outring.AddPoint(2581000, 1220500) + outring.AddPoint_2D(2581000, 1220500) + outring.AddPoint_2D(2582000, 1220500) + outring.AddPoint_2D(2582000, 1221000) + outring.AddPoint_2D(2581000, 1221000) + outring.AddPoint_2D(2581000, 1220500) polygon = ogr.Geometry(ogr.wkbPolygon) polygon.AddGeometry(outring) f.SetGeometry(polygon) @@ -195,28 +198,28 @@ class TestPackageLayers(QgisTestCase): f["name"] = "city one" f["province"] = 1 point = ogr.Geometry(ogr.wkbPoint) - point.AddPoint(2580200, 1221200) + point.AddPoint_2D(2580200, 1221200) f.SetGeometry(point) lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f["name"] = "city two" f["province"] = 1 point = ogr.Geometry(ogr.wkbPoint) - point.AddPoint(2580400, 1220700) + point.AddPoint_2D(2580400, 1220700) f.SetGeometry(point) lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f["name"] = "city three" f["province"] = 2 point = ogr.Geometry(ogr.wkbPoint) - point.AddPoint(2580900, 1220900) + point.AddPoint_2D(2580900, 1220900) f.SetGeometry(point) lyr.CreateFeature(f) f = ogr.Feature(lyr.GetLayerDefn()) f["name"] = "city four" f["province"] = 4 point = ogr.Geometry(ogr.wkbPoint) - point.AddPoint(2581200, 1220300) + point.AddPoint_2D(2581200, 1220300) f.SetGeometry(point) lyr.CreateFeature(f) @@ -466,6 +469,98 @@ class TestPackageLayers(QgisTestCase): province.selectByIds([]) city.selectByIds([]) + def test_crs(self): + """Test export with transformation""" + + alg = self.registry.createAlgorithmById("qgis:package") + self.assertIsNotNone(alg) + + def _test(parameters, expected_wkts): + + feedback = ConsoleFeedBack() + context = QgsProcessingContext() + context.setProject(QgsProject.instance()) + # Note: the following returns true also in case of errors ... + self.assertTrue(execute(alg, parameters, context, feedback)) + # ... so we check the log + self.assertEqual(feedback._errors, []) + + # Check export + for layer_name, wkts in expected_wkts.items(): + l = QgsVectorLayer( + self.temp_export_path + f"|layername={layer_name}", layer_name + ) + self.assertTrue(l.isValid()) + features = {f.id(): f for f in l.getFeatures()} + self.assertCountEqual( + list(features.keys()), + list(wkts.keys()), + layer_name + str(features.keys()), + ) + for id, wkt in wkts.items(): + self.assertEqual( + features[id].geometry().asWkt(3), wkt, f"{layer_name}: {id}" + ) + + region = QgsProject.instance().mapLayersByName("region")[0] + province = QgsProject.instance().mapLayersByName("province")[0] + city = QgsProject.instance().mapLayersByName("city")[0] + + parameters = { + "EXPORT_RELATED_LAYERS": False, + "LAYERS": [province, region, city], + "OUTPUT": self.temp_export_path, + "OVERWRITE": True, + "SELECTED_FEATURES_ONLY": False, + "EXTENT": None, + "CRS": "EPSG:4326", + } + + # Test with no extent given + _test( + parameters, + { + "region": { + 1: "Polygon ((7.175 47.135, 7.188 47.135, 7.188 47.144, 7.175 47.144, 7.175 47.135))", + 2: "Polygon ((7.188 47.131, 7.201 47.131, 7.201 47.14, 7.188 47.14, 7.188 47.131))", + }, + "province": { + 1: "Polygon ((7.175 47.135, 7.182 47.135, 7.182 47.144, 7.175 47.144, 7.175 47.135))", + 2: "Polygon ((7.182 47.135, 7.188 47.135, 7.188 47.144, 7.182 47.144, 7.182 47.135))", + 3: "Polygon ((7.188 47.131, 7.201 47.131, 7.201 47.135, 7.188 47.135, 7.188 47.131))", + 4: "Polygon ((7.188 47.135, 7.201 47.135, 7.201 47.14, 7.188 47.14, 7.188 47.135))", + }, + "city": { + 1: "Point (7.178 47.141)", + 2: "Point (7.18 47.137)", + 3: "Point (7.187 47.139)", + 4: "Point (7.191 47.133)", + }, + }, + ) + + # Test with extent + # Test more interesting extent + parameters["EXTENT"] = QgsReferencedRectangle( + QgsRectangle(2580700, 1220000, 2581500, 1222000), + QgsCoordinateReferenceSystem("EPSG:2056"), + ) + _test( + parameters, + { + "region": { + 1: "Polygon ((7.175 47.135, 7.188 47.135, 7.188 47.144, 7.175 47.144, 7.175 47.135))", + 2: "Polygon ((7.188 47.131, 7.201 47.131, 7.201 47.14, 7.188 47.14, 7.188 47.131))", + }, + "province": { + 2: "Polygon ((7.182 47.135, 7.188 47.135, 7.188 47.144, 7.182 47.144, 7.182 47.135))", + 3: "Polygon ((7.188 47.131, 7.201 47.131, 7.201 47.135, 7.188 47.135, 7.188 47.131))", + 4: "Polygon ((7.188 47.135, 7.201 47.135, 7.201 47.14, 7.188 47.14, 7.188 47.135))", + }, + "city": {3: "Point (7.187 47.139)", 4: "Point (7.191 47.133)"}, + }, + ) + if __name__ == "__main__": unittest.main()