From 22ab72e66b58c743fa62e09fe7fe5cd6bb23a3a4 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Thu, 12 Oct 2023 10:56:23 +0200 Subject: [PATCH 01/97] Implement pdf output format for WMS GetMap --- src/server/services/wms/CMakeLists.txt | 1 + src/server/services/wms/qgspdfwriter.cpp | 41 +++++++++++++++++++ src/server/services/wms/qgspdfwriter.h | 18 ++++++++ src/server/services/wms/qgswms.cpp | 5 +++ .../services/wms/qgswmsgetcapabilities.cpp | 1 + src/server/services/wms/qgswmsrenderer.cpp | 15 +++++++ src/server/services/wms/qgswmsrenderer.h | 8 ++++ 7 files changed, 89 insertions(+) create mode 100644 src/server/services/wms/qgspdfwriter.cpp create mode 100644 src/server/services/wms/qgspdfwriter.h diff --git a/src/server/services/wms/CMakeLists.txt b/src/server/services/wms/CMakeLists.txt index 68dfe7a6959..651e44162bd 100644 --- a/src/server/services/wms/CMakeLists.txt +++ b/src/server/services/wms/CMakeLists.txt @@ -6,6 +6,7 @@ set (WMS_SRCS qgswms.cpp qgswmsutils.cpp qgsdxfwriter.cpp + qgspdfwriter.cpp qgswmsdescribelayer.cpp qgswmsgetcapabilities.cpp qgswmsgetcontext.cpp diff --git a/src/server/services/wms/qgspdfwriter.cpp b/src/server/services/wms/qgspdfwriter.cpp new file mode 100644 index 00000000000..30407adc0b6 --- /dev/null +++ b/src/server/services/wms/qgspdfwriter.cpp @@ -0,0 +1,41 @@ +/*************************************************************************** + qgspdfwriter.cpp + ------------------------------------------------------------------- +Date : 09 October 2023 +Copyright : (C) 2023 +email : marco.hugentobler at sourcepole 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 "qgsapplication.h" +#include "qgsmaprenderertask.h" +#include "qgsmapsettings.h" +#include "qgsmodule.h" +#include "qgspdfwriter.h" +#include "qgswmsrenderer.h" + +namespace QgsWms +{ + void writeAsPdf( QgsServerInterface *serverIface, const QgsProject *project, + const QgsWmsRequest &request, + QgsServerResponse &response ) + { + QgsWmsRenderContext context( project, serverIface ); + context.setParameters( request.wmsParameters() ); + QTemporaryFile tmpFile; + tmpFile.open(); + QgsRenderer renderer( context ); + std::unique_ptr pdfTask = renderer.getPdf( tmpFile.fileName() ); + QgsApplication::taskManager()->addTask( pdfTask.get() ); + pdfTask->waitForFinished(); + response.write( tmpFile.readAll() ); + tmpFile.close(); + } +} // namespace QgsWms diff --git a/src/server/services/wms/qgspdfwriter.h b/src/server/services/wms/qgspdfwriter.h new file mode 100644 index 00000000000..eb74d6da004 --- /dev/null +++ b/src/server/services/wms/qgspdfwriter.h @@ -0,0 +1,18 @@ +#ifndef QGSPDFWRITER_H +#define QGSPDFWRITER_H + +#include "qgswmsrequest.h" + +namespace QgsWms +{ + + /** + * Output GetMap response in DXF format + */ + void writeAsPdf( QgsServerInterface *serverIface, const QgsProject *project, + const QgsWmsRequest &request, + QgsServerResponse &response ); + +} // namespace QgsWms + +#endif // QGSPDFWRITER_H diff --git a/src/server/services/wms/qgswms.cpp b/src/server/services/wms/qgswms.cpp index 2deff365c66..2b1b21a2ef9 100644 --- a/src/server/services/wms/qgswms.cpp +++ b/src/server/services/wms/qgswms.cpp @@ -21,6 +21,7 @@ #include "qgsmodule.h" #include "qgsdxfwriter.h" +#include "qgspdfwriter.h" #include "qgswmsserviceexception.h" #include "qgswmsgetcapabilities.h" #include "qgswmsgetmap.h" @@ -91,6 +92,10 @@ namespace QgsWms { writeAsDxf( mServerIface, project, request, response ); } + else if QSTR_COMPARE( wmsRequest.wmsParameters().formatAsString(), "application/pdf" ) + { + writeAsPdf( mServerIface, project, request, response ); + } else { writeGetMap( mServerIface, project, request, response ); diff --git a/src/server/services/wms/qgswmsgetcapabilities.cpp b/src/server/services/wms/qgswmsgetcapabilities.cpp index 7758327669c..3135300f372 100644 --- a/src/server/services/wms/qgswmsgetcapabilities.cpp +++ b/src/server/services/wms/qgswmsgetcapabilities.cpp @@ -456,6 +456,7 @@ namespace QgsWms appendFormat( elem, QStringLiteral( "image/png; mode=8bit" ) ); appendFormat( elem, QStringLiteral( "image/png; mode=1bit" ) ); appendFormat( elem, QStringLiteral( "application/dxf" ) ); + appendFormat( elem, QStringLiteral( "application/pdf" ) ); elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities' requestElem.appendChild( elem ); diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 2eba50f71fd..19e35b7a57a 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -31,6 +31,7 @@ #include "qgslayertreemodel.h" #include "qgslegendrenderer.h" #include "qgsmaplayer.h" +#include "qgsmaprenderertask.h" #include "qgsmapthemecollection.h" #include "qgsmaptopixel.h" #include "qgsproject.h" @@ -1133,6 +1134,20 @@ namespace QgsWms return dxf; } + std::unique_ptr QgsRenderer::getPdf( const QString &tmpFileName ) + { + QgsMapSettings ms; + ms.setExtent( mWmsParameters.bboxAsRectangle() ); + ms.setLayers( mContext.layersToRender() ); + ms.setDestinationCrs( QgsCoordinateReferenceSystem::fromOgcWmsCrs( mWmsParameters.crs() ) ); + ms.setOutputSize( QSize( mWmsParameters.widthAsInt(), mWmsParameters.heightAsInt() ) ); + ms.setDpiTarget( mWmsParameters.dpiAsDouble() ); + + QgsAbstractGeoPdfExporter::ExportDetails pdfExportDetails = QgsAbstractGeoPdfExporter::ExportDetails(); + std::unique_ptr pdf = std::make_unique( ms, tmpFileName, QStringLiteral( "PDF" ), false, QgsTask::Hidden, true, pdfExportDetails ); + return pdf; + } + static void infoPointToMapCoordinates( int i, int j, QgsPointXY *infoPoint, const QgsMapSettings &mapSettings ) { //check if i, j are in the pixel range of the image diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index 28fcb345c81..69839ea177b 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -36,6 +36,7 @@ class QgsPrintLayout; class QgsFeature; class QgsLayout; class QgsMapLayer; +class QgsMapRendererTask; class QgsMapSettings; class QgsPointXY; class QgsRasterLayer; @@ -130,6 +131,13 @@ namespace QgsWms */ std::unique_ptr getDxf(); + /** + * Returns a configured pdf export task + * \tmpFileName the name of the temporary file to store the pdf + * \returns pdf export object + */ + std::unique_ptr getPdf( const QString &tmpFileName ); + /** * Returns printed page as binary * \returns printed page as binary or 0 in case of error From 352ca78b348ab68fc87aa68d2a4bef861dbec1a5 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 13 Oct 2023 14:21:50 +0200 Subject: [PATCH 02/97] Consider format options WRITE_GEO_PDF,EXPORT_METADATA,USE_ISO_32000_EXTENSION_FORMAT_GEOREFERENCING,USE_OGC_BEST_PRACTICE_FORMAT_GEOREFERENCING --- src/server/services/wms/qgswmsrenderer.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 19e35b7a57a..9091e9b965d 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -1143,8 +1143,21 @@ namespace QgsWms ms.setOutputSize( QSize( mWmsParameters.widthAsInt(), mWmsParameters.heightAsInt() ) ); ms.setDpiTarget( mWmsParameters.dpiAsDouble() ); - QgsAbstractGeoPdfExporter::ExportDetails pdfExportDetails = QgsAbstractGeoPdfExporter::ExportDetails(); - std::unique_ptr pdf = std::make_unique( ms, tmpFileName, QStringLiteral( "PDF" ), false, QgsTask::Hidden, true, pdfExportDetails ); + QgsAbstractGeoPdfExporter::ExportDetails pdfExportDetails; + if ( mWmsParameters.pdfExportMetadata() ) + { + pdfExportDetails.author = QgsProject::instance()->metadata().author(); + pdfExportDetails.producer = QStringLiteral( "QGIS %1" ).arg( Qgis::version() ); + pdfExportDetails.creator = QStringLiteral( "QGIS %1" ).arg( Qgis::version() ); + pdfExportDetails.creationDateTime = QDateTime::currentDateTime(); + pdfExportDetails.subject = QgsProject::instance()->metadata().abstract(); + pdfExportDetails.title = QgsProject::instance()->metadata().title(); + pdfExportDetails.keywords = QgsProject::instance()->metadata().keywords(); + } + pdfExportDetails.useIso32000ExtensionFormatGeoreferencing = mWmsParameters.pdfUseIso32000ExtensionFormatGeoreferencing(); + pdfExportDetails.useOgcBestPracticeFormatGeoreferencing = mWmsParameters.pdfUseOgcBestPracticeFormatGeoreferencing(); + bool geoPdf = mWmsParameters.pdfAppendGeoreference(); + std::unique_ptr pdf = std::make_unique( ms, tmpFileName, QStringLiteral( "PDF" ), false, QgsTask::Hidden, geoPdf, pdfExportDetails ); return pdf; } From 9a55cba86325eed3434ba5d085d97da338eac019 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 13 Oct 2023 14:32:14 +0200 Subject: [PATCH 03/97] Add copyright header --- src/server/services/wms/qgspdfwriter.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/server/services/wms/qgspdfwriter.h b/src/server/services/wms/qgspdfwriter.h index eb74d6da004..2f4f99aa531 100644 --- a/src/server/services/wms/qgspdfwriter.h +++ b/src/server/services/wms/qgspdfwriter.h @@ -1,3 +1,19 @@ +/*************************************************************************** + qgspdfwriter.h + ------------------------------------------------------------------- +Date : 09 October 2023 +Copyright : (C) 2023 +email : marco.hugentobler at sourcepole 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 QGSPDFWRITER_H #define QGSPDFWRITER_H From 68e3123b384b4e43761bd33e891491664620d5a5 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 26 Oct 2023 17:03:38 +0200 Subject: [PATCH 04/97] Fix WMS multiple layers Fix #55042 --- src/providers/wms/qgswmsprovider.cpp | 39 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp index 7b8cbfabe2e..1f34edd026c 100644 --- a/src/providers/wms/qgswmsprovider.cpp +++ b/src/providers/wms/qgswmsprovider.cpp @@ -5220,7 +5220,20 @@ QVariantMap QgsWmsProviderMetadata::decodeUri( const QString &uri ) const } else { - decoded[ item.first ] = item.second; + if ( decoded.contains( item.first ) ) + { + if ( decoded[ item.first ].type() == QVariant::String ) + { + decoded[ item.first ] = QStringList() << decoded[ item.first ].toString(); + } + QStringList items = decoded[ item.first ].toStringList(); + items.append( item.second ); + decoded[ item.first ] = items; + } + else + { + decoded[ item.first ] = item.second; + } } } return decoded; @@ -5230,6 +5243,7 @@ QString QgsWmsProviderMetadata::encodeUri( const QVariantMap &parts ) const { QUrlQuery query; QList > items; + QList > listItems; for ( auto it = parts.constBegin(); it != parts.constEnd(); ++it ) { if ( it.key() == QLatin1String( "path" ) ) @@ -5238,11 +5252,30 @@ QString QgsWmsProviderMetadata::encodeUri( const QVariantMap &parts ) const } else { - items.push_back( { it.key(), it.value().toString() } ); + if ( it.value().type() == QVariant::StringList ) + { + listItems.push_back( { it.key(), it.value().toStringList() } ); + } + else + { + items.push_back( { it.key(), it.value().toString() } ); + } } } query.setQueryItems( items ); - return query.toString(); + QString uri { query.toString() }; + // Add lists + for ( auto it = listItems.constBegin(); it != listItems.constEnd(); ++it ) + { + for ( auto itItem = it->second.constBegin(); itItem != it->second.constEnd(); ++itItem ) + { + uri.append( '&' ); + uri.append( it->first ); + uri.append( '=' ); + uri.append( *itItem ); + } + } + return uri; } QList QgsWmsProviderMetadata::querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags, QgsFeedback * ) const From 2c9163338f3001a00eea54a32de2ab75d30a0a3c Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 27 Oct 2023 11:08:26 +0200 Subject: [PATCH 05/97] Add content-type header --- src/server/services/wms/qgspdfwriter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/services/wms/qgspdfwriter.cpp b/src/server/services/wms/qgspdfwriter.cpp index 30407adc0b6..96a4a050ed3 100644 --- a/src/server/services/wms/qgspdfwriter.cpp +++ b/src/server/services/wms/qgspdfwriter.cpp @@ -35,6 +35,7 @@ namespace QgsWms std::unique_ptr pdfTask = renderer.getPdf( tmpFile.fileName() ); QgsApplication::taskManager()->addTask( pdfTask.get() ); pdfTask->waitForFinished(); + response.setHeader( "Content-Type", "application/pdf" ); response.write( tmpFile.readAll() ); tmpFile.close(); } From de52554c25b65b3e1ef6e504b594ad62e6e40719 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Wed, 1 Nov 2023 07:51:49 +0100 Subject: [PATCH 06/97] Fix comment --- src/server/services/wms/qgspdfwriter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/services/wms/qgspdfwriter.h b/src/server/services/wms/qgspdfwriter.h index 2f4f99aa531..ff9716d465b 100644 --- a/src/server/services/wms/qgspdfwriter.h +++ b/src/server/services/wms/qgspdfwriter.h @@ -23,7 +23,7 @@ namespace QgsWms { /** - * Output GetMap response in DXF format + * Output GetMap response in PDF format */ void writeAsPdf( QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, From ac80a3782ee4dd4427e4d0751d242bd7a6c8e521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Thu, 2 Nov 2023 16:31:45 +0100 Subject: [PATCH 07/97] Avoid None as value for referenced_columns (use empty list instead) in qgsfunction.py documentation --- python/core/additions/qgsfunction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/core/additions/qgsfunction.py b/python/core/additions/qgsfunction.py index 9175c82ec48..9fd935c18f4 100644 --- a/python/core/additions/qgsfunction.py +++ b/python/core/additions/qgsfunction.py @@ -118,7 +118,7 @@ def register_function( :param args: DEPRECATED since QGIS 3.32. Use ``params_as_list`` if you want to pass parameters as a list. :param group: the expression group in which the function should be added :param usesgeometry: Defines if this expression requires the geometry. By default False. - :param referenced_columns: An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to None for slightly faster evaluation. + :param referenced_columns: An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to an empty list for slightly faster evaluation. :param handlesnull: Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default. :param params_as_list: If True, the function will receive the expression parameters as a list. If False, the function will receive the parameters as individual arguments. False by default. @@ -185,7 +185,7 @@ def qgsfunction(args="auto", group="custom", **kwargs): * *usesgeometry* (``bool``) -- Defines if this expression requires the geometry. By default False. * *referenced_columns* (``list``) -- - An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to None for slightly faster evaluation. + An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to an empty list for slightly faster evaluation. * *handlesnull* (``bool``) -- Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default. * *params_as_list* (``bool``) \since QGIS 3.32 -- From 66c2c467b222d535e431840e3c0906451c0074d6 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Tue, 7 Nov 2023 14:28:31 +0100 Subject: [PATCH 08/97] Trigger CI run --- src/server/services/wms/qgspdfwriter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/services/wms/qgspdfwriter.cpp b/src/server/services/wms/qgspdfwriter.cpp index 96a4a050ed3..c0ad5704493 100644 --- a/src/server/services/wms/qgspdfwriter.cpp +++ b/src/server/services/wms/qgspdfwriter.cpp @@ -21,6 +21,7 @@ email : marco.hugentobler at sourcepole dot com #include "qgspdfwriter.h" #include "qgswmsrenderer.h" + namespace QgsWms { void writeAsPdf( QgsServerInterface *serverIface, const QgsProject *project, From cb8c062b25fc6e3d86d261a2f055596fbb58ba7b Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Fri, 3 Nov 2023 11:17:39 +0700 Subject: [PATCH 09/97] [editor widgets] Fix QML editor widget not responsive to attribute changes --- .../editorwidgets/qgsqmlwidgetwrapper.sip.in | 1 + .../editorwidgets/qgshtmlwidgetwrapper.cpp | 2 +- src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp | 42 +++++++++++++++++-- src/gui/editorwidgets/qgsqmlwidgetwrapper.h | 5 ++- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/python/gui/auto_generated/editorwidgets/qgsqmlwidgetwrapper.sip.in b/python/gui/auto_generated/editorwidgets/qgsqmlwidgetwrapper.sip.in index cc6202e3581..22bb1421bf7 100644 --- a/python/gui/auto_generated/editorwidgets/qgsqmlwidgetwrapper.sip.in +++ b/python/gui/auto_generated/editorwidgets/qgsqmlwidgetwrapper.sip.in @@ -7,6 +7,7 @@ ************************************************************************/ + class QgsQmlWidgetWrapper : QgsWidgetWrapper { %Docstring(signature="appended") diff --git a/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp b/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp index edf639c1c15..fb249f1e438 100644 --- a/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp @@ -19,6 +19,7 @@ #include "qgswebframe.h" #include "qgsvaluerelationfieldformatter.h" #include "qgsattributeform.h" + #include QgsHtmlWidgetWrapper::QgsHtmlWidgetWrapper( QgsVectorLayer *layer, QWidget *editor, QWidget *parent ) @@ -34,7 +35,6 @@ bool QgsHtmlWidgetWrapper::valid() const QWidget *QgsHtmlWidgetWrapper::createWidget( QWidget *parent ) { - QgsAttributeForm *form = qobject_cast( parent ); if ( form ) diff --git a/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp b/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp index e4ecb2256cb..eb40e325e84 100644 --- a/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp @@ -13,12 +13,13 @@ * (at your option) any later version. * * * ***************************************************************************/ + #include "qgsqmlwidgetwrapper.h" +#include "qgsattributeform.h" #include "qgsmessagelog.h" #include "qgsexpressioncontextutils.h" +#include "qgsvaluerelationfieldformatter.h" -#include -#include #include #include #include @@ -36,6 +37,26 @@ bool QgsQmlWidgetWrapper::valid() const QWidget *QgsQmlWidgetWrapper::createWidget( QWidget *parent ) { + QgsAttributeForm *form = qobject_cast( parent ); + + if ( form ) + { + mFormFeature = form->feature(); + connect( form, &QgsAttributeForm::widgetValueChanged, this, [ = ]( const QString & attribute, const QVariant & newValue, bool attributeChanged ) + { + if ( attributeChanged ) + { + const thread_local QRegularExpression expRe { QStringLiteral( R"re(expression.evaluate\s*\(\s*"(.*)"\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption }; + const QRegularExpressionMatch match { expRe.match( mQmlCode ) }; + if ( match.hasMatch() && QgsValueRelationFieldFormatter::expressionRequiresFormScope( match.captured( 1 ) ) ) + { + mFormFeature.setAttribute( attribute, newValue ); + setQmlContext(); + } + } + } ); + } + return new QQuickWidget( parent ); } @@ -71,6 +92,13 @@ void QgsQmlWidgetWrapper::reinitWidget( ) void QgsQmlWidgetWrapper::setQmlCode( const QString &qmlCode ) { + if ( mQmlCode == qmlCode ) + { + return; + } + + mQmlCode = qmlCode; + if ( !mQmlFile.open() ) { QgsMessageLog::logMessage( tr( "Failed to open temporary QML file" ) ); @@ -78,7 +106,7 @@ void QgsQmlWidgetWrapper::setQmlCode( const QString &qmlCode ) } mQmlFile.resize( 0 ); - mQmlFile.write( qmlCode.toUtf8() ); + mQmlFile.write( mQmlCode.toUtf8() ); mQmlFile.close(); } @@ -90,7 +118,12 @@ void QgsQmlWidgetWrapper::setQmlContext( ) const QgsAttributeEditorContext attributecontext = context(); QgsExpressionContext expressionContext = layer()->createExpressionContext(); - expressionContext << QgsExpressionContextUtils::formScope( mFeature, attributecontext.attributeFormModeString() ); + expressionContext << QgsExpressionContextUtils::formScope( mFormFeature, attributecontext.attributeFormModeString() ); + if ( attributecontext.parentFormFeature().isValid() ) + { + expressionContext << QgsExpressionContextUtils::parentFormScope( attributecontext.parentFormFeature() ); + } + expressionContext.setFeature( mFeature ); QmlExpression *qmlExpression = new QmlExpression(); @@ -105,6 +138,7 @@ void QgsQmlWidgetWrapper::setFeature( const QgsFeature &feature ) return; mFeature = feature; + mFormFeature = feature; setQmlContext(); } diff --git a/src/gui/editorwidgets/qgsqmlwidgetwrapper.h b/src/gui/editorwidgets/qgsqmlwidgetwrapper.h index 3b941904e0d..b785ab86397 100644 --- a/src/gui/editorwidgets/qgsqmlwidgetwrapper.h +++ b/src/gui/editorwidgets/qgsqmlwidgetwrapper.h @@ -19,7 +19,8 @@ #include "qgswidgetwrapper.h" #include "qgis_sip.h" #include "qgis_gui.h" -#include + +#include /** * \ingroup gui @@ -62,8 +63,10 @@ class GUI_EXPORT QgsQmlWidgetWrapper : public QgsWidgetWrapper private: QTemporaryFile mQmlFile; + QString mQmlCode; QQuickWidget *mWidget = nullptr; QgsFeature mFeature; + QgsFeature mFormFeature; }; From 5b1d8ec3be45612b478e9c91ad0b0a18e95ac24e Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Wed, 8 Nov 2023 14:27:44 +0700 Subject: [PATCH 10/97] Apply suggestion --- .../editorwidgets/qgshtmlwidgetwrapper.cpp | 16 ++++++++++--- src/gui/editorwidgets/qgshtmlwidgetwrapper.h | 1 + src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp | 15 +++++++++--- src/gui/editorwidgets/qgsqmlwidgetwrapper.h | 1 + .../editorwidgets/qgstextwidgetwrapper.cpp | 24 ++++++++++--------- src/gui/editorwidgets/qgstextwidgetwrapper.h | 1 + 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp b/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp index fb249f1e438..ab53d10c446 100644 --- a/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp @@ -44,9 +44,7 @@ QWidget *QgsHtmlWidgetWrapper::createWidget( QWidget *parent ) { if ( attributeChanged ) { - const thread_local QRegularExpression expRe { QStringLiteral( R"re(expression.evaluate\s*\(\s*"(.*)"\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption }; - const QRegularExpressionMatch match { expRe.match( mHtmlCode ) }; - if ( match.hasMatch() && QgsValueRelationFieldFormatter::expressionRequiresFormScope( match.captured( 1 ) ) ) + if ( mRequiresFormScope ) { mFormFeature.setAttribute( attribute, newValue ); setHtmlContext(); @@ -121,6 +119,18 @@ void QgsHtmlWidgetWrapper::checkGeometryNeeds() void QgsHtmlWidgetWrapper::setHtmlCode( const QString &htmlCode ) { mHtmlCode = htmlCode; + + bool ok = false; + const thread_local QRegularExpression expRe( QStringLiteral( R"re(expression.evaluate\s*\(\s*"(.*)"\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption ); + QRegularExpressionMatchIterator matchIt = expRe.globalMatch( mHtmlCode ); + while ( !ok && matchIt.hasNext() ) + { + const QRegularExpressionMatch match = matchIt.next(); + const QgsExpression exp = match.captured( 1 ); + ok = QgsValueRelationFieldFormatter::expressionRequiresFormScope( exp ); + } + mRequiresFormScope = ok; + checkGeometryNeeds(); } diff --git a/src/gui/editorwidgets/qgshtmlwidgetwrapper.h b/src/gui/editorwidgets/qgshtmlwidgetwrapper.h index c211c6284a5..201c2dc467c 100644 --- a/src/gui/editorwidgets/qgshtmlwidgetwrapper.h +++ b/src/gui/editorwidgets/qgshtmlwidgetwrapper.h @@ -79,6 +79,7 @@ class GUI_EXPORT QgsHtmlWidgetWrapper : public QgsWidgetWrapper QgsFeature mFeature; bool mNeedsGeometry = false; QgsFeature mFormFeature; + bool mRequiresFormScope = false; friend class TestQgsHtmlWidgetWrapper; }; diff --git a/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp b/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp index eb40e325e84..d50104f4e68 100644 --- a/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsqmlwidgetwrapper.cpp @@ -46,9 +46,7 @@ QWidget *QgsQmlWidgetWrapper::createWidget( QWidget *parent ) { if ( attributeChanged ) { - const thread_local QRegularExpression expRe { QStringLiteral( R"re(expression.evaluate\s*\(\s*"(.*)"\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption }; - const QRegularExpressionMatch match { expRe.match( mQmlCode ) }; - if ( match.hasMatch() && QgsValueRelationFieldFormatter::expressionRequiresFormScope( match.captured( 1 ) ) ) + if ( mRequiresFormScope ) { mFormFeature.setAttribute( attribute, newValue ); setQmlContext(); @@ -99,6 +97,17 @@ void QgsQmlWidgetWrapper::setQmlCode( const QString &qmlCode ) mQmlCode = qmlCode; + bool ok = false; + const thread_local QRegularExpression expRe( QStringLiteral( R"re(expression.evaluate\s*\(\s*"(.*)"\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption ); + QRegularExpressionMatchIterator matchIt = expRe.globalMatch( mQmlCode ); + while ( !ok && matchIt.hasNext() ) + { + const QRegularExpressionMatch match = matchIt.next(); + const QgsExpression exp = match.captured( 1 ); + ok = QgsValueRelationFieldFormatter::expressionRequiresFormScope( exp ); + } + mRequiresFormScope = ok; + if ( !mQmlFile.open() ) { QgsMessageLog::logMessage( tr( "Failed to open temporary QML file" ) ); diff --git a/src/gui/editorwidgets/qgsqmlwidgetwrapper.h b/src/gui/editorwidgets/qgsqmlwidgetwrapper.h index b785ab86397..fd5c4a524ba 100644 --- a/src/gui/editorwidgets/qgsqmlwidgetwrapper.h +++ b/src/gui/editorwidgets/qgsqmlwidgetwrapper.h @@ -67,6 +67,7 @@ class GUI_EXPORT QgsQmlWidgetWrapper : public QgsWidgetWrapper QQuickWidget *mWidget = nullptr; QgsFeature mFeature; QgsFeature mFormFeature; + bool mRequiresFormScope = false; }; diff --git a/src/gui/editorwidgets/qgstextwidgetwrapper.cpp b/src/gui/editorwidgets/qgstextwidgetwrapper.cpp index 77b253791e8..b8d872365e2 100644 --- a/src/gui/editorwidgets/qgstextwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgstextwidgetwrapper.cpp @@ -42,17 +42,7 @@ QWidget *QgsTextWidgetWrapper::createWidget( QWidget *parent ) { if ( attributeChanged ) { - bool ok { false }; - const thread_local QRegularExpression sRegEx{ QStringLiteral( "\\[%(.*?)%\\]" ), QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption }; - QRegularExpressionMatchIterator matchIt { sRegEx.globalMatch( mText ) }; - while ( !ok && matchIt.hasNext() ) - { - const QRegularExpressionMatch match { matchIt.next() }; - const QgsExpression exp { match.captured( 1 ) }; - ok = QgsValueRelationFieldFormatter::expressionRequiresFormScope( exp ); - } - - if ( ok ) + if ( mRequiresFormScope ) { mFormFeature.setAttribute( attribute, newValue ); updateTextContext(); @@ -99,6 +89,18 @@ void QgsTextWidgetWrapper::reinitWidget( ) void QgsTextWidgetWrapper::setText( const QString &text ) { mText = text; + + bool ok = false; + const thread_local QRegularExpression sRegEx( QStringLiteral( "\\[%(.*?)%\\]" ), QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption ); + QRegularExpressionMatchIterator matchIt = sRegEx.globalMatch( mText ); + while ( !ok && matchIt.hasNext() ) + { + const QRegularExpressionMatch match = matchIt.next(); + const QgsExpression exp = match.captured( 1 ); + ok = QgsValueRelationFieldFormatter::expressionRequiresFormScope( exp ); + } + mRequiresFormScope = ok; + reinitWidget(); } diff --git a/src/gui/editorwidgets/qgstextwidgetwrapper.h b/src/gui/editorwidgets/qgstextwidgetwrapper.h index 0c5ac9acdd7..e7639e23520 100644 --- a/src/gui/editorwidgets/qgstextwidgetwrapper.h +++ b/src/gui/editorwidgets/qgstextwidgetwrapper.h @@ -71,6 +71,7 @@ class GUI_EXPORT QgsTextWidgetWrapper : public QgsWidgetWrapper QLabel *mWidget = nullptr; QgsFeature mFeature; QgsFeature mFormFeature; + bool mRequiresFormScope = false; QgsExpressionContext mTextContext; bool mNeedsGeometry = false; From 225860bc5be3b724c8eb087e48b0e4c70dbc7dcf Mon Sep 17 00:00:00 2001 From: mhugent Date: Fri, 10 Nov 2023 10:08:52 +0100 Subject: [PATCH 12/97] Update src/server/services/wms/qgswmsrenderer.cpp Co-authored-by: Paul Blottiere --- src/server/services/wms/qgswmsrenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 9091e9b965d..9be58de83a0 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -1156,7 +1156,7 @@ namespace QgsWms } pdfExportDetails.useIso32000ExtensionFormatGeoreferencing = mWmsParameters.pdfUseIso32000ExtensionFormatGeoreferencing(); pdfExportDetails.useOgcBestPracticeFormatGeoreferencing = mWmsParameters.pdfUseOgcBestPracticeFormatGeoreferencing(); - bool geoPdf = mWmsParameters.pdfAppendGeoreference(); + const bool geoPdf = mWmsParameters.pdfAppendGeoreference(); std::unique_ptr pdf = std::make_unique( ms, tmpFileName, QStringLiteral( "PDF" ), false, QgsTask::Hidden, geoPdf, pdfExportDetails ); return pdf; } From 35be2e75d397bc6f6671d97976af6ef1fb034a51 Mon Sep 17 00:00:00 2001 From: mhugent Date: Fri, 10 Nov 2023 10:09:05 +0100 Subject: [PATCH 13/97] Update src/server/services/wms/qgswmsrenderer.h Co-authored-by: Paul Blottiere --- src/server/services/wms/qgswmsrenderer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index 69839ea177b..a8d7771c1f9 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -135,6 +135,7 @@ namespace QgsWms * Returns a configured pdf export task * \tmpFileName the name of the temporary file to store the pdf * \returns pdf export object + * \since QGIS 3.36 */ std::unique_ptr getPdf( const QString &tmpFileName ); From 046db9055b60e5fb10976eb6467bf7257c176ea4 Mon Sep 17 00:00:00 2001 From: mhugent Date: Fri, 10 Nov 2023 10:09:17 +0100 Subject: [PATCH 14/97] Update src/server/services/wms/qgspdfwriter.h Co-authored-by: Paul Blottiere --- src/server/services/wms/qgspdfwriter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/services/wms/qgspdfwriter.h b/src/server/services/wms/qgspdfwriter.h index ff9716d465b..eaafb438565 100644 --- a/src/server/services/wms/qgspdfwriter.h +++ b/src/server/services/wms/qgspdfwriter.h @@ -24,6 +24,7 @@ namespace QgsWms /** * Output GetMap response in PDF format + * \since QGIS 3.36 */ void writeAsPdf( QgsServerInterface *serverIface, const QgsProject *project, const QgsWmsRequest &request, From e9b4ff3b3ac8c34e0b245e745d4f255d2b8e0d73 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 10 Nov 2023 16:11:57 +0100 Subject: [PATCH 15/97] Add pdf output format to test capabilities --- tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt | 1 + tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt | 1 + tests/testdata/qgis_server/wms_getcapabilities_empty_layer.txt | 1 + .../qgis_server/wms_getcapabilities_empty_spatial_layer.txt | 1 + tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt | 1 + tests/testdata/qgis_server/wms_getcapabilities_without_title.txt | 1 + 6 files changed, 6 insertions(+) diff --git a/tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt b/tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt index bd32edf5a60..9f9ed3fe723 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt @@ -41,6 +41,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt b/tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt index 6285da83e77..262d5ae8bf6 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/wms_getcapabilities_empty_layer.txt b/tests/testdata/qgis_server/wms_getcapabilities_empty_layer.txt index e6d3a5e4897..4702bde0390 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_empty_layer.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_empty_layer.txt @@ -32,6 +32,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/wms_getcapabilities_empty_spatial_layer.txt b/tests/testdata/qgis_server/wms_getcapabilities_empty_spatial_layer.txt index 6f3ae70809c..afd2028cd67 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_empty_spatial_layer.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_empty_spatial_layer.txt @@ -32,6 +32,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt b/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt index f61941bc86a..af4ce56e3b0 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/wms_getcapabilities_without_title.txt b/tests/testdata/qgis_server/wms_getcapabilities_without_title.txt index 5223b39da82..babdf2bf6da 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_without_title.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_without_title.txt @@ -34,6 +34,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf From 1883b9bdf36899ab89a5690e26bde4331f1a6caf Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Mon, 13 Nov 2023 10:29:25 +0100 Subject: [PATCH 16/97] Add pdf output format to test capabilities --- tests/testdata/qgis_server/getcapabilities-map.txt | 1 + tests/testdata/qgis_server/getcapabilities.txt | 1 + tests/testdata/qgis_server/getcapabilities_inspire.txt | 1 + tests/testdata/qgis_server/getcapabilities_without_map_param.txt | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/testdata/qgis_server/getcapabilities-map.txt b/tests/testdata/qgis_server/getcapabilities-map.txt index 6b5c6dd6788..cb2e47a553e 100644 --- a/tests/testdata/qgis_server/getcapabilities-map.txt +++ b/tests/testdata/qgis_server/getcapabilities-map.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/getcapabilities.txt b/tests/testdata/qgis_server/getcapabilities.txt index 0345e2abec2..c540c08a916 100644 --- a/tests/testdata/qgis_server/getcapabilities.txt +++ b/tests/testdata/qgis_server/getcapabilities.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/getcapabilities_inspire.txt b/tests/testdata/qgis_server/getcapabilities_inspire.txt index bd848a6bd32..fbe980fe267 100644 --- a/tests/testdata/qgis_server/getcapabilities_inspire.txt +++ b/tests/testdata/qgis_server/getcapabilities_inspire.txt @@ -41,6 +41,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/getcapabilities_without_map_param.txt b/tests/testdata/qgis_server/getcapabilities_without_map_param.txt index d41f77ced54..17df5bc3aa1 100644 --- a/tests/testdata/qgis_server/getcapabilities_without_map_param.txt +++ b/tests/testdata/qgis_server/getcapabilities_without_map_param.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf From 147ef8bfe586512e80388a4cea0ee409394978e9 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Mon, 13 Nov 2023 11:47:46 +0100 Subject: [PATCH 17/97] Add unit test for GetMap pdf --- tests/src/python/test_qgsserver_wms_getmap.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/python/test_qgsserver_wms_getmap.py b/tests/src/python/test_qgsserver_wms_getmap.py index a4a57460af9..6c050cb2d6c 100644 --- a/tests/src/python/test_qgsserver_wms_getmap.py +++ b/tests/src/python/test_qgsserver_wms_getmap.py @@ -2208,6 +2208,25 @@ class TestQgsServerWMSGetMap(QgsServerTestBase): server.handleRequest(request, response, project) self._img_diff_error(response.body(), response.headers(), "WMS_GetMap_LabelingOpacities") + def test_get_map_pdf(self): + r, h = self._result(self._execute_request(qs)) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectPath), + "SERVICE": "WMS", + "VERSION": "1.1.1", + "REQUEST": "GetMap", + "LAYERS": "Country,dem", + "STYLES": "", + "FORMAT": "application/pdf", + "BBOX": "-16817707,-4710778,5696513,14587125", + "HEIGHT": "500", + "WIDTH": "500", + "CRS": "EPSG:3857" + }.items())]) + + self._assert_status_code(self, 200, qs) + if __name__ == '__main__': unittest.main() From e25aa8261fb4cc0af81d46dd38b8db61acc1895d Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Mon, 13 Nov 2023 11:57:10 +0100 Subject: [PATCH 18/97] Fix unit test --- tests/src/python/test_qgsserver_wms_getmap.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/python/test_qgsserver_wms_getmap.py b/tests/src/python/test_qgsserver_wms_getmap.py index 6c050cb2d6c..f8fd27524f6 100644 --- a/tests/src/python/test_qgsserver_wms_getmap.py +++ b/tests/src/python/test_qgsserver_wms_getmap.py @@ -2209,8 +2209,6 @@ class TestQgsServerWMSGetMap(QgsServerTestBase): self._img_diff_error(response.body(), response.headers(), "WMS_GetMap_LabelingOpacities") def test_get_map_pdf(self): - r, h = self._result(self._execute_request(qs)) - qs = "?" + "&".join(["%s=%s" % i for i in list({ "MAP": urllib.parse.quote(self.projectPath), "SERVICE": "WMS", From 570c907b1c73384ee37254c72576644ffa0ffdfe Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Mon, 13 Nov 2023 14:04:44 +0100 Subject: [PATCH 19/97] Take 2 --- tests/src/python/test_qgsserver_wms_getmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/python/test_qgsserver_wms_getmap.py b/tests/src/python/test_qgsserver_wms_getmap.py index f8fd27524f6..34dc328b002 100644 --- a/tests/src/python/test_qgsserver_wms_getmap.py +++ b/tests/src/python/test_qgsserver_wms_getmap.py @@ -2223,7 +2223,7 @@ class TestQgsServerWMSGetMap(QgsServerTestBase): "CRS": "EPSG:3857" }.items())]) - self._assert_status_code(self, 200, qs) + self._assert_status_code(200, qs) if __name__ == '__main__': From 3f7e5011b1c9b82fb0fc6a247707439efe701e39 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Wed, 15 Nov 2023 10:54:08 +0100 Subject: [PATCH 20/97] fix(Qgs3DSceneExporter): 3D scene export failed due to bad feature sort --- src/3d/qgs3dsceneexporter.cpp | 2 -- tests/src/3d/testqgs3drendering.cpp | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/3d/qgs3dsceneexporter.cpp b/src/3d/qgs3dsceneexporter.cpp index 109fba7fc52..bb0de2d521d 100644 --- a/src/3d/qgs3dsceneexporter.cpp +++ b/src/3d/qgs3dsceneexporter.cpp @@ -544,8 +544,6 @@ Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeo if ( tessGeom ) { QVector featureIds = tessGeom->featureIds(); - // sort feature by their id in order to always export them in the same way: - std::sort( featureIds.begin(), featureIds.end() ); QVector triangleIndex = tessGeom->triangleIndexStartingIndices(); for ( int idx = 0; idx < featureIds.size(); idx++ ) diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 523fe658282..409df27f6f7 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -1516,15 +1516,16 @@ void TestQgs3DRendering::do3DSceneExport( int zoomLevelsCount, int expectedObjec exporter.setScale( 1.0 ); QVERIFY( exporter.parseVectorLayerEntity( scene->layerEntity( layerPoly ), layerPoly ) ); + exporter.save( QString( "test3DSceneExporter-%1" ).arg( zoomLevelsCount ), "/tmp/" ); + int sum = 0; for ( auto o : exporter.mObjects ) { QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() ); sum += o->indexes().size(); } - QCOMPARE( sum, maxFaceCount ); - exporter.save( QString( "test3DSceneExporter-%1" ).arg( zoomLevelsCount ), "/tmp/" ); + QCOMPARE( sum, maxFaceCount ); QCOMPARE( exporter.mExportedFeatureIds.size(), 3 ); QCOMPARE( exporter.mObjects.size(), expectedObjectCount ); } @@ -1577,9 +1578,9 @@ void TestQgs3DRendering::test3DSceneExporter() // =========== check with 4 tiles ==> 3 exported objects do3DSceneExport( 2, 1, 165, scene, symbol3d, layerPoly, &engine ); // =========== check with 9 tiles ==> 3 exported objects - do3DSceneExport( 3, 3, 216, scene, symbol3d, layerPoly, &engine ); + do3DSceneExport( 3, 3, 165, scene, symbol3d, layerPoly, &engine ); // =========== check with 16 tiles ==> 3 exported objects - do3DSceneExport( 4, 3, 132, scene, symbol3d, layerPoly, &engine ); + do3DSceneExport( 4, 3, 165, scene, symbol3d, layerPoly, &engine ); // =========== check with 25 tiles ==> 3 exported objects do3DSceneExport( 5, 3, 165, scene, symbol3d, layerPoly, &engine ); From ec599ab39282bf98804c91608a71b28a61ff1f3d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 15 Nov 2023 11:36:53 +1000 Subject: [PATCH 21/97] Don't fetch tile in main thread This can block the user interface -- instead defer tile loading till we're running on the background thread. --- src/3d/qgstiledscenechunkloader_p.cpp | 24 ++++++++++++------------ src/3d/qgstiledscenechunkloader_p.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/3d/qgstiledscenechunkloader_p.cpp b/src/3d/qgstiledscenechunkloader_p.cpp index 40848967b25..23fe0b3eb99 100644 --- a/src/3d/qgstiledscenechunkloader_p.cpp +++ b/src/3d/qgstiledscenechunkloader_p.cpp @@ -47,23 +47,26 @@ static bool hasLargeBounds( const QgsTiledSceneTile &t ) /// -QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneChunkLoaderFactory &factory, const QgsTiledSceneTile &t, double zValueScale, double zValueOffset ) +QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneIndex &index, const QgsTiledSceneChunkLoaderFactory &factory, double zValueScale, double zValueOffset ) : QgsChunkLoader( node ) , mFactory( factory ) - , mTile( t ) + , mIndex( index ) { mFutureWatcher = new QFutureWatcher( this ); connect( mFutureWatcher, &QFutureWatcher::finished, this, &QgsChunkQueueJob::finished ); - const QFuture future = QtConcurrent::run( [this, zValueScale, zValueOffset] + const QgsChunkNodeId tileId = node->tileId(); + const QFuture future = QtConcurrent::run( [this, tileId, zValueScale, zValueOffset] { + const QgsTiledSceneTile tile = mIndex.getTile( tileId.uniqueId ); + // we do not load tiles that are too big - at least for the time being // the problem is that their 3D bounding boxes with ECEF coordinates are huge // and we are unable to turn them into planar bounding boxes - if ( hasLargeBounds( mTile ) ) + if ( hasLargeBounds( tile ) ) return; - QString uri = mTile.resources().value( QStringLiteral( "content" ) ).toString(); + QString uri = tile.resources().value( QStringLiteral( "content" ) ).toString(); if ( uri.isEmpty() ) { // nothing to show for this tile @@ -71,7 +74,7 @@ QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const Qg return; } - uri = mTile.baseUrl().resolved( uri ).toString(); + uri = tile.baseUrl().resolved( uri ).toString(); QByteArray content = mFactory.mIndex.retrieveContent( uri ); if ( content.isEmpty() ) { @@ -88,13 +91,13 @@ QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const Qg } QgsGltf3DUtils::EntityTransform entityTransform; - entityTransform.tileTransform = ( mTile.transform() ? *mTile.transform() : QgsMatrix4x4() ); + entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() ); entityTransform.tileTransform.translate( tileContent.rtcCenter ); entityTransform.sceneOriginTargetCrs = mFactory.mMap.origin(); entityTransform.ecefToTargetCrs = &mFactory.mBoundsTransform; entityTransform.zValueScale = zValueScale; entityTransform.zValueOffset = zValueOffset; - entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( mTile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() ); + entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( tile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() ); QStringList errors; mEntity = QgsGltf3DUtils::gltfToEntity( tileContent.gltf, entityTransform, uri, &errors ); @@ -122,7 +125,6 @@ QgsTiledSceneChunkLoader::~QgsTiledSceneChunkLoader() } } - Qt3DCore::QEntity *QgsTiledSceneChunkLoader::createEntity( Qt3DCore::QEntity *parent ) { if ( !mEntity ) @@ -145,9 +147,7 @@ QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory( const Qgs3DMap QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const { - const QgsTiledSceneTile t = mIndex.getTile( node->tileId().uniqueId ); - - return new QgsTiledSceneChunkLoader( node, *this, t, mZValueScale, mZValueOffset ); + return new QgsTiledSceneChunkLoader( node, mIndex, *this, mZValueScale, mZValueOffset ); } // converts box from map coordinates to world coords (also flips [X,Y] to [X,-Z]) diff --git a/src/3d/qgstiledscenechunkloader_p.h b/src/3d/qgstiledscenechunkloader_p.h index 5e34cdff3ce..dd6c95f2c5c 100644 --- a/src/3d/qgstiledscenechunkloader_p.h +++ b/src/3d/qgstiledscenechunkloader_p.h @@ -54,7 +54,7 @@ class QgsTiledSceneChunkLoader : public QgsChunkLoader { Q_OBJECT public: - QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneChunkLoaderFactory &factory, const QgsTiledSceneTile &t, double zValueScale, double zValueOffset ); + QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneIndex &index, const QgsTiledSceneChunkLoaderFactory &factory, double zValueScale, double zValueOffset ); ~QgsTiledSceneChunkLoader(); @@ -62,7 +62,7 @@ class QgsTiledSceneChunkLoader : public QgsChunkLoader private: const QgsTiledSceneChunkLoaderFactory &mFactory; - QgsTiledSceneTile mTile; + QgsTiledSceneIndex mIndex; QFutureWatcher *mFutureWatcher = nullptr; Qt3DCore::QEntity *mEntity = nullptr; }; From f8e45036be214dfe8656c14666ef531c37aa87af Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Thu, 16 Nov 2023 08:11:20 +0100 Subject: [PATCH 22/97] More adapted test capabilities --- tests/testdata/qgis_server/getprojectsettings.txt | 1 + tests/testdata/qgis_server/getprojectsettings_opacity.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/testdata/qgis_server/getprojectsettings.txt b/tests/testdata/qgis_server/getprojectsettings.txt index ea4fef5723c..093d7458c34 100644 --- a/tests/testdata/qgis_server/getprojectsettings.txt +++ b/tests/testdata/qgis_server/getprojectsettings.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf diff --git a/tests/testdata/qgis_server/getprojectsettings_opacity.txt b/tests/testdata/qgis_server/getprojectsettings_opacity.txt index 1dbbd9d7215..c80494448cc 100644 --- a/tests/testdata/qgis_server/getprojectsettings_opacity.txt +++ b/tests/testdata/qgis_server/getprojectsettings_opacity.txt @@ -40,6 +40,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf From bfa5964baeeed489c8250cf773c0c7319204c837 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Thu, 16 Nov 2023 12:38:42 +0100 Subject: [PATCH 23/97] Adapt accesscontrol test capabilities --- .../results/getcapabilities_wms_dimension.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testdata/qgis_server_accesscontrol/results/getcapabilities_wms_dimension.txt b/tests/testdata/qgis_server_accesscontrol/results/getcapabilities_wms_dimension.txt index 6917309f899..76d5a106fb7 100644 --- a/tests/testdata/qgis_server_accesscontrol/results/getcapabilities_wms_dimension.txt +++ b/tests/testdata/qgis_server_accesscontrol/results/getcapabilities_wms_dimension.txt @@ -41,6 +41,7 @@ Content-Type: text/xml; charset=utf-8 image/png; mode=8bit image/png; mode=1bit application/dxf + application/pdf From 51e2041c45302f421b69b9b847aadba6301f6731 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 21 Aug 2023 20:38:56 +0200 Subject: [PATCH 24/97] qgsfeaturesource: Remove empty line at the end of file --- src/core/qgsfeaturesource.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/qgsfeaturesource.cpp b/src/core/qgsfeaturesource.cpp index 15be6a53cb4..33bf319e7ad 100644 --- a/src/core/qgsfeaturesource.cpp +++ b/src/core/qgsfeaturesource.cpp @@ -191,4 +191,3 @@ QgsFeatureSource::SpatialIndexPresence QgsFeatureSource::hasSpatialIndex() const { return SpatialIndexUnknown; } - From 76425f694590a2ba5b498261ade0dcbc3a9f83d5 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Mon, 2 Oct 2023 14:44:16 +0200 Subject: [PATCH 25/97] feat(qgsxmlutils): add read/write QgsBox3D --- python/core/auto_generated/qgsxmlutils.sip.in | 24 ++++++++++++ src/core/qgsxmlutils.cpp | 38 +++++++++++++++++++ src/core/qgsxmlutils.h | 19 ++++++++++ 3 files changed, 81 insertions(+) diff --git a/python/core/auto_generated/qgsxmlutils.sip.in b/python/core/auto_generated/qgsxmlutils.sip.in index 77cd94f7973..b7f33303ea7 100644 --- a/python/core/auto_generated/qgsxmlutils.sip.in +++ b/python/core/auto_generated/qgsxmlutils.sip.in @@ -37,6 +37,17 @@ Decodes a distance unit from a DOM element. static QgsRectangle readRectangle( const QDomElement &element ); + static QgsBox3D readBox3D( const QDomElement &element ); +%Docstring +Decodes a DOM element to a 3D box. + +:param element: DOM document + +:return: decoded 3D box + +.. versionadded:: 3.36 +%End + static QDomElement writeMapUnits( Qgis::DistanceUnit units, QDomDocument &doc ); %Docstring @@ -48,6 +59,19 @@ Encodes a distance unit to a DOM element. :return: element containing encoded units .. seealso:: :py:func:`readMapUnits` +%End + + static QDomElement writeBox3D( const QgsBox3D &box, QDomDocument &doc, const QString &elementName = QStringLiteral( "extent3D" ) ); +%Docstring +Encodes a 3D box to a DOM element. + +:param box: 3D box to encode +:param doc: DOM document +:param elementName: name of the DOM element + +:return: element containing encoded 3D box + +.. versionadded:: 3.36 %End static QDomElement writeRectangle( const QgsRectangle &rect, QDomDocument &doc, const QString &elementName = QStringLiteral( "extent" ) ); diff --git a/src/core/qgsxmlutils.cpp b/src/core/qgsxmlutils.cpp index aa785e9e95d..a0c2b6b6df3 100644 --- a/src/core/qgsxmlutils.cpp +++ b/src/core/qgsxmlutils.cpp @@ -36,6 +36,31 @@ Qgis::DistanceUnit QgsXmlUtils::readMapUnits( const QDomElement &element ) } } +QgsBox3D QgsXmlUtils::readBox3D( const QDomElement &element ) +{ + QgsBox3D aoi; + + const double xmin = element.attribute( QStringLiteral( "xmin" ) ).toDouble(); + aoi.setXMinimum( xmin ); + + const double ymin = element.attribute( QStringLiteral( "ymin" ) ).toDouble(); + aoi.setYMinimum( ymin ); + + const double zmin = element.attribute( QStringLiteral( "zmin" ) ).toDouble(); + aoi.setZMinimum( zmin ); + + const double xmax = element.attribute( QStringLiteral( "xmax" ) ).toDouble(); + aoi.setXMaximum( xmax ); + + const double ymax = element.attribute( QStringLiteral( "ymax" ) ).toDouble(); + aoi.setYMaximum( ymax ); + + const double zmax = element.attribute( QStringLiteral( "zmax" ) ).toDouble(); + aoi.setZMaximum( zmax ); + + return aoi; +} + QgsRectangle QgsXmlUtils::readRectangle( const QDomElement &element ) { QgsRectangle aoi; @@ -78,6 +103,19 @@ QDomElement QgsXmlUtils::writeMapUnits( Qgis::DistanceUnit units, QDomDocument & return unitsNode; } +QDomElement QgsXmlUtils::writeBox3D( const QgsBox3D &box, QDomDocument &doc, const QString &elementName ) +{ + QDomElement elemExtent3D = doc.createElement( elementName ); + elemExtent3D.setAttribute( QStringLiteral( "xMin" ), box.xMinimum() ); + elemExtent3D.setAttribute( QStringLiteral( "yMin" ), box.yMinimum() ); + elemExtent3D.setAttribute( QStringLiteral( "zMin" ), box.zMinimum() ); + elemExtent3D.setAttribute( QStringLiteral( "xMax" ), box.xMaximum() ); + elemExtent3D.setAttribute( QStringLiteral( "yMax" ), box.yMaximum() ); + elemExtent3D.setAttribute( QStringLiteral( "zMax" ), box.zMaximum() ); + + return elemExtent3D; +} + QDomElement QgsXmlUtils::writeRectangle( const QgsRectangle &rect, QDomDocument &doc, const QString &elementName ) { QDomElement xMin = doc.createElement( QStringLiteral( "xmin" ) ); diff --git a/src/core/qgsxmlutils.h b/src/core/qgsxmlutils.h index 48f05e3f1f8..0decd29baec 100644 --- a/src/core/qgsxmlutils.h +++ b/src/core/qgsxmlutils.h @@ -18,6 +18,7 @@ class QDomDocument; class QgsRectangle; +class QgsBox3D; #include #include @@ -48,6 +49,14 @@ class CORE_EXPORT QgsXmlUtils static QgsRectangle readRectangle( const QDomElement &element ); + /** + * Decodes a DOM element to a 3D box. + * \param element DOM document + * \returns decoded 3D box + * \since QGIS 3.36 + */ + static QgsBox3D readBox3D( const QDomElement &element ); + /* writing */ /** @@ -59,6 +68,16 @@ class CORE_EXPORT QgsXmlUtils */ static QDomElement writeMapUnits( Qgis::DistanceUnit units, QDomDocument &doc ); + /** + * Encodes a 3D box to a DOM element. + * \param box 3D box to encode + * \param doc DOM document + * \param elementName name of the DOM element + * \returns element containing encoded 3D box + * \since QGIS 3.36 + */ + static QDomElement writeBox3D( const QgsBox3D &box, QDomDocument &doc, const QString &elementName = QStringLiteral( "extent3D" ) ); + /** * Encodes a rectangle to a DOM element. * \param rect rectangle to encode From 576d810875f525aa22e1bcdf8f0636ceeccba1c9 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Mon, 2 Oct 2023 14:45:24 +0200 Subject: [PATCH 26/97] feat(QgsMapLayer): add 3D extent handling --- python/core/auto_generated/qgsmaplayer.sip.in | 14 +++++ src/core/qgsmaplayer.cpp | 51 ++++++++++++++++--- src/core/qgsmaplayer.h | 16 +++++- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index bc2505e69f8..58ada48baee 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -520,6 +520,13 @@ Returns new instance of :py:class:`QgsMapLayerRenderer` that will be used for re virtual QgsRectangle extent() const; %Docstring Returns the extent of the layer. +%End + + virtual QgsBox3D extent3D() const; +%Docstring +Returns the 3D extent of the layer. + +.. versionadded:: 3.36 %End QgsRectangle wgs84Extent( bool forceRecalculate = false ) const; @@ -2117,6 +2124,13 @@ Copies attributes like name, short name, ... into another layer. virtual void setExtent( const QgsRectangle &rect ); %Docstring Sets the extent +%End + + virtual void setExtent3D( const QgsBox3D &box ); +%Docstring +Sets the extent + +.. versionadded:: 3.36 %End void setValid( bool valid ); diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 96d0724cc90..5b3bc1b0151 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -138,7 +138,7 @@ void QgsMapLayer::clone( QgsMapLayer *layer ) const layer->setName( name() ); layer->setShortName( shortName() ); - layer->setExtent( extent() ); + layer->setExtent3D( extent3D() ); layer->setMaximumScale( maximumScale() ); layer->setMinimumScale( minimumScale() ); layer->setScaleBasedVisibility( hasScaleBasedVisibility() ); @@ -363,6 +363,13 @@ QgsRectangle QgsMapLayer::extent() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS + return mExtent.toRectangle(); +} + +QgsBox3D QgsMapLayer::extent3D() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + return mExtent; } @@ -608,10 +615,18 @@ bool QgsMapLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &cont // read Extent if ( mReadFlags & QgsMapLayer::FlagReadExtentFromXml ) { - const QDomNode extentNode = layer_node.namedItem( QStringLiteral( "extent" ) ); - if ( !extentNode.isNull() ) + const QDomNode extent3DNode = layer_node.namedItem( QStringLiteral( "extent3D" ) ); + if ( extent3DNode.isNull() ) { - mExtent = QgsXmlUtils::readRectangle( extentNode.toElement() ); + const QDomNode extentNode = layer_node.namedItem( QStringLiteral( "extent" ) ); + if ( !extentNode.isNull() ) + { + mExtent = QgsXmlUtils::readRectangle( extentNode.toElement() ); + } + } + else + { + mExtent = QgsXmlUtils::readBox3D( extent3DNode.toElement() ); } } @@ -625,7 +640,8 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume if ( !extent().isNull() ) { - layerElement.appendChild( QgsXmlUtils::writeRectangle( mExtent, document ) ); + layerElement.appendChild( QgsXmlUtils::writeBox3D( mExtent, document ) ); + layerElement.appendChild( QgsXmlUtils::writeRectangle( mExtent.toRectangle(), document ) ); layerElement.appendChild( QgsXmlUtils::writeRectangle( wgs84Extent( true ), document, QStringLiteral( "wgs84extent" ) ) ); } @@ -968,8 +984,16 @@ QgsDataProvider::ReadFlags QgsMapLayer::providerReadFlags( const QDomNode &layer if ( layerReadFlags & QgsMapLayer::FlagReadExtentFromXml ) { - const QDomNode extentNode = layerNode.namedItem( QStringLiteral( "extent" ) ); - if ( !extentNode.isNull() ) + const QDomNode extent3DNode = layerNode.namedItem( QStringLiteral( "extent3D" ) ); + if ( extent3DNode.isNull() ) + { + const QDomNode extentNode = layerNode.namedItem( QStringLiteral( "extent" ) ); + if ( !extentNode.isNull() ) + { + flags |= QgsDataProvider::SkipGetExtent; + } + } + else { flags |= QgsDataProvider::SkipGetExtent; } @@ -2604,6 +2628,11 @@ void QgsMapLayer::emitStyleChanged() } void QgsMapLayer::setExtent( const QgsRectangle &extent ) +{ + updateExtent( QgsBox3D( extent ) ); +} + +void QgsMapLayer::setExtent3D( const QgsBox3D &extent ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -2753,7 +2782,7 @@ QgsRectangle QgsMapLayer::wgs84Extent( bool forceRecalculate ) const transformer.setBallparkTransformsAreAppropriate( true ); try { - wgs84Extent = transformer.transformBoundingBox( mExtent ); + wgs84Extent = transformer.transformBoundingBox( mExtent.toRectangle() ); } catch ( const QgsCsException &cse ) { @@ -2765,6 +2794,12 @@ QgsRectangle QgsMapLayer::wgs84Extent( bool forceRecalculate ) const } void QgsMapLayer::updateExtent( const QgsRectangle &extent ) const +{ + QgsBox3D box = extent; + updateExtent( box ); +} + +void QgsMapLayer::updateExtent( const QgsBox3D &extent ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index e1fe5ffef71..d2cf25d8a4b 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -58,6 +58,7 @@ class QDomDocument; class QKeyEvent; class QPainter; class QgsRenderContext; +class QgsBox3D; /* * Constants used to describe copy-paste MIME types @@ -543,6 +544,12 @@ class CORE_EXPORT QgsMapLayer : public QObject //! Returns the extent of the layer. virtual QgsRectangle extent() const; + /** + * Returns the 3D extent of the layer. + * \since QGIS 3.36 + */ + virtual QgsBox3D extent3D() const; + /** * Returns the WGS84 extent (EPSG:4326) of the layer according to * ReadFlag::FlagTrustLayerMetadata. If that flag is activated, then the @@ -2061,6 +2068,12 @@ class CORE_EXPORT QgsMapLayer : public QObject //! Sets the extent virtual void setExtent( const QgsRectangle &rect ); + /** + * Sets the extent + * \since QGIS 3.36 + */ + virtual void setExtent3D( const QgsBox3D &box ); + //! Sets whether layer is valid or not void setValid( bool valid ); @@ -2278,6 +2291,7 @@ class CORE_EXPORT QgsMapLayer : public QObject // const method because extents are mutable void updateExtent( const QgsRectangle &extent ) const; + void updateExtent( const QgsBox3D &extent ) const; /** * This method returns TRUE by default but can be overwritten to specify @@ -2344,7 +2358,7 @@ class CORE_EXPORT QgsMapLayer : public QObject QgsAbstract3DRenderer *m3DRenderer = nullptr; //! Extent of the layer - mutable QgsRectangle mExtent; + mutable QgsBox3D mExtent; //! Extent of the layer in EPSG:4326 mutable QgsRectangle mWgs84Extent; From 21f75ca05190462bd2566ef739818c2095cfca82 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 13 Oct 2023 08:51:40 +0200 Subject: [PATCH 27/97] feat(QgsFeatureSource): add sourceExtent3D function --- python/core/auto_generated/qgsfeaturesource.sip.in | 9 +++++++++ src/core/qgsfeaturesource.cpp | 9 +++++++-- src/core/qgsfeaturesource.h | 8 ++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/python/core/auto_generated/qgsfeaturesource.sip.in b/python/core/auto_generated/qgsfeaturesource.sip.in index d952452ca5b..aa9a6a0693c 100644 --- a/python/core/auto_generated/qgsfeaturesource.sip.in +++ b/python/core/auto_generated/qgsfeaturesource.sip.in @@ -127,6 +127,15 @@ all features in the source. Returns the extent of all geometries from the source. The base class implementation uses a non-optimised approach of looping through all features in the source. +%End + + virtual QgsBox3D sourceExtent3D() const; +%Docstring +Returns the 3D extent of all geometries from the source. +The base class implementation uses a non-optimised approach of looping through +all features in the source. + +.. versionadded:: 3.36 %End virtual QgsFeatureIds allFeatureIds() const; diff --git a/src/core/qgsfeaturesource.cpp b/src/core/qgsfeaturesource.cpp index 33bf319e7ad..2ce231275ed 100644 --- a/src/core/qgsfeaturesource.cpp +++ b/src/core/qgsfeaturesource.cpp @@ -97,7 +97,12 @@ QVariant QgsFeatureSource::maximumValue( int fieldIndex ) const QgsRectangle QgsFeatureSource::sourceExtent() const { - QgsRectangle r; + return sourceExtent3D().toRectangle(); +} + +QgsBox3D QgsFeatureSource::sourceExtent3D() const +{ + QgsBox3D r; QgsFeatureRequest req; req.setNoAttributes(); @@ -107,7 +112,7 @@ QgsRectangle QgsFeatureSource::sourceExtent() const while ( it.nextFeature( f ) ) { if ( f.hasGeometry() ) - r.combineExtentWith( f.geometry().boundingBox() ); + r.combineWith( f.geometry().boundingBox3D() ); } return r; } diff --git a/src/core/qgsfeaturesource.h b/src/core/qgsfeaturesource.h index 4be32eae97f..c6c4cab81c5 100644 --- a/src/core/qgsfeaturesource.h +++ b/src/core/qgsfeaturesource.h @@ -149,6 +149,14 @@ class CORE_EXPORT QgsFeatureSource */ virtual QgsRectangle sourceExtent() const; + /** + * Returns the 3D extent of all geometries from the source. + * The base class implementation uses a non-optimised approach of looping through + * all features in the source. + * \since QGIS 3.36 + */ + virtual QgsBox3D sourceExtent3D() const; + /** * Returns a list of all feature IDs for features present in the source. */ From f822021e95c15061d60b6956ddbb2f68fe67a7d1 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 13 Oct 2023 08:57:02 +0200 Subject: [PATCH 28/97] feat(qgsvectorlayer): add 3D extent handling --- .../vector/qgsvectorlayer.sip.in | 10 ++ src/core/vector/qgsvectorlayer.cpp | 110 ++++++++++++++++++ src/core/vector/qgsvectorlayer.h | 5 + tests/src/python/test_qgsvectorlayer.py | 2 + 4 files changed, 127 insertions(+) diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index 6bbe2c3f1e8..fe96f749f9c 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1745,6 +1745,11 @@ Returns new instance of :py:class:`QgsMapLayerRenderer` that will be used for re virtual QgsRectangle sourceExtent() const ${SIP_FINAL}; + virtual QgsBox3D extent3D() const ${SIP_FINAL}; + + virtual QgsBox3D sourceExtent3D() const ${SIP_FINAL}; + + virtual QgsFields fields() const ${SIP_FINAL}; %Docstring @@ -2993,6 +2998,11 @@ Emitted when the feature count for symbols on this layer has been recalculated. protected: virtual void setExtent( const QgsRectangle &rect ) ${SIP_FINAL}; +%Docstring +Sets the extent +%End + virtual void setExtent3D( const QgsBox3D &rect ) ${SIP_FINAL}; + %Docstring Sets the extent %End diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index 5079d9fcbf6..c9900a72077 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -985,6 +985,14 @@ void QgsVectorLayer::setExtent( const QgsRectangle &r ) mValidExtent = true; } +void QgsVectorLayer::setExtent3D( const QgsBox3D &r ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + QgsMapLayer::setExtent3D( r ); + mValidExtent = true; +} + void QgsVectorLayer::updateDefaultValues( QgsFeatureId fid, QgsFeature feature ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -1101,6 +1109,101 @@ QgsRectangle QgsVectorLayer::extent() const return rect; } +QgsBox3D QgsVectorLayer:: extent3D() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + QgsBox3D extent; + extent.setNull(); + + if ( !isSpatial() ) + return extent; + + if ( !mValidExtent && mLazyExtent && mReadExtentFromXml && !mXmlExtent.isNull() ) + { + updateExtent( mXmlExtent ); + mValidExtent = true; + mLazyExtent = false; + } + + if ( !mValidExtent && mLazyExtent && mDataProvider && mDataProvider->isValid() ) + { + // store the extent + updateExtent( mDataProvider->extent3D() ); + mValidExtent = true; + mLazyExtent = false; + + // show the extent + QgsDebugMsgLevel( QStringLiteral( "Extent of layer: %1" ).arg( mExtent.toString() ), 3 ); + } + + if ( mValidExtent ) + return QgsMapLayer::extent3D(); + + if ( !isValid() || !mDataProvider ) + { + QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 ); + return extent; + } + + if ( !mEditBuffer || + ( !mDataProvider->transaction() && ( mEditBuffer->deletedFeatureIds().isEmpty() && mEditBuffer->changedGeometries().isEmpty() ) ) || + QgsDataSourceUri( mDataProvider->dataSourceUri() ).useEstimatedMetadata() ) + { + mDataProvider->updateExtents(); + + // get the extent of the layer from the provider + // but only when there are some features already + if ( mDataProvider->featureCount() != 0 ) + { + const QgsBox3D ext = mDataProvider->extent3D(); + extent.combineWith( ext ); + } + + if ( mEditBuffer && !mDataProvider->transaction() ) + { + const auto addedFeatures = mEditBuffer->addedFeatures(); + for ( QgsFeatureMap::const_iterator it = addedFeatures.constBegin(); it != addedFeatures.constEnd(); ++it ) + { + if ( it->hasGeometry() ) + { + const QgsBox3D bbox = it->geometry().boundingBox3D(); + extent.combineWith( bbox ); + } + } + } + } + else + { + QgsFeatureIterator fit = getFeatures( QgsFeatureRequest() + .setNoAttributes() ); + + QgsFeature fet; + while ( fit.nextFeature( fet ) ) + { + if ( fet.hasGeometry() && fet.geometry().type() != Qgis::GeometryType::Unknown ) + { + const QgsBox3D bb = fet.geometry().boundingBox3D(); + extent.combineWith( bb ); + } + } + } + + if ( extent.xMinimum() > extent.xMaximum() && extent.yMinimum() > extent.yMaximum() && extent.zMinimum() > extent.zMaximum() ) + { + // special case when there are no features in provider nor any added + extent = QgsBox3D(); // use rectangle with zero coordinates + } + + updateExtent( extent ); + mValidExtent = true; + + // Send this (hopefully) up the chain to the map canvas + emit recalculateExtents(); + + return extent; +} + QgsRectangle QgsVectorLayer::sourceExtent() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -1108,6 +1211,13 @@ QgsRectangle QgsVectorLayer::sourceExtent() const return extent(); } +QgsBox3D QgsVectorLayer::sourceExtent3D() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return extent3D(); +} + QString QgsVectorLayer::subsetString() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 8ee442c2d11..9c360696030 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1677,6 +1677,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte QgsRectangle extent() const FINAL; QgsRectangle sourceExtent() const FINAL; + QgsBox3D extent3D() const FINAL; + QgsBox3D sourceExtent3D() const FINAL; + /** * Returns the list of fields of this layer. * This also includes fields which have not yet been saved to the provider. @@ -2802,6 +2805,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte protected: //! Sets the extent void setExtent( const QgsRectangle &rect ) FINAL; + //! Sets the extent + void setExtent3D( const QgsBox3D &rect ) FINAL; private slots: void invalidateSymbolCountedFlag(); diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 57b5f405083..58f6d6af0d7 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -4386,6 +4386,8 @@ class TestQgsVectorLayerTransformContext(QgisTestCase): layer.createMapRenderer(QgsRenderContext()) layer.extent() layer.sourceExtent() + layer.extent3D() + layer.sourceExtent3D() layer.fields() layer.attributeList() layer.primaryKeyAttributes() From fa2fe8f776717fe775a53b01c58b01c35c20ed88 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Wed, 27 Sep 2023 18:31:54 +0200 Subject: [PATCH 29/97] fix(QgsDataProvider): migrate members from QgsRasterDataProviderElevationProperties to parent class This allows provider classes to have a useable elevation properties class, for example to know if the provided data is 3D or 2D. This will be useful to compute 3D/2D extent. --- .../qgsdataproviderelevationproperties.sip.in | 26 +++++++++++++++++++ ...sterdataproviderelevationproperties.sip.in | 19 -------------- .../qgsdataproviderelevationproperties.cpp | 10 +++++++ src/core/qgsdataproviderelevationproperties.h | 23 ++++++++++++++++ ...srasterdataproviderelevationproperties.cpp | 10 ------- ...qgsrasterdataproviderelevationproperties.h | 21 --------------- 6 files changed, 59 insertions(+), 50 deletions(-) diff --git a/python/core/auto_generated/qgsdataproviderelevationproperties.sip.in b/python/core/auto_generated/qgsdataproviderelevationproperties.sip.in index 63f8f2b520b..e4f47e39337 100644 --- a/python/core/auto_generated/qgsdataproviderelevationproperties.sip.in +++ b/python/core/auto_generated/qgsdataproviderelevationproperties.sip.in @@ -36,9 +36,35 @@ Base class for handling elevation related properties for a data provider. QgsDataProviderElevationProperties(); %Docstring Constructor for QgsDataProviderElevationProperties. +%End + + virtual bool containsElevationData() const; +%Docstring +Returns ``True`` if the data provider definitely contains elevation related data. + +.. note:: + + Even if this method returns ``False``, the data may still relate to elevation values. ``True`` will only + be returned in situations where elevation data is definitively present. + +.. seealso:: :py:func:`setContainsElevationData` + +.. versionadded:: 3.36 +%End + + virtual void setContainsElevationData( bool contains ); +%Docstring +Sets whether the data provider definitely contains elevation related data. + +.. seealso:: :py:func:`containsElevationData` + +.. versionadded:: 3.36 %End virtual ~QgsDataProviderElevationProperties(); + + protected: + }; /************************************************************************ diff --git a/python/core/auto_generated/raster/qgsrasterdataproviderelevationproperties.sip.in b/python/core/auto_generated/raster/qgsrasterdataproviderelevationproperties.sip.in index 674b9a12e55..b76d211c0b6 100644 --- a/python/core/auto_generated/raster/qgsrasterdataproviderelevationproperties.sip.in +++ b/python/core/auto_generated/raster/qgsrasterdataproviderelevationproperties.sip.in @@ -26,25 +26,6 @@ Handles elevation related properties for a raster data provider. QgsRasterDataProviderElevationProperties(); %Docstring Constructor for QgsRasterDataProviderElevationProperties. -%End - - bool containsElevationData() const; -%Docstring -Returns ``True`` if the raster data provider definitely contains elevation related data. - -.. note:: - - Even if this method returns ``False``, the raster data may still relate to elevation values. ``True`` will only - be returned in situations where elevation data is definitively present. - -.. seealso:: :py:func:`setContainsElevationData` -%End - - void setContainsElevationData( bool contains ); -%Docstring -Sets whether the raster data provider definitely contains elevation related data. - -.. seealso:: :py:func:`containsElevationData` %End }; diff --git a/src/core/qgsdataproviderelevationproperties.cpp b/src/core/qgsdataproviderelevationproperties.cpp index d5e6126c9aa..64fc8cfdaaa 100644 --- a/src/core/qgsdataproviderelevationproperties.cpp +++ b/src/core/qgsdataproviderelevationproperties.cpp @@ -19,3 +19,13 @@ QgsDataProviderElevationProperties::QgsDataProviderElevationProperties() = default; QgsDataProviderElevationProperties::~QgsDataProviderElevationProperties() = default; + +bool QgsDataProviderElevationProperties::containsElevationData() const +{ + return mContainsElevationData; +} + +void QgsDataProviderElevationProperties::setContainsElevationData( bool contains ) +{ + mContainsElevationData = contains; +} diff --git a/src/core/qgsdataproviderelevationproperties.h b/src/core/qgsdataproviderelevationproperties.h index 370e71ce3a2..e3541ff8cfc 100644 --- a/src/core/qgsdataproviderelevationproperties.h +++ b/src/core/qgsdataproviderelevationproperties.h @@ -52,7 +52,30 @@ class CORE_EXPORT QgsDataProviderElevationProperties */ QgsDataProviderElevationProperties(); + /** + * Returns TRUE if the data provider definitely contains elevation related data. + * + * \note Even if this method returns FALSE, the data may still relate to elevation values. TRUE will only + * be returned in situations where elevation data is definitively present. + * + * \see setContainsElevationData() + * \since QGIS 3.36 + */ + virtual bool containsElevationData() const; + + /** + * Sets whether the data provider definitely contains elevation related data. + * + * \see containsElevationData() + * \since QGIS 3.36 + */ + virtual void setContainsElevationData( bool contains ); + virtual ~QgsDataProviderElevationProperties(); + + protected: + bool mContainsElevationData = false; + }; #endif // QGSDATAPROVIDERELEVATIONPROPERTIES_H diff --git a/src/core/raster/qgsrasterdataproviderelevationproperties.cpp b/src/core/raster/qgsrasterdataproviderelevationproperties.cpp index 97a90faa91a..f22e8bc34dd 100644 --- a/src/core/raster/qgsrasterdataproviderelevationproperties.cpp +++ b/src/core/raster/qgsrasterdataproviderelevationproperties.cpp @@ -17,13 +17,3 @@ #include "qgsrasterdataproviderelevationproperties.h" QgsRasterDataProviderElevationProperties::QgsRasterDataProviderElevationProperties() = default; - -bool QgsRasterDataProviderElevationProperties::containsElevationData() const -{ - return mContainsElevationData; -} - -void QgsRasterDataProviderElevationProperties::setContainsElevationData( bool contains ) -{ - mContainsElevationData = contains; -} diff --git a/src/core/raster/qgsrasterdataproviderelevationproperties.h b/src/core/raster/qgsrasterdataproviderelevationproperties.h index 69145b3d190..f337d74dfdd 100644 --- a/src/core/raster/qgsrasterdataproviderelevationproperties.h +++ b/src/core/raster/qgsrasterdataproviderelevationproperties.h @@ -40,27 +40,6 @@ class CORE_EXPORT QgsRasterDataProviderElevationProperties : public QgsDataProvi */ QgsRasterDataProviderElevationProperties(); - /** - * Returns TRUE if the raster data provider definitely contains elevation related data. - * - * \note Even if this method returns FALSE, the raster data may still relate to elevation values. TRUE will only - * be returned in situations where elevation data is definitively present. - * - * \see setContainsElevationData() - */ - bool containsElevationData() const; - - /** - * Sets whether the raster data provider definitely contains elevation related data. - * - * \see containsElevationData() - */ - void setContainsElevationData( bool contains ); - - private: - - bool mContainsElevationData = false; - }; #endif // QGSRASTERDATAPROVIDERELEVATIONPROPERTIES_H From 365cf6aa88ec2484a00a2770c03cca2b37d2b0ce Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Thu, 12 Oct 2023 18:09:45 +0200 Subject: [PATCH 30/97] fix(QgsVectorDataProvider): add default class members to support QgsDataProviderElevationProperties --- .../vector/qgsvectordataprovider.sip.in | 3 +++ src/core/providers/qgsdataprovider.h | 2 +- src/core/vector/qgsvectordataprovider.cpp | 16 ++++++++++++++++ src/core/vector/qgsvectordataprovider.h | 4 ++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index 5a6165a6e1f..8f0af569ac0 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -654,6 +654,9 @@ from the ``source`` provider. virtual QgsVectorDataProviderTemporalCapabilities *temporalCapabilities(); + virtual QgsDataProviderElevationProperties *elevationProperties(); + + signals: void raiseError( const QString &msg ) const; diff --git a/src/core/providers/qgsdataprovider.h b/src/core/providers/qgsdataprovider.h index 2cc783a5d36..9a75f5d0ae5 100644 --- a/src/core/providers/qgsdataprovider.h +++ b/src/core/providers/qgsdataprovider.h @@ -27,11 +27,11 @@ #include "qgscoordinatetransformcontext.h" #include "qgslayermetadata.h" #include "qgserror.h" +#include "qgsdataproviderelevationproperties.h" class QgsRectangle; class QgsCoordinateReferenceSystem; class QgsDataProviderTemporalCapabilities; -class QgsDataProviderElevationProperties; /** diff --git a/src/core/vector/qgsvectordataprovider.cpp b/src/core/vector/qgsvectordataprovider.cpp index 804c4e6edeb..e1e3f9afa2a 100644 --- a/src/core/vector/qgsvectordataprovider.cpp +++ b/src/core/vector/qgsvectordataprovider.cpp @@ -35,11 +35,13 @@ #include "qgsogrproxytextcodec.h" #include "qgsthreadingutils.h" #include +#include "qgsdataproviderelevationproperties.h" QgsVectorDataProvider::QgsVectorDataProvider( const QString &uri, const ProviderOptions &options, QgsDataProvider::ReadFlags flags ) : QgsDataProvider( uri, options, flags ) , mTemporalCapabilities( std::make_unique< QgsVectorDataProviderTemporalCapabilities >() ) + , mElevationProperties( std::make_unique< QgsDataProviderElevationProperties >() ) { } @@ -1056,3 +1058,17 @@ const QgsVectorDataProviderTemporalCapabilities *QgsVectorDataProvider::temporal return mTemporalCapabilities.get(); } + +QgsDataProviderElevationProperties *QgsVectorDataProvider::elevationProperties() +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return mElevationProperties.get(); +} + +const QgsDataProviderElevationProperties *QgsVectorDataProvider::elevationProperties() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return mElevationProperties.get(); +} diff --git a/src/core/vector/qgsvectordataprovider.h b/src/core/vector/qgsvectordataprovider.h index efcdcaef05e..dcbb252eae3 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -633,6 +633,9 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat QgsVectorDataProviderTemporalCapabilities *temporalCapabilities() override; const QgsVectorDataProviderTemporalCapabilities *temporalCapabilities() const override SIP_SKIP; + QgsDataProviderElevationProperties *elevationProperties() override; + const QgsDataProviderElevationProperties *elevationProperties() const override SIP_SKIP; + signals: /** @@ -713,6 +716,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat mutable QStringList mErrors; std::unique_ptr< QgsVectorDataProviderTemporalCapabilities > mTemporalCapabilities; + std::unique_ptr< QgsDataProviderElevationProperties > mElevationProperties; static QStringList sEncodings; From d3604b5c0b2e86ad7bcc0953249cf7c4a09b6342 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 13 Oct 2023 08:55:00 +0200 Subject: [PATCH 31/97] feat(QgsDataProvider): handle 3D extent/boundingbox --- .../core/auto_generated/providers/qgsdataprovider.sip.in | 8 ++++++++ src/core/providers/qgsdataprovider.h | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/python/core/auto_generated/providers/qgsdataprovider.sip.in b/python/core/auto_generated/providers/qgsdataprovider.sip.in index 34383333cdf..db0753f86ae 100644 --- a/python/core/auto_generated/providers/qgsdataprovider.sip.in +++ b/python/core/auto_generated/providers/qgsdataprovider.sip.in @@ -197,6 +197,14 @@ Returns the extent of the layer :return: :py:class:`QgsRectangle` containing the extent of the layer %End + virtual QgsBox3D extent3D() const; +%Docstring +Returns the 3D extent of the layer + +:return: :py:class:`QgsBox3D` containing the 3D extent of the layer + +.. versionadded:: 3.36 +%End virtual bool isValid() const = 0; %Docstring diff --git a/src/core/providers/qgsdataprovider.h b/src/core/providers/qgsdataprovider.h index 9a75f5d0ae5..a241d4aee2b 100644 --- a/src/core/providers/qgsdataprovider.h +++ b/src/core/providers/qgsdataprovider.h @@ -275,6 +275,15 @@ class CORE_EXPORT QgsDataProvider : public QObject */ virtual QgsRectangle extent() const = 0; + /** + * Returns the 3D extent of the layer + * \returns QgsBox3D containing the 3D extent of the layer + * \since QGIS 3.36 + */ + virtual QgsBox3D extent3D() const + { + return extent().toBox3d( std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() ); + } /** * Returns TRUE if this is a valid layer. It is up to individual providers From 8d1155dd00eb53d310a2a74899ae3c64c8614490 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 13 Oct 2023 08:59:33 +0200 Subject: [PATCH 32/97] feat(QgsVectorDataProvider): add support for sourceExtent3D function --- .../vector/qgsvectordataprovider.sip.in | 2 + src/core/vector/qgsvectordataprovider.cpp | 7 ++ src/core/vector/qgsvectordataprovider.h | 1 + tests/src/core/testqgsvectordataprovider.cpp | 89 ++++++++++++++++++ tests/testdata/3d/lines_with_z.gpkg.zip | Bin 0 -> 5018 bytes 5 files changed, 99 insertions(+) create mode 100644 tests/testdata/3d/lines_with_z.gpkg.zip diff --git a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index 8f0af569ac0..7328f09c5c7 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -175,6 +175,8 @@ Returns the fields associated with this data provider. virtual QgsRectangle sourceExtent() const; + virtual QgsBox3D sourceExtent3D() const; + virtual QString sourceName() const; virtual QString dataComment() const; diff --git a/src/core/vector/qgsvectordataprovider.cpp b/src/core/vector/qgsvectordataprovider.cpp index e1e3f9afa2a..09dc42b86b6 100644 --- a/src/core/vector/qgsvectordataprovider.cpp +++ b/src/core/vector/qgsvectordataprovider.cpp @@ -105,6 +105,13 @@ QgsRectangle QgsVectorDataProvider::sourceExtent() const return extent(); } +QgsBox3D QgsVectorDataProvider::sourceExtent3D() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return extent3D(); +} + QString QgsVectorDataProvider::dataComment() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/vector/qgsvectordataprovider.h b/src/core/vector/qgsvectordataprovider.h index dcbb252eae3..df6f27f0104 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -202,6 +202,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat QgsCoordinateReferenceSystem sourceCrs() const override; QgsRectangle sourceExtent() const override; + QgsBox3D sourceExtent3D() const override; QString sourceName() const override { return QString(); } /** diff --git a/tests/src/core/testqgsvectordataprovider.cpp b/tests/src/core/testqgsvectordataprovider.cpp index dfdf61ff6c2..3c37492a67d 100644 --- a/tests/src/core/testqgsvectordataprovider.cpp +++ b/tests/src/core/testqgsvectordataprovider.cpp @@ -46,16 +46,22 @@ class TestQgsVectorDataProvider : public QObject void featureAtId(); + void sourceExtent(); + private: QgsVectorLayer *vlayerPoints = nullptr; QgsVectorLayer *vlayerLines = nullptr; + QgsVectorLayer *vlayerPoints3D = nullptr; + QgsVectorLayer *vlayerLines3D = nullptr; }; void TestQgsVectorDataProvider::initTestCase() { vlayerPoints = nullptr; vlayerLines = nullptr; + vlayerPoints3D = nullptr; + vlayerLines3D = nullptr; // load QGIS QgsApplication::init(); @@ -63,6 +69,8 @@ void TestQgsVectorDataProvider::initTestCase() const QString layerPointsUrl = QStringLiteral( TEST_DATA_DIR ) + "/points.shp"; const QString layerLinesUrl = QStringLiteral( TEST_DATA_DIR ) + "/lines.shp"; + const QString layerPoints3DUrl = QStringLiteral( TEST_DATA_DIR ) + "/3d/points_with_z.shp"; + const QString layerLines3DUrl = QStringLiteral( TEST_DATA_DIR ) + "/3d/lines_with_z.gpkg.zip"; // load layers const QgsVectorLayer::LayerOptions options { QgsCoordinateTransformContext() }; @@ -73,12 +81,22 @@ void TestQgsVectorDataProvider::initTestCase() vlayerLines = new QgsVectorLayer( layerLinesUrl, QStringLiteral( "testlayer" ), QStringLiteral( "ogr" ), options ); QVERIFY( vlayerLines ); QVERIFY( vlayerLines->isValid() ); + + vlayerPoints3D = new QgsVectorLayer( layerPoints3DUrl, QStringLiteral( "testlayer" ), QStringLiteral( "ogr" ), options ); + QVERIFY( vlayerPoints3D ); + QVERIFY( vlayerPoints3D->isValid() ); + + vlayerLines3D = new QgsVectorLayer( layerLines3DUrl, QStringLiteral( "testlayer" ), QStringLiteral( "ogr" ), options ); + QVERIFY( vlayerLines3D ); + QVERIFY( vlayerLines3D->isValid() ); } void TestQgsVectorDataProvider::cleanupTestCase() { delete vlayerPoints; delete vlayerLines; + delete vlayerPoints3D; + delete vlayerLines3D; // unload QGIS QgsApplication::exitQgis(); @@ -224,6 +242,77 @@ void TestQgsVectorDataProvider::featureAtId() QVERIFY( !feature.isValid() ); } +void TestQgsVectorDataProvider::sourceExtent() +{ + // 2d data + QgsVectorDataProvider *prLines = vlayerLines->dataProvider(); + + QGSCOMPARENEAR( prLines->sourceExtent().xMinimum(), -117.623, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent().xMaximum(), -82.3226, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent().yMinimum(), 23.2082, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent().yMaximum(), 46.1829, 0.001 ); + + QGSCOMPARENEAR( prLines->sourceExtent3D().xMinimum(), -117.623, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent3D().xMaximum(), -82.3226, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent3D().yMinimum(), 23.2082, 0.001 ); + QGSCOMPARENEAR( prLines->sourceExtent3D().yMaximum(), 46.1829, 0.001 ); + QVERIFY( std::isnan( prLines->sourceExtent3D().zMinimum() ) ); + QVERIFY( std::isnan( prLines->sourceExtent3D().zMaximum() ) ); + + + QgsVectorDataProvider *prPoints = vlayerPoints->dataProvider(); + + QGSCOMPARENEAR( prPoints->sourceExtent().xMinimum(), -118.889, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent().xMaximum(), -83.3333, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent().yMinimum(), 22.8002, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent().yMaximum(), 46.872, 0.001 ); + + QGSCOMPARENEAR( prPoints->sourceExtent3D().xMinimum(), -118.889, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent3D().xMaximum(), -83.3333, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent3D().yMinimum(), 22.8002, 0.001 ); + QGSCOMPARENEAR( prPoints->sourceExtent3D().yMaximum(), 46.872, 0.001 ); + QVERIFY( std::isnan( prPoints->sourceExtent3D().zMinimum() ) ); + QVERIFY( std::isnan( prPoints->sourceExtent3D().zMaximum() ) ); + + // 3d data + QgsVectorDataProvider *prLines3D = vlayerLines3D->dataProvider(); + + QGSCOMPARENEAR( prLines3D->sourceExtent().xMinimum(), 0.0, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent().xMaximum(), 322355.71, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent().yMinimum(), 0.0, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent().yMaximum(), 129791.26, 0.01 ); + + QGSCOMPARENEAR( prLines3D->sourceExtent3D().xMinimum(), 0.0, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent3D().xMaximum(), 322355.71, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent3D().yMinimum(), 0.0, 0.01 ); + QGSCOMPARENEAR( prLines3D->sourceExtent3D().yMaximum(), 129791.26, 0.01 ); + // TODO still nan as provider implementation is not done + QVERIFY( std::isnan( prLines3D->sourceExtent3D().zMinimum() ) ); + QVERIFY( std::isnan( prLines3D->sourceExtent3D().zMaximum() ) ); + // TODO as providers will have a extent3d implementation theses results are expected: + // QGSCOMPARENEAR( prLines3D->sourceExtent3D().zMinimum(), -5.00, 0.01 ); + // QGSCOMPARENEAR( prLines3D->sourceExtent3D().zMaximum(), 15.0, 0.01 ); + + + QgsVectorDataProvider *prPoints3D = vlayerPoints3D->dataProvider(); + + QGSCOMPARENEAR( prPoints3D->sourceExtent().xMinimum(), 321384.94, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent().xMaximum(), 322342.3, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent().yMinimum(), 129147.09, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent().yMaximum(), 130554.6, 0.01 ); + + QGSCOMPARENEAR( prPoints3D->sourceExtent3D().xMinimum(), 321384.94, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent3D().xMaximum(), 322342.3, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent3D().yMinimum(), 129147.09, 0.01 ); + QGSCOMPARENEAR( prPoints3D->sourceExtent3D().yMaximum(), 130554.6, 0.01 ); + // TODO still nan as provider implementation is not done + QVERIFY( std::isnan( prPoints3D->sourceExtent3D().zMinimum() ) ); + QVERIFY( std::isnan( prPoints3D->sourceExtent3D().zMaximum() ) ); + // TODO as providers will have a extent3d implementation theses results are expected: + // QGSCOMPARENEAR( prPoints3D->sourceExtent3D().zMinimum(), 64.9, 0.01 ); + // QGSCOMPARENEAR( prPoints3D->sourceExtent3D().zMaximum(), 105.6, 0.01 ); +} + QGSTEST_MAIN( TestQgsVectorDataProvider ) diff --git a/tests/testdata/3d/lines_with_z.gpkg.zip b/tests/testdata/3d/lines_with_z.gpkg.zip new file mode 100644 index 0000000000000000000000000000000000000000..2e71a85a60e4133ff298e70eb4c34d2ba5f6306c GIT binary patch literal 5018 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 ajA#GHaHa*Oxcqm83%@+{7v;bEH}_xe`Fe8z literal 0 HcmV?d00001 From 02e97346c3723153a5e2981da67b6b845dd154fd Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Thu, 16 Nov 2023 15:52:45 +0100 Subject: [PATCH 33/97] Write georeference information to pdf --- src/server/services/wms/qgswmsrenderer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 9be58de83a0..364111d29d0 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -1158,6 +1158,10 @@ namespace QgsWms pdfExportDetails.useOgcBestPracticeFormatGeoreferencing = mWmsParameters.pdfUseOgcBestPracticeFormatGeoreferencing(); const bool geoPdf = mWmsParameters.pdfAppendGeoreference(); std::unique_ptr pdf = std::make_unique( ms, tmpFileName, QStringLiteral( "PDF" ), false, QgsTask::Hidden, geoPdf, pdfExportDetails ); + if ( mWmsParameters.pdfAppendGeoreference() ) + { + pdf->setSaveWorldFile( true ); + } return pdf; } From 3b6d7c37b0e3b21ec77bc25df33bbae7fe441c6c Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Thu, 16 Nov 2023 17:28:08 +0100 Subject: [PATCH 34/97] Also adapt content-length --- tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt b/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt index af4ce56e3b0..2df76939ae7 100644 --- a/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt +++ b/tests/testdata/qgis_server/wms_getcapabilities_rewriting.txt @@ -1,4 +1,4 @@ -Content-Length: 14317 +Content-Length: 14354 Content-Type: text/xml; charset=utf-8 From 4175beaef431c49423b01a7bdd3f4853c6afc3bf Mon Sep 17 00:00:00 2001 From: Yoann Quenach de Quivillic Date: Tue, 14 Nov 2023 15:59:17 +0100 Subject: [PATCH 35/97] Fix #55169 - Atlas toolbar visibility --- src/app/layout/qgslayoutdesignerdialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 548d58ac53e..31fc3cde55c 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -4127,7 +4127,6 @@ void QgsLayoutDesignerDialog::createAtlasWidget() atlasWidget->setMessageBar( mMessageBar ); mAtlasDock->setWidget( atlasWidget ); - mAtlasToolbar->show(); mPanelsMenu->addAction( mAtlasDock->toggleViewAction() ); connect( atlas, &QgsLayoutAtlas::messagePushed, mStatusBar, [ = ]( const QString & message ) From 7a8e82c8637b3d75b2d084f6cb85736b31662f40 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 15 Nov 2023 17:37:05 +0100 Subject: [PATCH 36/97] Don't call dataChanged if there is no data --- src/core/symbology/qgsstylemodel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/symbology/qgsstylemodel.cpp b/src/core/symbology/qgsstylemodel.cpp index c656f0a4569..5bb16f41c3e 100644 --- a/src/core/symbology/qgsstylemodel.cpp +++ b/src/core/symbology/qgsstylemodel.cpp @@ -745,7 +745,11 @@ void QgsStyleModel::rebuildSymbolIcons() { mIconCache[ QgsStyle::SymbolEntity ].clear(); mExpressionContext.reset(); - emit dataChanged( index( 0, 0 ), index( mEntityNames[ QgsStyle::SymbolEntity ].count() - 1, 0 ), QVector() << Qt::DecorationRole ); + const int lastRow = mEntityNames[ QgsStyle::SymbolEntity ].count() - 1; + if ( lastRow >= 0 ) + { + emit dataChanged( index( 0, 0 ), index( lastRow, 0 ), QVector() << Qt::DecorationRole ); + } } void QgsStyleModel::iconGenerated( QgsStyle::StyleEntity type, const QString &name, const QIcon &icon ) From 15c47b6ef622b1ffb2390f1819d13d098da14021 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 16 Nov 2023 14:26:54 +1000 Subject: [PATCH 37/97] When writing a layer with field alias, ensure the aliases are unique per table This avoids breaking a forced unique constraint that the geopackage format has, where a duplicate alias CANNOT be used for multiple fields in the same table. It's good practice anyway, so ensure the aliases are unique regardless of format. --- src/core/qgsvectorfilewriter.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index 5130b5aeecf..cc6d1aa8915 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -582,6 +582,8 @@ void QgsVectorFileWriter::init( QString vectorFileName, case CreateOrOverwriteLayer: case AppendToLayerAddFields: { + QSet< QString > usedAlternativeNames; + for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) { QgsField attrField = fields.at( fldIdx ); @@ -857,7 +859,18 @@ void QgsVectorFileWriter::init( QString vectorFileName, OGR_Fld_SetSubType( fld.get(), ogrSubType ); #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0) - OGR_Fld_SetAlternativeName( fld.get(), mCodec->fromUnicode( attrField.alias() ).constData() ); + if ( !attrField.alias().isEmpty() ) + { + QString alternativeName = attrField.alias(); + int counter = 1; + while ( usedAlternativeNames.contains( alternativeName ) ) + { + // field alternative names MUST be unique (at least for Geopackage, but let's apply the constraint universally) + alternativeName = attrField.alias() + QStringLiteral( " (%1)" ).arg( ++counter ); + } + OGR_Fld_SetAlternativeName( fld.get(), mCodec->fromUnicode( alternativeName ).constData() ); + usedAlternativeNames.insert( alternativeName ); + } #endif #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,7,0) OGR_Fld_SetComment( fld.get(), mCodec->fromUnicode( attrField.comment() ).constData() ); From 461a3158d01f9651af434df8f8b18717d76a0d59 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 17 Nov 2023 08:40:13 +1000 Subject: [PATCH 38/97] Fix build warning on older GDAL versions --- src/core/qgsvectorfilewriter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index cc6d1aa8915..37c4a5c3b18 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -582,8 +582,9 @@ void QgsVectorFileWriter::init( QString vectorFileName, case CreateOrOverwriteLayer: case AppendToLayerAddFields: { +#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0) QSet< QString > usedAlternativeNames; - +#endif for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx ) { QgsField attrField = fields.at( fldIdx ); From e69f2bf0f878b0bd1556542d235606f083a72c12 Mon Sep 17 00:00:00 2001 From: Germap Date: Fri, 17 Nov 2023 17:36:23 -0500 Subject: [PATCH 39/97] Update python/core/additions/qgsfunction.py Avoid using 'slightly' since this could be a considerable performance improvement depending on the provider. Co-authored-by: Matthias Kuhn --- python/core/additions/qgsfunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/core/additions/qgsfunction.py b/python/core/additions/qgsfunction.py index 9fd935c18f4..f796a66b864 100644 --- a/python/core/additions/qgsfunction.py +++ b/python/core/additions/qgsfunction.py @@ -118,7 +118,7 @@ def register_function( :param args: DEPRECATED since QGIS 3.32. Use ``params_as_list`` if you want to pass parameters as a list. :param group: the expression group in which the function should be added :param usesgeometry: Defines if this expression requires the geometry. By default False. - :param referenced_columns: An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to an empty list for slightly faster evaluation. + :param referenced_columns: An array of names of fields on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Specifying a subset of fields or an empty list will result in a faster execution. :param handlesnull: Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default. :param params_as_list: If True, the function will receive the expression parameters as a list. If False, the function will receive the parameters as individual arguments. False by default. From 43511aced22efd4ed84840fe8466e428d15884dd Mon Sep 17 00:00:00 2001 From: Germap Date: Sat, 18 Nov 2023 17:53:29 -0500 Subject: [PATCH 40/97] Update qgsfunction.py, homogenize referenced_columns subsection --- python/core/additions/qgsfunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/core/additions/qgsfunction.py b/python/core/additions/qgsfunction.py index f796a66b864..4a33ecf40f3 100644 --- a/python/core/additions/qgsfunction.py +++ b/python/core/additions/qgsfunction.py @@ -185,7 +185,7 @@ def qgsfunction(args="auto", group="custom", **kwargs): * *usesgeometry* (``bool``) -- Defines if this expression requires the geometry. By default False. * *referenced_columns* (``list``) -- - An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to an empty list for slightly faster evaluation. + An array of names of fields on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Specifying a subset of fields or an empty list will result in a faster execution. * *handlesnull* (``bool``) -- Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default. * *params_as_list* (``bool``) \since QGIS 3.32 -- From 01884a13c54a677c78180bfbedf63571db910b37 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 3 Nov 2023 09:50:10 +1000 Subject: [PATCH 41/97] When saving master auth pw in keychain, use a unique key per user profile Otherwise, the auth db in one profile which is encrypted using a certain key will not be decryptable using the master pw stored in the keychain for a different user profile. --- src/core/auth/qgsauthmanager.cpp | 21 +++++++++++++++------ src/core/auth/qgsauthmanager.h | 4 +++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/core/auth/qgsauthmanager.cpp b/src/core/auth/qgsauthmanager.cpp index 1a7c2662abe..5948df209df 100644 --- a/src/core/auth/qgsauthmanager.cpp +++ b/src/core/auth/qgsauthmanager.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #include @@ -50,7 +51,6 @@ #include "keychain.h" // QGIS includes -#include "qgsapplication.h" #include "qgsauthcertutils.h" #include "qgsauthcrypto.h" #include "qgsauthmethod.h" @@ -75,7 +75,7 @@ const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manage const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[A-Z]|[0-9]){7}" ); -const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME( "QGIS-Master-Password" ); +const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME_BASE( "QGIS-Master-Password" ); const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" ); @@ -131,7 +131,7 @@ QSqlDatabase QgsAuthManager::authDatabaseConnection() const authdb = QSqlDatabase::addDatabase( QStringLiteral( "QSQLITE" ), connectionName ); authdb.setDatabaseName( authenticationDatabasePath() ); // for background threads, remove database when current thread finishes - if ( QThread::currentThread() != qApp->thread() ) + if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) { QgsDebugMsgLevel( QStringLiteral( "Scheduled auth db remove on thread close" ), 2 ); @@ -3207,7 +3207,7 @@ bool QgsAuthManager::passwordHelperDelete() QgsSettings settings; job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); - job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + job.setKey( authPasswordHelperKeyName() ); QEventLoop loop; connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit ); job.start(); @@ -3239,7 +3239,7 @@ QString QgsAuthManager::passwordHelperRead() QgsSettings settings; job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); - job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + job.setKey( authPasswordHelperKeyName() ); QEventLoop loop; connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit ); job.start(); @@ -3281,7 +3281,7 @@ bool QgsAuthManager::passwordHelperWrite( const QString &password ) QgsSettings settings; job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() ); job.setAutoDelete( false ); - job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME ); + job.setKey( authPasswordHelperKeyName() ); job.setTextData( password ); QEventLoop loop; connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit ); @@ -3956,3 +3956,12 @@ void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source, QPair( source, cert ) ); } } + +QString QgsAuthManager::authPasswordHelperKeyName() const +{ + const QFileInfo info( mAuthDbPath ); + const QString dbProfilePath = info.dir().dirName(); + + // if not running from the default profile, ensure that a different key is used + return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( QLatin1String( "default" ), Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath ); +} diff --git a/src/core/auth/qgsauthmanager.h b/src/core/auth/qgsauthmanager.h index c64b16d4fed..6ab376d35ba 100644 --- a/src/core/auth/qgsauthmanager.h +++ b/src/core/auth/qgsauthmanager.h @@ -874,6 +874,8 @@ class CORE_EXPORT QgsAuthManager : public QObject const QString authDbTrustTable() const { return AUTH_TRUST_TABLE; } + QString authPasswordHelperKeyName() const; + static QgsAuthManager *sInstance; static const QString AUTH_CONFIG_TABLE; static const QString AUTH_PASS_TABLE; @@ -939,7 +941,7 @@ class CORE_EXPORT QgsAuthManager : public QObject bool mPasswordHelperFailedInit = false; //! Master password name in the wallets - static const QLatin1String AUTH_PASSWORD_HELPER_KEY_NAME; + static const QLatin1String AUTH_PASSWORD_HELPER_KEY_NAME_BASE; //! password helper folder in the wallets static const QLatin1String AUTH_PASSWORD_HELPER_FOLDER_NAME; From 373f8691bd73f9e60e8ef23ef2d1c40957e9aa8b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 21 Nov 2023 10:16:17 +1000 Subject: [PATCH 42/97] When replacing expressions in eg layout labels, don't show null values as "0" --- src/core/expression/qgsexpression.cpp | 9 +++++++-- tests/src/core/testqgsexpression.cpp | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/core/expression/qgsexpression.cpp b/src/core/expression/qgsexpression.cpp index f10481142a4..d39785cf268 100644 --- a/src/core/expression/qgsexpression.cpp +++ b/src/core/expression/qgsexpression.cpp @@ -502,8 +502,13 @@ QString QgsExpression::replaceExpressionText( const QString &action, const QgsEx continue; } - QgsDebugMsgLevel( "Expression result is: " + result.toString(), 3 ); - expr_action += action.mid( start, pos - start ) + result.toString(); + QString resultString; + if ( !QgsVariantUtils::isNull( result ) ) + resultString = result.toString(); + + QgsDebugMsgLevel( "Expression result is: " + resultString, 3 ); + + expr_action += action.mid( start, pos - start ) + resultString; } #if QT_VERSION < QT_VERSION_CHECK(5, 15, 2) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 5c9481cc731..dcec3dedd3d 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -5325,6 +5325,7 @@ class TestQgsExpression: public QObject QTest::newRow( "complex2" ) << "some [% 'my text]' %] text" << "some my text] text"; QTest::newRow( "newline 1" ) << "some \n [% 1 + 2 %] \n text" << "some \n 3 \n text"; QTest::newRow( "newline 2" ) << "some [% \n 1 \n + \n 2 %] \n text" << "some 3 \n text"; + QTest::newRow( "field values" ) << "[% \"string_field\" %] - [% \"non_null_int\" %] - [% \"null_int\" %]" << "string value - 5 - "; } void testReplaceExpressionText() @@ -5333,6 +5334,18 @@ class TestQgsExpression: public QObject QFETCH( QString, expected ); QgsExpressionContext context; + + QgsFields fields; + fields.append( QgsField( "string_field", QVariant::String ) ); + fields.append( QgsField( "non_null_int", QVariant::Int ) ); + fields.append( QgsField( "null_int", QVariant::Int ) ); + + QgsFeature feature( fields ); + feature.setAttributes( QgsAttributes( { QVariant( QStringLiteral( "string value" ) ), QVariant( 5 ), QVariant( QVariant::Int ) } ) ); + + context.setFeature( feature ); + context.setFields( fields ); + QCOMPARE( QgsExpression::replaceExpressionText( input, &context ), expected ); } From f45f6988418dbb39e94e12a679c9521acc849019 Mon Sep 17 00:00:00 2001 From: DelazJ Date: Mon, 20 Nov 2023 11:19:59 +0100 Subject: [PATCH 43/97] Use @geometry in vectorTile filtering expression --- src/core/vectortile/qgsvectortilebasicrenderer.cpp | 6 +++--- src/gui/vectortile/qgsvectortilebasiclabelingwidget.cpp | 6 +++--- src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/vectortile/qgsvectortilebasicrenderer.cpp b/src/core/vectortile/qgsvectortilebasicrenderer.cpp index b5060a7b4b3..05e2890a96f 100644 --- a/src/core/vectortile/qgsvectortilebasicrenderer.cpp +++ b/src/core/vectortile/qgsvectortilebasicrenderer.cpp @@ -476,15 +476,15 @@ QList QgsVectorTileBasicRenderer::simpleStyle( QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol( QgsSymbolLayerList() << markerSymbolLayer ); QgsVectorTileBasicRendererStyle st1( QStringLiteral( "Polygons" ), QString(), Qgis::GeometryType::Polygon ); - st1.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Polygon'" ) ); + st1.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Polygon'" ) ); st1.setSymbol( fillSymbol ); QgsVectorTileBasicRendererStyle st2( QStringLiteral( "Lines" ), QString(), Qgis::GeometryType::Line ); - st2.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Line'" ) ); + st2.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Line'" ) ); st2.setSymbol( lineSymbol ); QgsVectorTileBasicRendererStyle st3( QStringLiteral( "Points" ), QString(), Qgis::GeometryType::Point ); - st3.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Point'" ) ); + st3.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Point'" ) ); st3.setSymbol( markerSymbol ); QList lst; diff --git a/src/gui/vectortile/qgsvectortilebasiclabelingwidget.cpp b/src/gui/vectortile/qgsvectortilebasiclabelingwidget.cpp index e90375e339b..1145b36a6f8 100644 --- a/src/gui/vectortile/qgsvectortilebasiclabelingwidget.cpp +++ b/src/gui/vectortile/qgsvectortilebasiclabelingwidget.cpp @@ -441,13 +441,13 @@ void QgsVectorTileBasicLabelingWidget::addStyle( Qgis::GeometryType geomType ) switch ( geomType ) { case Qgis::GeometryType::Point: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Point'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Point'" ) ); break; case Qgis::GeometryType::Line: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Line'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Line'" ) ); break; case Qgis::GeometryType::Polygon: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Polygon'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Polygon'" ) ); break; case Qgis::GeometryType::Unknown: case Qgis::GeometryType::Null: diff --git a/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp b/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp index 3b49c006122..063ed98fa56 100644 --- a/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp +++ b/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp @@ -442,13 +442,13 @@ void QgsVectorTileBasicRendererWidget::addStyle( Qgis::GeometryType geomType ) switch ( geomType ) { case Qgis::GeometryType::Point: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Point'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Point'" ) ); break; case Qgis::GeometryType::Line: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Line'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Line'" ) ); break; case Qgis::GeometryType::Polygon: - style.setFilterExpression( QStringLiteral( "geometry_type($geometry)='Polygon'" ) ); + style.setFilterExpression( QStringLiteral( "geometry_type(@geometry)='Polygon'" ) ); break; case Qgis::GeometryType::Unknown: case Qgis::GeometryType::Null: From e47aaf5555080a7924dbad6350e378890752b350 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Fri, 10 Nov 2023 14:15:49 +0100 Subject: [PATCH 44/97] Show render times in server profile and clear content after each request --- src/server/qgsserver.cpp | 6 +++++- src/server/services/wms/qgswmsrenderer.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/server/qgsserver.cpp b/src/server/qgsserver.cpp index 245680757ba..b3d2816b4c2 100644 --- a/src/server/qgsserver.cpp +++ b/src/server/qgsserver.cpp @@ -593,9 +593,13 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res } - // Clear the profiler server section after each request + // Clear the profiler content after each request + QgsApplication::profiler()->clear( QStringLiteral( "startup" ) ); + QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) ); + QgsApplication::profiler()->clear( QStringLiteral( "rendering" ) ); QgsApplication::profiler()->clear( QStringLiteral( "server" ) ); + } diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 6979c9369b9..bebda733832 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -1421,6 +1421,12 @@ namespace QgsWms mapSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, mContext.renderMapTiles() ); + // enable profiling + if ( mContext.settings().logProfile() ) + { + mapSettings.setFlag( Qgis::MapSettingsFlag::RecordProfile ); + } + // set selection color mapSettings.setSelectionColor( mProject->selectionColor() ); From 44323f02cacc9fc1385b0e183205c62d4da70c0e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 21 Nov 2023 12:44:20 +1000 Subject: [PATCH 45/97] Disable QWT_POLAR on msys2 workflow --- .github/workflows/mingw-w64-msys2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mingw-w64-msys2.yml b/.github/workflows/mingw-w64-msys2.yml index 625eed542f3..542e3259bba 100644 --- a/.github/workflows/mingw-w64-msys2.yml +++ b/.github/workflows/mingw-w64-msys2.yml @@ -83,7 +83,7 @@ jobs: -DWITH_3D=ON \ -DWITH_PDAL=OFF \ -DWITH_CUSTOM_WIDGETS=ON \ - -DWITH_QWTPOLAR=ON \ + -DWITH_QWTPOLAR=OFF \ -DWITH_BINDINGS=OFF \ -DWITH_GRASS=OFF \ -DWITH_DRACO=OFF \ From 3b2ec15336ce7b9f30b1ed363ba08bcdb6119339 Mon Sep 17 00:00:00 2001 From: joonalai <33314057+Joonalai@users.noreply.github.com> Date: Tue, 21 Nov 2023 10:42:46 +0200 Subject: [PATCH 46/97] Fix pgraster statistics collection on Windows --- src/providers/postgres/raster/qgspostgresrasterprovider.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/postgres/raster/qgspostgresrasterprovider.h b/src/providers/postgres/raster/qgspostgresrasterprovider.h index 48bcd48f137..0f761a06baf 100644 --- a/src/providers/postgres/raster/qgspostgresrasterprovider.h +++ b/src/providers/postgres/raster/qgspostgresrasterprovider.h @@ -126,9 +126,9 @@ class QgsPostgresRasterProvider : public QgsRasterDataProvider //! Has spatial index bool mHasSpatialIndex = false; //! Raster size x - long mWidth = 0; + qlonglong mWidth = 0; //! Raster size y - long mHeight = 0; + qlonglong mHeight = 0; //! Raster tile size x int mTileWidth = 0; //! Raster tile size y From e494d1a335e0ac7ddef227a49437f11958eda5cd Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 21 Nov 2023 20:23:29 +1000 Subject: [PATCH 47/97] Generate markdown report from Python tests too --- python/testing/__init__.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/python/testing/__init__.py b/python/testing/__init__.py index ba80d16405f..028f87ffbb6 100644 --- a/python/testing/__init__.py +++ b/python/testing/__init__.py @@ -73,11 +73,14 @@ class QgisTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls.report = '' + cls.markdown_report = '' @classmethod def tearDownClass(cls): if cls.report: cls.write_local_html_report(cls.report) + if cls.markdown_report: + cls.write_local_markdown_report(cls.markdown_report) @classmethod def control_path_prefix(cls) -> Optional[str]: @@ -127,6 +130,24 @@ class QgisTestCase(unittest.TestCase): if not QgisTestCase.is_ci_run(): QDesktopServices.openUrl(QUrl.fromLocalFile(report_file)) + @classmethod + def write_local_markdown_report(cls, report: str): + report_dir = QgsRenderChecker.testReportDir() + if not report_dir.exists(): + QDir().mkpath(report_dir.path()) + + report_file = report_dir.filePath('summary.md') + + # only append to existing reports if running under CI + if cls.is_ci_run() or \ + os.environ.get("QGIS_APPEND_TO_TEST_REPORT") == 'true': + file_mode = 'ta' + else: + file_mode = 'wt' + + with open(report_file, file_mode, encoding='utf-8') as f: + f.write(report) + @classmethod def image_check(cls, name: str, @@ -156,6 +177,11 @@ class QgisTestCase(unittest.TestCase): cls.report += f"

Render {name}

\n" cls.report += checker.report() + markdown = checker.markdownReport() + if markdown: + cls.markdown_report += "## {}\n\n".format(name) + cls.markdown_report += markdown + return result @classmethod @@ -178,6 +204,11 @@ class QgisTestCase(unittest.TestCase): cls.report += f"

Render {name}

\n" cls.report += checker.report() + markdown = checker.markdownReport() + if markdown: + cls.markdown_report += "## {}\n\n".format(name) + cls.markdown_report += markdown + return result @classmethod @@ -193,6 +224,12 @@ class QgisTestCase(unittest.TestCase): if not result: cls.report += f"

Render {name}

\n" cls.report += checker.report() + + markdown = checker.markdownReport() + if markdown: + cls.markdown_report += "## {}\n\n".format(name) + cls.markdown_report += markdown + return result def assertLayersEqual(self, layer_expected, layer_result, **kwargs): From 4ea302113b8e71385b7263d34c53113bf56662a4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 21 Nov 2023 20:23:52 +1000 Subject: [PATCH 48/97] Ensure selective masking test gracefully cleans up Otherwise it leaves files in the test report folder which subsequent github action steps cannot access, breaking the workflows --- tests/src/python/test_selective_masking.py | 109 +++++++++++---------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/tests/src/python/test_selective_masking.py b/tests/src/python/test_selective_masking.py index c90c5724ddb..4e9a689a0b7 100644 --- a/tests/src/python/test_selective_masking.py +++ b/tests/src/python/test_selective_masking.py @@ -14,6 +14,7 @@ __date__ = '28/06/2019' import os import subprocess +import tempfile import qgis # NOQA from qgis.PyQt.QtCore import QRectF, QSize, Qt, QUuid @@ -198,56 +199,57 @@ class TestSelectiveMasking(QgisTestCase): Generate a PDF layout export and control the output matches expected_filename """ - # generate vector file - layout = QgsLayout(QgsProject.instance()) - page = QgsLayoutItemPage(layout) - page.setPageSize(QgsLayoutSize(50, 33)) - layout.pageCollection().addPage(page) + with tempfile.TemporaryDirectory() as temp_dir: + # generate vector file + layout = QgsLayout(QgsProject.instance()) + page = QgsLayoutItemPage(layout) + page.setPageSize(QgsLayoutSize(50, 33)) + layout.pageCollection().addPage(page) - map = QgsLayoutItemMap(layout) - map.attemptSetSceneRect(QRectF(1, 1, 48, 32)) - map.setFrameEnabled(True) - layout.addLayoutItem(map) - map.setExtent(extent if extent is not None else self.lines_layer.extent()) - map.setLayers(layers if layers is not None else [self.points_layer, self.lines_layer, self.polys_layer]) + map = QgsLayoutItemMap(layout) + map.attemptSetSceneRect(QRectF(1, 1, 48, 32)) + map.setFrameEnabled(True) + layout.addLayoutItem(map) + map.setExtent(extent if extent is not None else self.lines_layer.extent()) + map.setLayers(layers if layers is not None else [self.points_layer, self.lines_layer, self.polys_layer]) - settings = QgsLayoutExporter.PdfExportSettings() + settings = QgsLayoutExporter.PdfExportSettings() - if dpiTarget is not None: - settings.dpi = dpiTarget + if dpiTarget is not None: + settings.dpi = dpiTarget - exporter = QgsLayoutExporter(layout) - result_filename = getTempfilePath('pdf') - exporter.exportToPdf(result_filename, settings) - self.assertTrue(os.path.exists(result_filename)) + exporter = QgsLayoutExporter(layout) + result_filename = os.path.join(temp_dir, "export.pdf") + exporter.exportToPdf(result_filename, settings) + self.assertTrue(os.path.exists(result_filename)) - # Generate a readable PDF file so we count raster in it - result_txt = getTempfilePath("txt") - subprocess.run(["qpdf", "--qdf", "--object-streams=disable", result_filename, result_txt]) - self.assertTrue(os.path.exists(result_txt)) + # Generate a readable PDF file so we count raster in it + result_txt = os.path.join(temp_dir, "export.txt") + subprocess.run(["qpdf", "--qdf", "--object-streams=disable", result_filename, result_txt]) + self.assertTrue(os.path.exists(result_txt)) - result = open(result_txt, 'rb') - result_lines = [l.decode('iso-8859-1') for l in result.readlines()] - result.close() - nb_raster = len([l for l in result_lines if "/Subtype /Image" in l]) - self.assertEqual(nb_raster, expected_nb_raster) + result = open(result_txt, 'rb') + result_lines = [l.decode('iso-8859-1') for l in result.readlines()] + result.close() + nb_raster = len([l for l in result_lines if "/Subtype /Image" in l]) + self.assertEqual(nb_raster, expected_nb_raster) - # Generate an image from pdf to compare with expected control image - # keep PDF DPI resolution (300) - image_result_filename = getTempfilePath("png") - subprocess.run(["pdftoppm", result_filename, - os.path.splitext(image_result_filename)[0], - "-png", "-r", "300", "-singlefile"]) + # Generate an image from pdf to compare with expected control image + # keep PDF DPI resolution (300) + image_result_filename = os.path.join(temp_dir, "export.png") + subprocess.run(["pdftoppm", result_filename, + os.path.splitext(image_result_filename)[0], + "-png", "-r", "300", "-singlefile"]) - rendered_image = QImage(image_result_filename) - res = self.image_check(control_name, - control_name, - rendered_image, - control_name, - allowed_mismatch=0, - color_tolerance=0) + rendered_image = QImage(image_result_filename) + res = self.image_check(control_name, + control_name, + rendered_image, + control_name, + allowed_mismatch=0, + color_tolerance=0) - self.assertTrue(res) + self.assertTrue(res) def test_save_restore_references(self): """ @@ -728,21 +730,22 @@ class TestSelectiveMasking(QgisTestCase): QgsUnitTypes.RenderPixels, QSize(64, 64)).pixmap(QSize(64, 64)).save(tmp)) ]: - tmp = getTempfilePath('png') - render_function() + with tempfile.TemporaryDirectory() as temp_dir: + tmp = os.path.join(temp_dir, "render.png") + render_function() - rendered_image = QImage(tmp) + rendered_image = QImage(tmp) - res = self.image_check( - control_name, - control_name, - rendered_image, - control_name, - allowed_mismatch=90, - color_tolerance=0 - ) + res = self.image_check( + control_name, + control_name, + rendered_image, + control_name, + allowed_mismatch=90, + color_tolerance=0 + ) - self.assertTrue(res) + self.assertTrue(res) def test_mask_with_effect(self): p = QgsMarkerSymbol.createSimple({'color': '#fdbf6f', 'size': "7"}) From 59e5d3caac47ad06b3530c8d1b5f4744393ae5c4 Mon Sep 17 00:00:00 2001 From: Tarot Osuji Date: Wed, 22 Nov 2023 06:31:56 +0900 Subject: [PATCH 49/97] [OGR provider] Fix shapefile extent not shrinking (#55324) Fixes #23661, #13985 --- src/core/providers/ogr/qgsogrprovider.cpp | 5 +++- tests/src/python/test_provider_shapefile.py | 29 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index 5cf8950f4ec..f662048b643 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -1128,7 +1128,10 @@ QgsRectangle QgsOgrProvider::extent() const // get the extent_ (envelope) of the layer QgsDebugMsgLevel( QStringLiteral( "Starting get extent" ), 3 ); - if ( mForceRecomputeExtent && mValid && mGDALDriverName == QLatin1String( "GPKG" ) && mOgrOrigLayer ) + if ( mForceRecomputeExtent && mValid && mWriteAccess && + ( mGDALDriverName == QLatin1String( "GPKG" ) || + mGDALDriverName == QLatin1String( "ESRI Shapefile" ) ) && + mOgrOrigLayer ) { // works with unquoted layerName QByteArray sql = QByteArray( "RECOMPUTE EXTENT ON " ) + mOgrOrigLayer->name(); diff --git a/tests/src/python/test_provider_shapefile.py b/tests/src/python/test_provider_shapefile.py index 5cbca8a6801..381888739b0 100644 --- a/tests/src/python/test_provider_shapefile.py +++ b/tests/src/python/test_provider_shapefile.py @@ -1142,6 +1142,35 @@ class TestPyQgsShapefileProvider(QgisTestCase, ProviderTestCase): f = next(vl.getFeatures()) self.assertEqual(f['name'], 'Apple') + def testRecomputeExtent(self): + """Test that extents are recomputed correctly after update""" + + tmpdir = tempfile.mkdtemp() + self.dirs_to_cleanup.append(tmpdir) + srcpath = os.path.join(TEST_DATA_DIR, 'provider') + for file in glob.glob(os.path.join(srcpath, 'shapefile.*')): + shutil.copy(os.path.join(srcpath, file), tmpdir) + datasource = os.path.join(tmpdir, 'shapefile.shp') + + vl = QgsVectorLayer(f'{datasource}|layerid=0', 'test', 'ogr') + extent = vl.extent() + vl.startEditing() + for fet in vl.getFeatures(): + vl.translateFeature(fet.id(), 1, -1) + vl.commitChanges() + updated_extent = vl.extent() + self.assertEqual(updated_extent.xMaximum(), extent.xMaximum() + 1) + self.assertEqual(updated_extent.xMinimum(), extent.xMinimum() + 1) + self.assertEqual(updated_extent.yMaximum(), extent.yMaximum() - 1) + self.assertEqual(updated_extent.yMinimum(), extent.yMinimum() - 1) + + # close file and reopen, then recheck to confirm that changes were saved to file + del vl + vl = None + vl = QgsVectorLayer(f'{datasource}|layerid=0', 'test', 'ogr') + reopened_extent = vl.extent() + self.assertEqual(reopened_extent, updated_extent) + if __name__ == '__main__': unittest.main() From a2675f255ac67c71b0937efbb545e6b5cf675eea Mon Sep 17 00:00:00 2001 From: Yoann Quenach de Quivillic Date: Tue, 21 Nov 2023 22:41:24 +0100 Subject: [PATCH 50/97] Fix decoration size on scaled screens --- src/app/decorations/qgsdecorationcopyright.cpp | 4 ++-- src/app/decorations/qgsdecorationimage.cpp | 4 ++-- src/app/decorations/qgsdecorationnortharrow.cpp | 4 ++-- src/app/decorations/qgsdecorationoverlay.cpp | 3 ++- src/app/decorations/qgsdecorationscalebar.cpp | 4 ++-- src/app/decorations/qgsdecorationtitle.cpp | 4 ++-- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/app/decorations/qgsdecorationcopyright.cpp b/src/app/decorations/qgsdecorationcopyright.cpp index 937157ef013..27494560f53 100644 --- a/src/app/decorations/qgsdecorationcopyright.cpp +++ b/src/app/decorations/qgsdecorationcopyright.cpp @@ -125,8 +125,8 @@ void QgsDecorationCopyright::render( const QgsMapSettings &mapSettings, QgsRende const double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, Qgis::TextLayoutMode::Point ); QPaintDevice *device = context.painter()->device(); - const int deviceHeight = device->height() / device->devicePixelRatioF(); - const int deviceWidth = device->width() / device->devicePixelRatioF(); + const float deviceHeight = static_cast( device->height() ) / context.devicePixelRatio(); + const float deviceWidth = static_cast( device->width() ) / context.devicePixelRatio(); float xOffset( 0 ), yOffset( 0 ); diff --git a/src/app/decorations/qgsdecorationimage.cpp b/src/app/decorations/qgsdecorationimage.cpp index 2757fadc427..5f13d79c9d8 100644 --- a/src/app/decorations/qgsdecorationimage.cpp +++ b/src/app/decorations/qgsdecorationimage.cpp @@ -201,8 +201,8 @@ void QgsDecorationImage::render( const QgsMapSettings &mapSettings, QgsRenderCon // need width/height of paint device QPaintDevice *device = context.painter()->device(); - const int deviceHeight = device->height() / device->devicePixelRatioF(); - const int deviceWidth = device->width() / device->devicePixelRatioF(); + const float deviceHeight = static_cast( device->height() ) / context.devicePixelRatio(); + const float deviceWidth = static_cast( device->width() ) / context.devicePixelRatio(); // Set margin according to selected units int xOffset = 0; diff --git a/src/app/decorations/qgsdecorationnortharrow.cpp b/src/app/decorations/qgsdecorationnortharrow.cpp index 8e10a01f3bf..b5c3b7d3fa3 100644 --- a/src/app/decorations/qgsdecorationnortharrow.cpp +++ b/src/app/decorations/qgsdecorationnortharrow.cpp @@ -171,8 +171,8 @@ void QgsDecorationNorthArrow::render( const QgsMapSettings &mapSettings, QgsRend ) - centerYDouble ); // need width/height of paint device QPaintDevice *device = context.painter()->device(); - const int deviceHeight = device->height() / device->devicePixelRatioF(); - const int deviceWidth = device->width() / device->devicePixelRatioF(); + const float deviceHeight = static_cast( device->height() ) / context.devicePixelRatio(); + const float deviceWidth = static_cast( device->width() ) / context.devicePixelRatio(); // Set margin according to selected units int xOffset = 0; diff --git a/src/app/decorations/qgsdecorationoverlay.cpp b/src/app/decorations/qgsdecorationoverlay.cpp index 39abb0fefbe..32794cb4c38 100644 --- a/src/app/decorations/qgsdecorationoverlay.cpp +++ b/src/app/decorations/qgsdecorationoverlay.cpp @@ -47,10 +47,11 @@ void QgsDecorationOverlay::paintEvent( QPaintEvent * ) QgsRenderContext context = QgsRenderContext::fromMapSettings( QgisApp::instance()->mapCanvas()->mapSettings() ); context.setPainter( &p ); + context.setDevicePixelRatio( 1 ); for ( QgsMapDecoration *item : decorations ) { - // Do not render decorations with fixed map positionn they are rendered directly on the map canvas + // Do not render decorations with fixed map position they are rendered directly on the map canvas if ( item->hasFixedMapPosition() ) continue; item->render( QgisApp::instance()->mapCanvas()->mapSettings(), context ); diff --git a/src/app/decorations/qgsdecorationscalebar.cpp b/src/app/decorations/qgsdecorationscalebar.cpp index 2544e032757..237854a1ba0 100644 --- a/src/app/decorations/qgsdecorationscalebar.cpp +++ b/src/app/decorations/qgsdecorationscalebar.cpp @@ -240,8 +240,8 @@ void QgsDecorationScaleBar::render( const QgsMapSettings &mapSettings, QgsRender //Get canvas dimensions QPaintDevice *device = context.painter()->device(); - const int deviceHeight = device->height() / device->devicePixelRatioF(); - const int deviceWidth = device->width() / device->devicePixelRatioF(); + const float deviceHeight = static_cast( device->height() ) / context.devicePixelRatio(); + const float deviceWidth = static_cast( device->width() ) / context.devicePixelRatio(); const Qgis::DistanceUnit preferredUnits = QgsProject::instance()->distanceUnits(); Qgis::DistanceUnit scaleBarUnits = mapSettings.mapUnits(); diff --git a/src/app/decorations/qgsdecorationtitle.cpp b/src/app/decorations/qgsdecorationtitle.cpp index 77cc2391409..fd8a83a8671 100644 --- a/src/app/decorations/qgsdecorationtitle.cpp +++ b/src/app/decorations/qgsdecorationtitle.cpp @@ -114,8 +114,8 @@ void QgsDecorationTitle::render( const QgsMapSettings &mapSettings, QgsRenderCon const double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, Qgis::TextLayoutMode::Point ); QPaintDevice *device = context.painter()->device(); - const int deviceHeight = device->height() / device->devicePixelRatioF(); - const int deviceWidth = device->width() / device->devicePixelRatioF(); + const float deviceHeight = static_cast( device->height() ) / context.devicePixelRatio(); + const float deviceWidth = static_cast( device->width() ) / context.devicePixelRatio(); float xOffset( 0 ), yOffset( 0 ); From dcaf766206721a7e1919992b5bb80e4db0a062c6 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Sat, 18 Nov 2023 11:53:04 +0100 Subject: [PATCH 51/97] fix doxygen warning --- src/server/services/wms/qgswmsrenderer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index a038506bf8b..e47ab7b51d9 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -112,7 +112,7 @@ namespace QgsWms /** * Returns the map legend as a JSON object (or NULLPTR in case of error). The * caller takes ownership of the image object. - * \param legendNodeModel The legend node to use for building the legend + * \param legendNode The legend node to use for building the legend * \param jsonRenderFlags The JSON export flags * \returns the legend as a JSON object * \since QGIS 3.36 From 0de7e680952ee53b544582c27a4fca0f32eebf4e Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Wed, 22 Nov 2023 08:21:01 +0100 Subject: [PATCH 52/97] translation string fix --- src/core/geometry/qgsgeos.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index 9cc7dde8027..02da31501f1 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -2049,7 +2049,7 @@ Qgis::CoverageValidityResult QgsGeos::validateCoverage( double gapWidth, std::un ( void )gapWidth; ( void )invalidEdges; ( void )errorMsg; - throw QgsNotSupportedException( QObject::tr( "Validiting coverages requires a QGIS build based on GEOS 3.12 or later" ) ); + throw QgsNotSupportedException( QObject::tr( "Validating coverages requires a QGIS build based on GEOS 3.12 or later" ) ); #else if ( !mGeos ) { From 4347539838f1e37e5a7c463ea7f1056c66c25492 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 20 Nov 2023 14:29:22 +0100 Subject: [PATCH 53/97] qgswfsparameters: Ensure to get the default value on wrong version `QgsWfsParameters::versionAsNumber` creates a `QgsProjectVersion` from the version number defined in the request `VERSION` parameter. If the version is not defined, the version 1.1.0 is used. However, if the version number defined in the `VERSION` parameter is not handled by QGIS (for example 2.0.0), the `QgsProjectVersion` is nver populated which corresponds to use version 0.0.0 instead of 1.1.0, the default value. This issue is fixed by always using version number 1.1.0 if the list of handled versions (`mVersions`) does not contain the requested version. --- src/server/services/wfs/qgswfsparameters.cpp | 6 +++--- tests/src/python/test_qgsserver_wfs.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/server/services/wfs/qgswfsparameters.cpp b/src/server/services/wfs/qgswfsparameters.cpp index dc37c42f3ac..c6ce4b77e25 100644 --- a/src/server/services/wfs/qgswfsparameters.cpp +++ b/src/server/services/wfs/qgswfsparameters.cpp @@ -368,10 +368,10 @@ namespace QgsWfs const QString vStr = version(); QgsProjectVersion version; - if ( vStr.isEmpty() ) - version = QgsProjectVersion( 1, 1, 0 ); // default value - else if ( mVersions.contains( QgsProjectVersion( vStr ) ) ) + if ( mVersions.contains( QgsProjectVersion( vStr ) ) ) version = QgsProjectVersion( vStr ); + else + version = QgsProjectVersion( 1, 1, 0 ); // default value return version; } diff --git a/tests/src/python/test_qgsserver_wfs.py b/tests/src/python/test_qgsserver_wfs.py index 42e897ec86e..55db5d08c9e 100644 --- a/tests/src/python/test_qgsserver_wfs.py +++ b/tests/src/python/test_qgsserver_wfs.py @@ -560,6 +560,16 @@ class TestQgsServerWFS(QgsServerTestBase): self.wfs_request_compare( "GetFeature", '1.1.0', "TYPENAME=testlayer&FEATUREID=testlayer.0", 'wfs_getFeature_1_1_0_featureid_0_1_1_0_srsname') + def test_get_feature_wrong_version_nomber(self): + """Test GetFeature with a wrong version number. + This should fall back to the default version: 1.1.0 + """ + self.wfs_request_compare( + "GetFeature", '2.0.0', "SRSNAME=urn:ogc:def:crs:EPSG::4326&TYPENAME=testlayer&FEATUREID=testlayer.0", 'wfs_getFeature_1_1_0_featureid_0_1_1_0') + + self.wfs_request_compare( + "GetFeature", '2.0.0', "TYPENAME=testlayer&FEATUREID=testlayer.0", 'wfs_getFeature_1_1_0_featureid_0_1_1_0_srsname') + def test_getFeature_EXP_FILTER_regression_20927(self): """Test expressions with EXP_FILTER""" From db71cf5b323b2bc84b7b62fef8f0b179474ef4d8 Mon Sep 17 00:00:00 2001 From: Jacky Volpes Date: Wed, 22 Nov 2023 15:31:02 +0100 Subject: [PATCH 54/97] Fix Oracle syntax when estimating feature count on views Missing quoted identifiers --- src/providers/oracle/qgsoracleprovider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp index bc203f6b0ab..1a07a94214a 100644 --- a/src/providers/oracle/qgsoracleprovider.cpp +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -2650,7 +2650,7 @@ long long QgsOracleProvider::featureCount() const // Else, to estimate feature count, if it is a view or there is a where clause we use the explain plan else if ( !mSqlWhereClause.isEmpty() || relkind() == QgsOracleProvider::View ) { - sql = QString( "explain plan for select 1 from %1" ).arg( mTableName ); + sql = QString( "explain plan for select 1 from %1.%2" ).arg( quotedIdentifier( mOwnerName ) ).arg( quotedIdentifier( mTableName ) ); if ( !mSqlWhereClause.isEmpty() ) sql += " WHERE " + mSqlWhereClause; if ( LoggedExecStatic( qry, From fdc4ee7994ef8f142426e425a1666c8c911b7389 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Mon, 20 Nov 2023 11:57:32 +0100 Subject: [PATCH 55/97] Add "Render as a surface" option to 2D point cloud renderers When enabled, we will do Delaunay triangulation of the points in the current map view, and then render triangles instead of points. For each point we keep its color for interpolation in the triangle. Global map shading is also supported with the new option, when enabled, we also keep elevation of each point, and then rasterize triangles with interpolated elevations to the provided elevation map. When "Render as a surface" is enabled, drawing order is ignored, because points do not obscure other points anymore - all input points participate in the triangulation. There is also an option to filter large triangles (given by the maximum length of edge of a triangle), which is useful when one wants to see the actual holes in the data. Compared to the implementation for 3D rendering, the 2D rendering only provides filtering based on horizontal length of triangles. Filtering based on triangle size on the vertical axis seems irrelevant because the 2D view is always from the top. --- .../pointcloud/qgspointcloudrenderer.sip.in | 112 +++++++++++++ .../auto_generated/qgselevationmap.sip.in | 1 + src/core/CMakeLists.txt | 1 + src/core/mesh/qgsmeshlayerutils.cpp | 7 + src/core/mesh/qgsmeshlayerutils.h | 18 +++ .../qgspointcloudattributebyramprenderer.cpp | 22 ++- .../qgspointcloudclassifiedrenderer.cpp | 21 ++- .../pointcloud/qgspointcloudlayerrenderer.cpp | 148 +++++++++++++++++- .../pointcloud/qgspointcloudlayerrenderer.h | 1 + src/core/pointcloud/qgspointcloudrenderer.cpp | 15 ++ src/core/pointcloud/qgspointcloudrenderer.h | 130 +++++++++++++++ .../pointcloud/qgspointcloudrgbrenderer.cpp | 21 ++- src/core/qgselevationmap.h | 9 ++ .../qgspointcloudrendererpropertieswidget.cpp | 23 +++ .../qgspointcloudrendererpropsdialogbase.ui | 52 +++++- tests/src/core/testqgsmaprendererjob.cpp | 14 ++ ...st_qgspointcloudattributebyramprenderer.py | 31 ++++ .../test_qgspointcloudclassifiedrenderer.py | 24 +++ .../python/test_qgspointcloudrgbrenderer.py | 44 ++++++ ...d_render_shading_point_cloud_triangles.png | Bin 0 -> 285336 bytes .../expected_classified_triangles.png | Bin 0 -> 471523 bytes .../expected_ramp_triangles.png | Bin 0 -> 471523 bytes .../expected_rgb_triangles.png | Bin 0 -> 471523 bytes .../expected_rgb_triangles_filter.png | Bin 0 -> 471523 bytes 24 files changed, 680 insertions(+), 14 deletions(-) create mode 100644 tests/testdata/control_images/map_renderer/expected_render_shading_point_cloud_triangles/expected_render_shading_point_cloud_triangles.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_classified_triangles/expected_classified_triangles.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_ramp_triangles/expected_ramp_triangles.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_rgb_triangles/expected_rgb_triangles.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_rgb_triangles_filter/expected_rgb_triangles_filter.png diff --git a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in index 051e1cecccd..1fb9bb7c081 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in @@ -157,6 +157,7 @@ Returns the feedback object used to cancel rendering %End + private: QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh ); }; @@ -436,6 +437,110 @@ Sets the ``unit`` for the maximum screen error allowed when rendering the point .. seealso:: :py:func:`setMaximumScreenError` .. seealso:: :py:func:`maximumScreenErrorUnit` +%End + + bool renderAsTriangles() const; +%Docstring +Returns whether points are triangulated to render solid surface + +.. versionadded:: 3.36 +%End + + void setRenderAsTriangles( bool asTriangles ); +%Docstring +Sets whether points are triangulated to render solid surface + +.. versionadded:: 3.36 +%End + + bool horizontalTriangleFilter() const; +%Docstring +Returns whether large triangles will get rendered. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` +is enabled. When the triangle filtering is enabled, triangles where at least one side is +horizontally longer than the threshold in :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterThreshold` do not get rendered. + +.. seealso:: :py:func:`horizontalTriangleFilterThreshold` + +.. seealso:: :py:func:`horizontalTriangleFilterUnit` + +.. seealso:: :py:func:`setHorizontalTriangleFilter` + +.. versionadded:: 3.36 +%End + + void setHorizontalTriangleFilter( bool enabled ); +%Docstring +Sets whether large triangles will get rendered. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` +is enabled. When the triangle filtering is enabled, triangles where at least one side is +horizontally longer than the threshold in :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterThreshold` do not get rendered. + +.. seealso:: :py:func:`setHorizontalTriangleFilterThreshold` + +.. seealso:: :py:func:`setHorizontalTriangleFilterUnit` + +.. seealso:: :py:func:`horizontalTriangleFilter` + +.. versionadded:: 3.36 +%End + + float horizontalTriangleFilterThreshold() const; +%Docstring +Returns threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer +than the threshold, such triangle will not get rendered. Units of the threshold value are +given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`. + +.. seealso:: :py:func:`horizontalTriangleFilter` + +.. seealso:: :py:func:`horizontalTriangleFilterUnit` + +.. seealso:: :py:func:`setHorizontalTriangleFilterThreshold` + +.. versionadded:: 3.36 +%End + + void setHorizontalTriangleFilterThreshold( float threshold ); +%Docstring +Sets threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer +than the threshold, such triangle will not get rendered. Units of the threshold value are +given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`. + +.. seealso:: :py:func:`horizontalTriangleFilter` + +.. seealso:: :py:func:`horizontalTriangleFilterUnit` + +.. seealso:: :py:func:`horizontalTriangleFilterThreshold` + +.. versionadded:: 3.36 +%End + + Qgis::RenderUnit horizontalTriangleFilterUnit() const; +%Docstring +Returns units of the treshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. + +.. seealso:: :py:func:`horizontalTriangleFilter` + +.. seealso:: :py:func:`horizontalTriangleFilterThreshold` + +.. seealso:: :py:func:`setHorizontalTriangleFilterUnit` + +.. versionadded:: 3.36 +%End + + void setHorizontalTriangleFilterUnit( Qgis::RenderUnit unit ); +%Docstring +Sets units of the treshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. + +.. seealso:: :py:func:`horizontalTriangleFilter` + +.. seealso:: :py:func:`horizontalTriangleFilterThreshold` + +.. seealso:: :py:func:`horizontalTriangleFilterUnit` + +.. versionadded:: 3.36 %End virtual QList createLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/; @@ -466,6 +571,13 @@ Draws a point using a ``color`` at the specified ``x`` and ``y`` (in map coordin %End + void addPointToTriangulation( double x, double y, double z, const QColor &color, QgsPointCloudRenderContext &context ); +%Docstring +Adds a point to the list of points to be triangulated (only used when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` is enabled) + +.. versionadded:: 3.36 +%End + void copyCommonProperties( QgsPointCloudRenderer *destination ) const; %Docstring Copies common point cloud properties (such as point size and screen error) to the ``destination`` renderer. diff --git a/python/core/auto_generated/qgselevationmap.sip.in b/python/core/auto_generated/qgselevationmap.sip.in index ceddab8e883..5573e762734 100644 --- a/python/core/auto_generated/qgselevationmap.sip.in +++ b/python/core/auto_generated/qgselevationmap.sip.in @@ -99,6 +99,7 @@ with the unit of the encoded elevation in this elevation map. Returns raw elevation image with elevations encoded as color values %End + QPainter *painter() const; %Docstring Returns painter to the underlying QImage with elevations diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a802a8f6431..5f14eb5f018 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2305,6 +2305,7 @@ target_include_directories(qgis_core PUBLIC vector vectortile ${CMAKE_SOURCE_DIR}/external + ${CMAKE_SOURCE_DIR}/external/delaunator-cpp ${CMAKE_SOURCE_DIR}/external/nlohmann ${CMAKE_SOURCE_DIR}/external/kdbush/include ${CMAKE_SOURCE_DIR}/external/nmea diff --git a/src/core/mesh/qgsmeshlayerutils.cpp b/src/core/mesh/qgsmeshlayerutils.cpp index 10a00a4de92..e6c00b840f3 100644 --- a/src/core/mesh/qgsmeshlayerutils.cpp +++ b/src/core/mesh/qgsmeshlayerutils.cpp @@ -287,6 +287,13 @@ static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &p return true; } +bool QgsMeshLayerUtils::calculateBarycentricCoordinates( + const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP, + double &lam1, double &lam2, double &lam3 ) +{ + return E3T_physicalToBarycentric( pA, pB, pC, pP, lam1, lam2, lam3 ); +} + double QgsMeshLayerUtils::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3, double val1, double val2, double val3, const QgsPointXY &pt ) { diff --git a/src/core/mesh/qgsmeshlayerutils.h b/src/core/mesh/qgsmeshlayerutils.h index c316ebd683b..e215e5efcb1 100644 --- a/src/core/mesh/qgsmeshlayerutils.h +++ b/src/core/mesh/qgsmeshlayerutils.h @@ -141,6 +141,24 @@ class CORE_EXPORT QgsMeshLayerUtils double devicePixelRatio = 1.0 ); + /** + * Calculates barycentric coordinates of point \a pP in a triangle given by + * its vertices \a pA, \a pB and \a pC. The results are written to \a lam1, + * \a lam2, \a lam3. The function returns true or false, depending on whether + * the point \a pP is inside the triangle. + * + * \since QGIS 3.36 + */ + static bool calculateBarycentricCoordinates( + const QgsPointXY &pA, + const QgsPointXY &pB, + const QgsPointXY &pC, + const QgsPointXY &pP, + double &lam1, + double &lam2, + double &lam3 + ); + /** * Interpolates value based on known values on the vertices of a edge * \returns value on the point pt a or NaN diff --git a/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp b/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp index 4ea3294a12f..554a5d00fdf 100644 --- a/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp +++ b/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp @@ -49,7 +49,13 @@ QgsPointCloudRenderer *QgsPointCloudAttributeByRampRenderer::clone() const void QgsPointCloudAttributeByRampRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context ) { - const QgsRectangle visibleExtent = context.renderContext().extent(); + QgsRectangle visibleExtent = context.renderContext().extent(); + if ( renderAsTriangles() ) + { + // we need to include also points slightly outside of the visible extent, + // otherwise the triangulation may be missing triangles near the edges and corners + visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 ); + } const char *ptr = block->data(); int count = block->pointCount(); @@ -122,9 +128,17 @@ void QgsPointCloudAttributeByRampRenderer::renderBlock( const QgsPointCloudBlock attributeValue = ( context.offset().z() + context.scale().z() * attributeValue ) * context.zValueScale() + context.zValueFixedOffset(); mColorRampShader.shade( attributeValue, &red, &green, &blue, &alpha ); - drawPoint( x, y, QColor( red, green, blue, alpha ), context ); - if ( renderElevation ) - drawPointToElevationMap( x, y, z, context ); + + if ( renderAsTriangles() ) + { + addPointToTriangulation( x, y, z, QColor( red, green, blue, alpha ), context ); + } + else + { + drawPoint( x, y, QColor( red, green, blue, alpha ), context ); + if ( renderElevation ) + drawPointToElevationMap( x, y, z, context ); + } rendered++; } diff --git a/src/core/pointcloud/qgspointcloudclassifiedrenderer.cpp b/src/core/pointcloud/qgspointcloudclassifiedrenderer.cpp index 54c78684647..6cec0fdf4fd 100644 --- a/src/core/pointcloud/qgspointcloudclassifiedrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudclassifiedrenderer.cpp @@ -67,7 +67,13 @@ QgsPointCloudRenderer *QgsPointCloudClassifiedRenderer::clone() const void QgsPointCloudClassifiedRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context ) { - const QgsRectangle visibleExtent = context.renderContext().extent(); + QgsRectangle visibleExtent = context.renderContext().extent(); + if ( renderAsTriangles() ) + { + // we need to include also points slightly outside of the visible extent, + // otherwise the triangulation may be missing triangles near the edges and corners + visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 ); + } const char *ptr = block->data(); int count = block->pointCount(); @@ -136,9 +142,16 @@ void QgsPointCloudClassifiedRenderer::renderBlock( const QgsPointCloudBlock *blo } } - drawPoint( x, y, color, context ); - if ( renderElevation ) - drawPointToElevationMap( x, y, z, context ); + if ( renderAsTriangles() ) + { + addPointToTriangulation( x, y, z, color, context ); + } + else + { + drawPoint( x, y, color, context ); + if ( renderElevation ) + drawPointToElevationMap( x, y, z, context ); + } rendered++; } } diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp index 8f4bd0c2973..ff75da14f3c 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp @@ -24,6 +24,7 @@ #include "qgspointcloudindex.h" #include "qgscolorramp.h" #include "qgselevationmap.h" +#include "qgsmeshlayerutils.h" #include "qgspointcloudrequest.h" #include "qgspointcloudattribute.h" #include "qgspointcloudrenderer.h" @@ -36,6 +37,9 @@ #include "qgsruntimeprofiler.h" #include "qgsapplication.h" +#include + + QgsPointCloudLayerRenderer::QgsPointCloudLayerRenderer( QgsPointCloudLayer *layer, QgsRenderContext &context ) : QgsMapLayerRenderer( layer->id(), &context ) , mLayer( layer ) @@ -279,7 +283,15 @@ bool QgsPointCloudLayerRenderer::renderIndex( QgsPointCloudIndex *pc ) int nodesDrawn = 0; bool canceled = false; - switch ( mRenderer->drawOrder2d() ) + Qgis::PointCloudDrawOrder drawOrder = mRenderer->drawOrder2d(); + if ( mRenderer->renderAsTriangles() ) + { + // Ordered rendering is ignored when drawing as surface, because all points are used for triangulation. + // We would need to have a way to detect if a point is occluded by some other points, which may be costly. + drawOrder = Qgis::PointCloudDrawOrder::Default; + } + + switch ( drawOrder ) { case Qgis::PointCloudDrawOrder::BottomToTop: case Qgis::PointCloudDrawOrder::TopToBottom: @@ -355,6 +367,12 @@ int QgsPointCloudLayerRenderer::renderNodesSync( const QVectorrenderAsTriangles() ) + { + renderTriangulatedSurface( context ); + } + return nodesDrawn; } @@ -440,6 +458,11 @@ int QgsPointCloudLayerRenderer::renderNodesAsync( const QVectordeleteLater(); } + if ( mRenderer->renderAsTriangles() ) + { + renderTriangulatedSurface( context ); + } + return nodesDrawn; } @@ -569,6 +592,129 @@ int QgsPointCloudLayerRenderer::renderNodesSorted( const QVector length; +} + +static void renderTriangle( QImage &img, QPointF *pts, QRgb c0, QRgb c1, QRgb c2, float horizontalFilter, float *elev, QgsElevationMap *elevationMap ) +{ + if ( horizontalFilter > 0 ) + { + float filterThreshold2 = horizontalFilter * horizontalFilter; + if ( isEdgeTooLong( pts[0], pts[1], filterThreshold2 ) || + isEdgeTooLong( pts[1], pts[2], filterThreshold2 ) || + isEdgeTooLong( pts[2], pts[0], filterThreshold2 ) ) + return; + } + + QgsRectangle screenBBox = QgsMeshLayerUtils::triangleBoundingBox( pts[0], pts[1], pts[2] ); + + QSize outputSize = img.size(); + + int topLim = std::max( int( screenBBox.yMinimum() ), 0 ); + int bottomLim = std::min( int( screenBBox.yMaximum() ), outputSize.height() - 1 ); + int leftLim = std::max( int( screenBBox.xMinimum() ), 0 ); + int rightLim = std::min( int( screenBBox.xMaximum() ), outputSize.width() - 1 ); + + int red0 = qRed( c0 ), green0 = qGreen( c0 ), blue0 = qBlue( c0 ); + int red1 = qRed( c1 ), green1 = qGreen( c1 ), blue1 = qBlue( c1 ); + int red2 = qRed( c2 ), green2 = qGreen( c2 ), blue2 = qBlue( c2 ); + + QRgb *elevData = elevationMap ? elevationMap->rawElevationImageData() : nullptr; + + for ( int j = topLim; j <= bottomLim; j++ ) + { + QRgb *scanLine = ( QRgb * ) img.scanLine( j ); + QRgb *elevScanLine = elevData ? elevData + ( outputSize.width() * j ) : nullptr; + for ( int k = leftLim; k <= rightLim; k++ ) + { + QPointF pt( k, j ); + double lam1, lam2, lam3; + if ( !QgsMeshLayerUtils::calculateBarycentricCoordinates( pts[0], pts[1], pts[2], pt, lam3, lam2, lam1 ) ) + continue; + + // interpolate color + int r = red0 * lam1 + red1 * lam2 + red2 * lam3; + int g = green0 * lam1 + green1 * lam2 + green2 * lam3; + int b = blue0 * lam1 + blue1 * lam2 + blue2 * lam3; + scanLine[k] = qRgb( r, g, b ); + + // interpolate elevation - in case we are doing global map shading + if ( elevScanLine ) + { + float z = elev[0] * lam1 + elev[1] * lam2 + elev[2] * lam3; + elevScanLine[k] = QgsElevationMap::encodeElevation( z ); + } + } + } +} + +void QgsPointCloudLayerRenderer::renderTriangulatedSurface( QgsPointCloudRenderContext &context ) +{ + const QgsPointCloudRenderContext::TriangulationData &triangulation = context.triangulationData(); + const std::vector &points = triangulation.points; + + // Delaunator would crash if it gets less than three points + if ( points.size() < 3 ) + { + QgsDebugMsgLevel( QStringLiteral( "Need at least 3 points to triangulate" ), 4 ); + return; + } + + std::unique_ptr delaunator; + try + { + delaunator.reset( new delaunator::Delaunator( points ) ); + } + catch ( std::exception &e ) + { + // something went wrong, better to retrieve initial state + QgsDebugMsgLevel( QStringLiteral( "Error with triangulation" ), 4 ); + return; + } + + float horizontalFilter = 0; + if ( mRenderer->horizontalTriangleFilter() ) + { + horizontalFilter = renderContext()->convertToPainterUnits( + mRenderer->horizontalTriangleFilterThreshold(), mRenderer->horizontalTriangleFilterUnit() ); + } + + QImage img( context.renderContext().deviceOutputSize(), QImage::Format_ARGB32_Premultiplied ); + img.setDevicePixelRatio( context.renderContext().devicePixelRatio() ); + img.fill( 0 ); + + const std::vector &triangleIndexes = delaunator->triangles; + QPainter *painter = context.renderContext().painter(); + QgsElevationMap *elevationMap = context.renderContext().elevationMap(); + QPointF triangle[3]; + float elev[3]; + for ( size_t i = 0; i < triangleIndexes.size(); i += 3 ) + { + size_t v0 = triangleIndexes[i], v1 = triangleIndexes[i + 1], v2 = triangleIndexes[i + 2]; + triangle[0].rx() = points[v0 * 2]; + triangle[0].ry() = points[v0 * 2 + 1]; + triangle[1].rx() = points[v1 * 2]; + triangle[1].ry() = points[v1 * 2 + 1]; + triangle[2].rx() = points[v2 * 2]; + triangle[2].ry() = points[v2 * 2 + 1]; + + if ( elevationMap ) + { + elev[0] = triangulation.elevations[v0]; + elev[1] = triangulation.elevations[v1]; + elev[2] = triangulation.elevations[v2]; + } + + QRgb c0 = triangulation.colors[v0], c1 = triangulation.colors[v1], c2 = triangulation.colors[v2]; + renderTriangle( img, triangle, c0, c1, c2, horizontalFilter, elev, elevationMap ); + } + + painter->drawImage( 0, 0, img ); +} + bool QgsPointCloudLayerRenderer::forceRasterRender() const { // unless we are using the extent only renderer, point cloud layers should always be rasterized -- we don't want to export points as vectors diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.h b/src/core/pointcloud/qgspointcloudlayerrenderer.h index d14e7c76ca0..35e1326eac7 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.h +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.h @@ -75,6 +75,7 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer int renderNodesSync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); int renderNodesAsync( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled ); int renderNodesSorted( const QVector &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order ); + void renderTriangulatedSurface( QgsPointCloudRenderContext &context ); bool renderIndex( QgsPointCloudIndex *pc ); QgsPointCloudLayer *mLayer = nullptr; diff --git a/src/core/pointcloud/qgspointcloudrenderer.cpp b/src/core/pointcloud/qgspointcloudrenderer.cpp index 3826a90881c..38549b1f306 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudrenderer.cpp @@ -195,6 +195,11 @@ void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destina destination->setMaximumScreenErrorUnit( mMaximumScreenErrorUnit ); destination->setPointSymbol( mPointSymbol ); destination->setDrawOrder2d( mDrawOrder2d ); + + destination->setRenderAsTriangles( mRenderAsTriangles ); + destination->setHorizontalTriangleFilter( mHorizontalTriangleFilter ); + destination->setHorizontalTriangleFilterThreshold( mHorizontalTriangleFilterThreshold ); + destination->setHorizontalTriangleFilterUnit( mHorizontalTriangleFilterUnit ); } void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & ) @@ -207,6 +212,11 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, mMaximumScreenErrorUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "maximumScreenErrorUnit" ), QStringLiteral( "MM" ) ) ); mPointSymbol = static_cast< Qgis::PointCloudSymbol >( element.attribute( QStringLiteral( "pointSymbol" ), QStringLiteral( "0" ) ).toInt() ); mDrawOrder2d = static_cast< Qgis::PointCloudDrawOrder >( element.attribute( QStringLiteral( "drawOrder2d" ), QStringLiteral( "0" ) ).toInt() ); + + mRenderAsTriangles = element.attribute( QStringLiteral( "renderAsTriangles" ), QStringLiteral( "0" ) ).toInt(); + mHorizontalTriangleFilter = element.attribute( QStringLiteral( "horizontalTriangleFilter" ), QStringLiteral( "0" ) ).toInt(); + mHorizontalTriangleFilterThreshold = element.attribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), QStringLiteral( "5" ) ).toFloat(); + mHorizontalTriangleFilterUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QStringLiteral( "MM" ) ) ); } void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const @@ -219,6 +229,11 @@ void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const Qg element.setAttribute( QStringLiteral( "maximumScreenErrorUnit" ), QgsUnitTypes::encodeUnit( mMaximumScreenErrorUnit ) ); element.setAttribute( QStringLiteral( "pointSymbol" ), QString::number( static_cast< int >( mPointSymbol ) ) ); element.setAttribute( QStringLiteral( "drawOrder2d" ), QString::number( static_cast< int >( mDrawOrder2d ) ) ); + + element.setAttribute( QStringLiteral( "renderAsTriangles" ), QString::number( static_cast< int >( mRenderAsTriangles ) ) ); + element.setAttribute( QStringLiteral( "horizontalTriangleFilter" ), QString::number( static_cast< int >( mHorizontalTriangleFilter ) ) ); + element.setAttribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), qgsDoubleToString( mHorizontalTriangleFilterThreshold ) ); + element.setAttribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QgsUnitTypes::encodeUnit( mHorizontalTriangleFilterUnit ) ); } Qgis::PointCloudSymbol QgsPointCloudRenderer::pointSymbol() const diff --git a/src/core/pointcloud/qgspointcloudrenderer.h b/src/core/pointcloud/qgspointcloudrenderer.h index a62c6ce7f28..977c456d7b9 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.h +++ b/src/core/pointcloud/qgspointcloudrenderer.h @@ -225,6 +225,28 @@ class CORE_EXPORT QgsPointCloudRenderContext } #endif +#ifndef SIP_RUN // this is only meant for low-level rendering in C++ code + + /** + * Helper data structure used when rendering points as triangulated surface. + * We populate the structure as we traverse the nodes and then run Delaunay + * triangulation at the end + draw the triangles. + * \since QGIS 3.36 + */ + struct TriangulationData + { + std::vector points; //!< X,Y for each point - kept in this structure so that we can use it without further conversions in Delaunator-cpp + std::vector colors; //!< RGB color for each point + std::vector elevations; //!< Z value for each point (only used when global map shading is enabled) + }; + + /** + * Returns reference to the triangulation data structure (only used when rendering as triangles is enabled) + * \since QGIS 3.36 + */ + TriangulationData &triangulationData() { return mTriangulationData; } +#endif + private: #ifdef SIP_RUN QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh ); @@ -243,6 +265,8 @@ class CORE_EXPORT QgsPointCloudRenderContext double mZValueFixedOffset = 0; QgsFeedback *mFeedback = nullptr; + + TriangulationData mTriangulationData; }; #ifndef SIP_RUN @@ -561,6 +585,92 @@ class CORE_EXPORT QgsPointCloudRenderer */ void setMaximumScreenErrorUnit( Qgis::RenderUnit unit ); + /** + * Returns whether points are triangulated to render solid surface + * + * \since QGIS 3.36 + */ + bool renderAsTriangles() const { return mRenderAsTriangles; } + + /** + * Sets whether points are triangulated to render solid surface + * + * \since QGIS 3.36 + */ + void setRenderAsTriangles( bool asTriangles ) { mRenderAsTriangles = asTriangles; } + + /** + * Returns whether large triangles will get rendered. This only applies when renderAsTriangles() + * is enabled. When the triangle filtering is enabled, triangles where at least one side is + * horizontally longer than the threshold in horizontalTriangleFilterThreshold() do not get rendered. + * + * \see horizontalTriangleFilterThreshold() + * \see horizontalTriangleFilterUnit() + * \see setHorizontalTriangleFilter() + * \since QGIS 3.36 + */ + bool horizontalTriangleFilter() const { return mHorizontalTriangleFilter; } + + /** + * Sets whether large triangles will get rendered. This only applies when renderAsTriangles() + * is enabled. When the triangle filtering is enabled, triangles where at least one side is + * horizontally longer than the threshold in horizontalTriangleFilterThreshold() do not get rendered. + * + * \see setHorizontalTriangleFilterThreshold() + * \see setHorizontalTriangleFilterUnit() + * \see horizontalTriangleFilter() + * \since QGIS 3.36 + */ + void setHorizontalTriangleFilter( bool enabled ) { mHorizontalTriangleFilter = enabled; } + + /** + * Returns threshold for filtering of triangles. This only applies when renderAsTriangles() and + * horizontalTriangleFilter() are both enabled. If any edge of a triangle is horizontally longer + * than the threshold, such triangle will not get rendered. Units of the threshold value are + * given by horizontalTriangleFilterUnits(). + * + * \see horizontalTriangleFilter() + * \see horizontalTriangleFilterUnit() + * \see setHorizontalTriangleFilterThreshold() + * \since QGIS 3.36 + */ + float horizontalTriangleFilterThreshold() const { return mHorizontalTriangleFilterThreshold; } + + /** + * Sets threshold for filtering of triangles. This only applies when renderAsTriangles() and + * horizontalTriangleFilter() are both enabled. If any edge of a triangle is horizontally longer + * than the threshold, such triangle will not get rendered. Units of the threshold value are + * given by horizontalTriangleFilterUnits(). + * + * \see horizontalTriangleFilter() + * \see horizontalTriangleFilterUnit() + * \see horizontalTriangleFilterThreshold() + * \since QGIS 3.36 + */ + void setHorizontalTriangleFilterThreshold( float threshold ) { mHorizontalTriangleFilterThreshold = threshold; } + + /** + * Returns units of the treshold for filtering of triangles. This only applies when renderAsTriangles() and + * horizontalTriangleFilter() are both enabled. + * + * \see horizontalTriangleFilter() + * \see horizontalTriangleFilterThreshold() + * \see setHorizontalTriangleFilterUnit() + * \since QGIS 3.36 + */ + Qgis::RenderUnit horizontalTriangleFilterUnit() const { return mHorizontalTriangleFilterUnit; } + + /** + * Sets units of the treshold for filtering of triangles. This only applies when renderAsTriangles() and + * horizontalTriangleFilter() are both enabled. + * + * \see horizontalTriangleFilter() + * \see horizontalTriangleFilterThreshold() + * \see horizontalTriangleFilterUnit() + * \since QGIS 3.36 + */ + void setHorizontalTriangleFilterUnit( Qgis::RenderUnit unit ) { mHorizontalTriangleFilterUnit = unit; } + /** * Creates a set of legend nodes representing the renderer. */ @@ -632,6 +742,21 @@ class CORE_EXPORT QgsPointCloudRenderer void drawPointToElevationMap( double x, double y, double z, QgsPointCloudRenderContext &context ) const; #endif + /** + * Adds a point to the list of points to be triangulated (only used when renderAsTriangles() is enabled) + * \since QGIS 3.36 + */ + void addPointToTriangulation( double x, double y, double z, const QColor &color, QgsPointCloudRenderContext &context ) + { + QgsPointXY p = context.renderContext().mapToPixel().transform( x, y ) * context.renderContext().devicePixelRatio(); + QgsPointCloudRenderContext::TriangulationData &triangulation = context.triangulationData(); + triangulation.points.push_back( p.x() ); + triangulation.points.push_back( p.y() ); + triangulation.colors.push_back( color.rgb() ); + if ( context.renderContext().elevationMap() ) + triangulation.elevations.push_back( z ); + } + /** * Copies common point cloud properties (such as point size and screen error) to the \a destination renderer. */ @@ -673,6 +798,11 @@ class CORE_EXPORT QgsPointCloudRenderer Qgis::PointCloudSymbol mPointSymbol = Qgis::PointCloudSymbol::Square; int mPainterPenWidth = 1; Qgis::PointCloudDrawOrder mDrawOrder2d = Qgis::PointCloudDrawOrder::Default; + + bool mRenderAsTriangles = false; + bool mHorizontalTriangleFilter = false; + float mHorizontalTriangleFilterThreshold = 5.0; + Qgis::RenderUnit mHorizontalTriangleFilterUnit = Qgis::RenderUnit::Millimeters; }; #endif // QGSPOINTCLOUDRENDERER_H diff --git a/src/core/pointcloud/qgspointcloudrgbrenderer.cpp b/src/core/pointcloud/qgspointcloudrgbrenderer.cpp index 58976524afe..81f2bfcfdee 100644 --- a/src/core/pointcloud/qgspointcloudrgbrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudrgbrenderer.cpp @@ -56,7 +56,13 @@ QgsPointCloudRenderer *QgsPointCloudRgbRenderer::clone() const void QgsPointCloudRgbRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context ) { - const QgsRectangle visibleExtent = context.renderContext().extent(); + QgsRectangle visibleExtent = context.renderContext().extent(); + if ( renderAsTriangles() ) + { + // we need to include also points slightly outside of the visible extent, + // otherwise the triangulation may be missing triangles near the edges and corners + visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 ); + } const char *ptr = block->data(); const int count = block->pointCount(); @@ -158,9 +164,16 @@ void QgsPointCloudRgbRenderer::renderBlock( const QgsPointCloudBlock *block, Qgs green = std::max( 0, std::min( 255, green ) ); blue = std::max( 0, std::min( 255, blue ) ); - drawPoint( x, y, QColor( red, green, blue ), context ); - if ( renderElevation ) - drawPointToElevationMap( x, y, z, context ); + if ( renderAsTriangles() ) + { + addPointToTriangulation( x, y, z, QColor( red, green, blue ), context ); + } + else + { + drawPoint( x, y, QColor( red, green, blue ), context ); + if ( renderElevation ) + drawPointToElevationMap( x, y, z, context ); + } rendered++; } } diff --git a/src/core/qgselevationmap.h b/src/core/qgselevationmap.h index 39ea8d08a31..89c84b0e62f 100644 --- a/src/core/qgselevationmap.h +++ b/src/core/qgselevationmap.h @@ -101,6 +101,15 @@ class CORE_EXPORT QgsElevationMap //! Returns raw elevation image with elevations encoded as color values QImage rawElevationImage() const { return mElevationImage; } +#ifndef SIP_RUN + + /** + * Returns pointer to the actual elevation image data + * \since QGIS 3.36 + */ + QRgb *rawElevationImageData() { return reinterpret_cast( mElevationImage.bits() ); } +#endif + //! Returns painter to the underlying QImage with elevations QPainter *painter() const; diff --git a/src/gui/pointcloud/qgspointcloudrendererpropertieswidget.cpp b/src/gui/pointcloud/qgspointcloudrendererpropertieswidget.cpp index 165972e56a5..10c6c8a1650 100644 --- a/src/gui/pointcloud/qgspointcloudrendererpropertieswidget.cpp +++ b/src/gui/pointcloud/qgspointcloudrendererpropertieswidget.cpp @@ -115,12 +115,25 @@ QgsPointCloudRendererPropertiesWidget::QgsPointCloudRendererPropertiesWidget( Qg Qgis::RenderUnit::Inches } ); mMaxErrorSpinBox->setClearValue( 0.3 ); + mHorizontalTriangleThresholdSpinBox->setClearValue( 5.0 ); + mHorizontalTriangleUnitWidget->setUnits( { Qgis::RenderUnit::Millimeters, + Qgis::RenderUnit::MetersInMapUnits, + Qgis::RenderUnit::MapUnits, + Qgis::RenderUnit::Pixels, + Qgis::RenderUnit::Points, + Qgis::RenderUnit::Inches } ); + connect( mMaxErrorSpinBox, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); connect( mMaxErrorUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); connect( mPointStyleComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); connect( mDrawOrderComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); + connect( mTriangulateGroupBox, &QGroupBox::toggled, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); + connect( mHorizontalTriangleCheckBox, &QCheckBox::clicked, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); + connect( mHorizontalTriangleThresholdSpinBox, qOverload( &QgsDoubleSpinBox::valueChanged ), this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); + connect( mHorizontalTriangleUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsPointCloudRendererPropertiesWidget::emitWidgetChanged ); + syncToLayer( layer ); } @@ -170,6 +183,11 @@ void QgsPointCloudRendererPropertiesWidget::syncToLayer( QgsMapLayer *layer ) mMaxErrorSpinBox->setValue( mLayer->renderer()->maximumScreenError() ); mMaxErrorUnitWidget->setUnit( mLayer->renderer()->maximumScreenErrorUnit() ); + + mTriangulateGroupBox->setChecked( mLayer->renderer()->renderAsTriangles() ); + mHorizontalTriangleCheckBox->setChecked( mLayer->renderer()->horizontalTriangleFilter() ); + mHorizontalTriangleThresholdSpinBox->setValue( mLayer->renderer()->horizontalTriangleFilterThreshold() ); + mHorizontalTriangleUnitWidget->setUnit( mLayer->renderer()->horizontalTriangleFilterUnit() ); } mBlockChangedSignal = false; @@ -204,6 +222,11 @@ void QgsPointCloudRendererPropertiesWidget::apply() mLayer->renderer()->setMaximumScreenError( mMaxErrorSpinBox->value() ); mLayer->renderer()->setMaximumScreenErrorUnit( mMaxErrorUnitWidget->unit() ); mLayer->renderer()->setDrawOrder2d( static_cast< Qgis::PointCloudDrawOrder >( mDrawOrderComboBox->currentData().toInt() ) ); + + mLayer->renderer()->setRenderAsTriangles( mTriangulateGroupBox->isChecked() ); + mLayer->renderer()->setHorizontalTriangleFilter( mHorizontalTriangleCheckBox->isChecked() ); + mLayer->renderer()->setHorizontalTriangleFilterThreshold( mHorizontalTriangleThresholdSpinBox->value() ); + mLayer->renderer()->setHorizontalTriangleFilterUnit( mHorizontalTriangleUnitWidget->unit() ); } void QgsPointCloudRendererPropertiesWidget::rendererChanged() diff --git a/src/ui/pointcloud/qgspointcloudrendererpropsdialogbase.ui b/src/ui/pointcloud/qgspointcloudrendererpropsdialogbase.ui index 8f5ba278de6..ecee9191aa0 100644 --- a/src/ui/pointcloud/qgspointcloudrendererpropsdialogbase.ui +++ b/src/ui/pointcloud/qgspointcloudrendererpropsdialogbase.ui @@ -7,7 +7,7 @@ 0 0 569 - 529 + 618 @@ -151,6 +151,50 @@ + + + + Render as a Surface (Triangulate) + + + true + + + false + + + + 0 + + + 3 + + + + + Skip triangles longer than + + + + + + + Maximum Triangle Side Size in Horizontal Plan + + + 6 + + + 99999999999.000000000000000 + + + + + + + + + @@ -290,6 +334,12 @@
qgspanelwidget.h
1 + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
cboRenderers diff --git a/tests/src/core/testqgsmaprendererjob.cpp b/tests/src/core/testqgsmaprendererjob.cpp index 096d6f6ff93..cf3ac89e65a 100644 --- a/tests/src/core/testqgsmaprendererjob.cpp +++ b/tests/src/core/testqgsmaprendererjob.cpp @@ -1190,6 +1190,20 @@ void TestQgsMapRendererJob::testMapShading() renderJob->waitForFinished(); img = renderJob->renderedImage(); QVERIFY( imageCheck( QStringLiteral( "render_shading_5" ), img ) ); + + // test elevation map when rendering point cloud with triangulation + QgsElevationShadingRenderer shadingRenderer2; + shadingRenderer2.setActive( true ); + shadingRenderer2.setActiveHillshading( true ); + shadingRenderer2.setActiveEyeDomeLighting( false ); + pointCloudLayer->renderer()->setRenderAsTriangles( true ); + mapSettings.setLayers( QList< QgsMapLayer * >() << pointCloudLayer.get() ); + mapSettings.setElevationShadingRenderer( shadingRenderer2 ); + renderJob.reset( new QgsMapRendererSequentialJob( mapSettings ) ); + renderJob->start(); + renderJob->waitForFinished(); + img = renderJob->renderedImage(); + QVERIFY( imageCheck( QStringLiteral( "render_shading_point_cloud_triangles" ), img ) ); } bool TestQgsMapRendererJob::imageCheck( const QString &testName, const QImage &image, int mismatchCount ) diff --git a/tests/src/python/test_qgspointcloudattributebyramprenderer.py b/tests/src/python/test_qgspointcloudattributebyramprenderer.py index 39092a53117..25e8b783220 100644 --- a/tests/src/python/test_qgspointcloudattributebyramprenderer.py +++ b/tests/src/python/test_qgspointcloudattributebyramprenderer.py @@ -432,6 +432,37 @@ class TestQgsPointCloudAttributeByRampRenderer(QgisTestCase): self.render_map_settings_check('ramp_bottom_to_top', 'ramp_bottom_to_top', mapsettings) ) + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderTriangles(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('Intensity') + renderer.setMinimum(200) + renderer.setMaximum(1000) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(200, 1000, ramp) + shader.classifyColorRamp() + renderer.setColorRampShader(shader) + renderer.setRenderAsTriangles(True) + + layer.setRenderer(renderer) + + layer.renderer().setPointSize(2) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(498061, 7050991, 498069, 7050999)) + mapsettings.setLayers([layer]) + + self.assertTrue( + self.render_map_settings_check('ramp_triangles', 'ramp_triangles', mapsettings) + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgspointcloudclassifiedrenderer.py b/tests/src/python/test_qgspointcloudclassifiedrenderer.py index befe7a7c5c5..9b5caa58892 100644 --- a/tests/src/python/test_qgspointcloudclassifiedrenderer.py +++ b/tests/src/python/test_qgspointcloudclassifiedrenderer.py @@ -288,6 +288,30 @@ class TestQgsPointCloudClassifiedRenderer(QgisTestCase): self.render_map_settings_check('classified_render_unfiltered', 'classified_render_unfiltered', mapsettings) ) + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderTriangles(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + categories = QgsPointCloudRendererRegistry.classificationAttributeCategories(layer) + renderer = QgsPointCloudClassifiedRenderer('Classification', categories) + renderer.setRenderAsTriangles(True) + layer.setRenderer(renderer) + + layer.renderer().setPointSize(2) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(498061, 7050991, 498069, 7050999)) + mapsettings.setLayers([layer]) + + self.assertTrue( + self.render_map_settings_check('classified_triangles', 'classified_triangles', mapsettings) + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_qgspointcloudrgbrenderer.py b/tests/src/python/test_qgspointcloudrgbrenderer.py index 7e96f48b874..a6cac7611d7 100644 --- a/tests/src/python/test_qgspointcloudrgbrenderer.py +++ b/tests/src/python/test_qgspointcloudrgbrenderer.py @@ -14,6 +14,7 @@ from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QPainter from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( + Qgis, QgsContrastEnhancement, QgsCoordinateReferenceSystem, QgsDoubleRange, @@ -421,6 +422,49 @@ class TestQgsPointCloudRgbRenderer(QgisTestCase): self.render_map_settings_check('rgb_bottom_to_top', 'rgb_bottom_to_top', mapsettings) ) + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderTriangles(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + layer.renderer().setPointSize(6) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + layer.renderer().setRenderAsTriangles(True) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6)) + mapsettings.setLayers([layer]) + + self.assertTrue( + self.render_map_settings_check('rgb_triangles', 'rgb_triangles', mapsettings) + ) + + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderTrianglesHorizontalFilter(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + layer.renderer().setPointSize(6) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + layer.renderer().setRenderAsTriangles(True) + layer.renderer().setHorizontalTriangleFilter(True) + layer.renderer().setHorizontalTriangleFilterThreshold(10.0) + layer.renderer().setHorizontalTriangleFilterUnit(Qgis.RenderUnit.Millimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6)) + mapsettings.setLayers([layer]) + + self.assertTrue( + self.render_map_settings_check('rgb_triangles_filter', 'rgb_triangles_filter', mapsettings) + ) + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/control_images/map_renderer/expected_render_shading_point_cloud_triangles/expected_render_shading_point_cloud_triangles.png b/tests/testdata/control_images/map_renderer/expected_render_shading_point_cloud_triangles/expected_render_shading_point_cloud_triangles.png new file mode 100644 index 0000000000000000000000000000000000000000..0699ed7b10ee88d19a83e1c21d53afb546e37d46 GIT binary patch literal 285336 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

V)*W8vxhPMK!ee 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

E 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

{lhh=-M!wc*aib zuEiEfZM*1Y7DETY(^7j+4E&UFGm2*9eRY)|c>oJ?A*JIS94>hi>YSf6Ua7mgx~>Z= zpY(e8Q+zgLzCRBz_&*v^+mAL^qONOe9++vcesQkNe$%IXHw7tk_ywnm}v1KG2yT#;Q zbUUQ^DYX&fb;zZy_hdgz4MA(vS>yWZmQq;a(W3e!2C#hS+a7@&z17jkPnGrk(K|(l+9Q-jzwdT*-DVJ@UR&!S zLP?^@&h>ZHhxVe+zo1|0b|flIUDuoxUqyB@#|xu0~CZn&A;Zy@c(56JgujR2s4na^peDSg7T0Nbim zzNQ;a?O^lejHF0k0@Y>7pTE;Hav}B-^vu}rMdlZb7B_URC!O#6iCuPJeI7d*G5e=* zB|xhC_XkGaSO0wZd3w`^VSV86PetKh%O8twD==J!`dribrezr^St?2t!n!}*>1IFk zLzCoh^V8Jl<`WNNIADxN-x{^2()afV36SSZuuJ6T?mK|FHr>oTzJJW*oS6J<^QU`k zg(EGJ*uLw9z}`A-84iZUt+;-wfYN-q$I2<*B`9QP+UgKwkjpR`Zt<3FCi?5)j?MA2 zo&NaemksClNXqkS^vCE?nv5GV-S`=)CNk5>R!M8ajxszFxc2ng;Lp-~aNiLH>G94@ zU!Dz<1WJTR{UTOWqfivs5S|W{iWPVptF;09!6AX}gZeg7nW_NSpAg|_R^6XuyN0Q6 z=a{piVse75!rUX&kPF(uQYQX=qFfPbEF3j2^*-{)LB%b$GdMs@*K9B#D>^ zhTJNdhTeC=mZ8oy)KH=Oo=M$SZ;Od2qc?n39219tG0Wwpie`Eohk)e3z#^qjnWYYnX-C-qiP7E0DB`+ z$vWT&w<5!AI+?2gD<$ho3!R-a0gHTy2{1E(fb*sT$dSEkdYOZbc&Z{}_SVu4ct6t& zM&jmgJxt(GbA6ZgjyMn~;J#m%nUy57P*X37)6b81I8snzHrljN>f5ICp8q*5iIQbE zPl^Bdhg_|8xsM{Ku|vjpJ|C#LR9O0i<-Bb5qSJ=2^m1o@#XuaGrX8rH)Vl|y_3!y@ zT??Q*vz<-_R3Ro~KFOx9aS%l&sYh%zarbgb%)M)j%zY_hp*dv$@WIP)E83*t zffbHzzTZ_Awk0x@P&eM^Dgv#(qDZVhx)CL6N$nJvVq_h_lX-D>ZU6XIBw_|65zou)d^HHNk%E%5P${mXC#`Gfm(0qO`J~f zOL~FB4Il`;Aba9Coe*nkH@7e`;$CjS>8w(OSk(nM~o3%pS~RqF905rh{U{$K>ns zeWB78Vts+?WokeDh!3Wk^>DG?*wR5A4z=t@}!B*H|cn9?M=Zm>*g<^ zF|Mu>sU80`UHiG_4{D1!w&oC#=<{Q}(a#7hK2VRJzm}H*#ZE_bV5KEQIp#3b-f}Z@ z$hHC*Mq6)-(HJ2{N2*C`tZE6Onox#I2C@x+8PeJQ2NOZ;zSYg>Iw7m<;PA?dLO}0L zTD;RXf#r9;%Y?BmIyy~GIQJm0F0W%SYIE23D!J?4B)v|UJ?n*<+Y5iG%`?`=)T`Wm zYb6i;afmZc@^ju98nL*|#gS$nc->;oY(FpW3eeiGd5)J0=S9oWXVvNpeW z{CX7qaT|{;;gTKPIJhNHEEw)JIrnrwH-Ep6O51^{>{p=pSvGRQY79AgA-I`(IoiWas@31vHsu@+I($;D6sVhV>aOH(T9)Bjr z_T3rIx}va2u)on@M{AbTk#eZ`r1%v+lr9(`alk7m#Jg{wmJ z4p=<-e2fzpd0BA!LH-%Y^NaQ zn}hHt$f@^<^JEVBryF9OFBjxgRa9r1CJ_$FMSIqR!#2Y)n{>uTRwPsv`UWi8J58dF z%+r4t!L7I7V(KIpGpD+6xy1RNdZEz9zv+rFbA$V-8s+6rzpP{^Da2xspr_a5x4&;= z^|~~B)_2kH{#Z(aGJ6|zEZbrF6c?ADU&8KvIx8}t=DAgi2?k^~Y)NyqNV^wn%&_qfH+hwf(!iQlX{ zpe7OUOFa5i6rWe-gH#z^S&jRkPS+>N-4cCNNc7hk5SLWo;v=NQjeC~=6@ubcCxgP4ORXVy%eqFMHcRtWL z;oJf#?}TH#{Hqm!p0r8x{DdSnSvVpf6 z>M1J+sk)4Cz)w&a>mmBh@Yv`h*B&wrxp{EU9h zglOwDS$O*4YlSy{S#&zWB{CVciip-jBrLP!ytcz0o5RHM8TLjLe)o8k-#s4X+cy>L zfk8;|zO*XCNhb#xXd7nbP8T(m3j3Q1f735MTFd?>gJ)kBN#BRt)2v!sOg6lC?Na8p zZzL9XxajkuAecT`bOzfxOv*|Xde;`T2R3X@6A8=gd2cM?(2?uU)@X>KopBUzfJsx6 zTzgpw`&(jM^`J;B;yPrI7<7DeTJ>ytZ$Hf~1lzR(8~~PxpZ%qbJ>@R4;F)BE;h^w2~B(DxtYyz-Z^-V2Dt_;kc?k z;qsTQvQ31-o}!Q!JeTFd>|K%zMmUz`;24N_gXm)cM%6>2XJ-{D1*ZT|Y?Aj46cu~sS{`~hNn z8(GyQ6C2)23}{{juLe}6O)YQ*TH8%%U%_>kpU6F*;5r{{n#{F-tz){cpG6e~8{FD* znS;F&uk0Iz;V$LksoU|pO(b#>%jB>9jP5Xhu9rwiA!@ov<7ObCkd$4FK5Y&gU+>}N zRlA7xC5pNum$)^VS{V%GhEQg%~wYm(WeSsk#S=*etc^={e~OODoz->doA`f>ID4?C!cUQj{~4bF30qxc=k3S>~rjy z#bbhK%0`!=Qgbn-zMHp)M+|`eQ;Z}4Blx$muWu}wwu1Ne=r5kn)P~nMb=m-qW@0By zG|4IQuXiMf+A{O&4l*NB&$%b@P1GLYo? z)>=AZKIT>R(UpkMI8e?|#-b!;5O7ii>W5H$GF?3pLW<7gUE}%5N$=;m1$LG^n5+IJ zTf%X~w!N0s)K?TC!p#E#B#HfdS&^7E&5dO_ zY~7K`67;~%{?#d}grz&8l9xrIqGY`ea{K?ng-}%6qN>lD|KwH#v#(K>q0jJg%kCvS zcg&k8van8$#cfh4F?JBiN=^|_XbO}BWlF;ey5Z0}U@_2`%6qc^?e8)E_ID9j!Xbr) z6HFfbr)xoygzDT^zYu2c0iDlW*GWl0rmj}y?q3dY&qISu8n5xgKOMzy|92lZf3J^w ze^AbjWCa@z2KemcCRz>_x)WzyJPBo>gLghiQeERlHr*_!^-)nKAO|HAVgfo}`o^OOCmMy z0w&=~H$R-ZmYW_;Gi~fJ<)sOHUI|yZiq}(Mt4A$~0|Q~EM+Wf9a7NWZYLys!lVPgl zH1)w4eWr&>k06;<1yWSYY3^-58kb{nOkFp#Du(Gy*ql(^OGr*4nHF)cL#tgv<4`H7 zK8@4_jZ0=UplP{4o+Za+zyESUFt=MTXXo%@L{O;I9Y@0B&W;bVY<$~xux$z55_5N1 zDhT%E&ua_jeQw5YJMgW|OE8~%mW$2_pr}H?2eMlYSq_0mD+KfW>r(+o!t0fYgysBv zKi4sU0&!55C3a@QEUIZk(08x zOsiiU&8FQc63N1*!^!ub&y0q(96L+q^>+%S&y4QyKUal#^{oW=O<0;EdHU(o9jdjSJc&+`9M-)%fwxnWm_Mn5h0{Bj6&PTmXOJoWVKmiYqF>18N)q(w z9(ocX`V$_KhRKPO_EHnd5b%o)14DO^a&2;1RQ{Ijfb5_tQoc%avgE*=(N69=M6}@x zGhM%5#_bZpFX!~9XC-k-7fu5J@OW>H?M5cC&jvw!(jH37fiLntadwtT3lVw*nI zg~uZiOW4etE%vp~IxE2A@p(W40PnO0Xd{HFi-70#N>nFWx&JoNrVa*V*1tNM87H*R z`1GsXTVKOjUp|T}C-?K6EhmB7m_$>P-B0l zj;iLjG1CA3YUccL3QR2Rr^T+pb`+3IEL$Q7gxw+_z0+9F-?mN1YwL`w8~~3=GNo`vxDnlU z7}g#1jDzM#cpRC>H_hPO8M`PkC1!e(Tvi&TOAexC=jS;#eNF(cP{9{Sf%YMly>8^b z$ff*D-L{1}PdsTON#J%V7_x(zO+aiL)U45tOA~}T%MzRdO_LZLw%EQagKz5w7B2Q? zI}X_dBkL5H;~+>`!eDQ|#XcME|J@)D-c?B=VT*M>(_r0u!<=)vKbxQwx+@^M9GV6T zgZ-@Qlc~(4kS#TDN@SQ$)))vt}(A|~1-oJwfu^V3PV8zP9 zj=oEinC&jKcw=$9s9Jpd!3ei)n#$95j7;wqRP!X)_vaIU z<&VYr%-L}AnI)XI)R#A>dhvDfS}Q9x!X-KhV~A9XL1M;y9(%TnZBJ}w+ab?cQ>C)C zx13#>G30DO z-`Gi*?LE}^Z6=h)DDmq^aX-~=1HUX7=YBKAP}+r8wyxZ z`!o0|Q+O*=^bEVPTsk4y5UqR(HV#(PmaL$%GKOTy_*DhBtgv6NWPWKcIC4J7i}t>R zWo9F8HdyBM_H#^aTM+6LS%|9GmV=@eD)XHEuIA#9b4&SlqeP)CpMShdqUE5$o&yG# zo*!i4QEpVFFuTvL@8i;>tZJa>2&`J0!ynzp6r>9yz^u;}bD2?b?>0Xd)CT;4wqdej(w|XDWr>S9GGR`K!ChX(x9`}+vwsvj`Mv!XJK9w?S`l7d-ax2C z77l|GYdLC}2sY0w?<%?$zIKP`SHHPRw3078C&2oRX(o(!GpS-#(PK5anB2B)XXdcB z^^La*Wx>(4Dj~^W&krAC&ms3~)m50$!>ji{zi=!~JxOAQ%=n57=lL3VrE@Z#$|S0s zreSaVuw(vR#8brrIO`MRNoQ|JXPQ;fleTUP|3G>EF zLo9zzp?^?(?$=#jQe^e4sS;T@bu}srPpoBkw3;Q| z&+=y4BAFQ>0RA@^NdQLhiEzxP=ip0s)2VCt0y@z_4KV5Lv*>+lBf&r=rG07S9+Mqf zgoEia<_F>!cA9Pb%6MV*G$M1O?A8@(Ws4mDx49?Gnte4yQc1j_6lzrl9XPPJg0MSD zOqD1NC5b2o<(frAb@9PKnATVXZ$f3()E-v%Rg$t4W`+mw;1pHG`M~dpfNfh2jw)eg zW0Kps0M5n#0ie$}=JzZ4O3d`-;p zy>1EQf^2Y((3sN}kPurA0EwhSdqW!~L4`$U9ptXt%h|an!!!HG@u|oGUIm7uHnXO= z*||&1nPnNC{A~>#T_#QLDct|9HHTdv&%6|;Bq-C}ZQxjimiZU13USdn0nS?HLn-la z%ybz&ZFAWdk35Gz*kQ8loc)L2kvzPO?kjQ8*DW!a3Xrr_l8!>sQCU#Y#j5_TI4Q|2p`;wsHcoh9v&jK`yzfV8k657Tt_ z*(B3C$-y*Vx@rRVz9~jN$1N>o@d=fD(BPrrjc&sG_cQ(IFl*i@Y-fz%-^Yj$&rj}M!4L;ie%c)`dxp!Y_YE`G z-;LiiSl3WT`~EPayfL=LD)A~dvQp5+&k60wa&g<=Gzz-=LgdHEi}4tGK};vN4wwt@ zmg5xE{IVSkNzPXBvZ%^6Rp)w|rvV15MQhHlf=l4RSK81b0-k>>zmjMLrw@iXl9 zT}gWSEDp97CqcL0GyD-p2)wqujw=`UGk>m!Z+$tEzuo7N2$v|_aNF4YZMS{3l$*a( z&uPJR47!Cp?{$}km^xA8<{KiMe{DfoP*LF9SE=l^EBW-9Vmxxgt;cc6`Gw~gH@}Im z+}?}ND?-eSDJ}-0HZQ#%XXidWAN)c5o&EB6L%hE!&CDq(p@4W_-+%O_{A0>S2idlv zk5x}>q3xM}(A}jIEKx}ek5~ZzHyG&vjNlWY)QXcxI5=3?@#2c5^0f2t4+pWLI$eD} zVm)x`+}-@Pbp{KnyP4=s(3^6xBU;7D zN2SdUA{i!T$|46+dfy7d)$Q% zLMD?H>X8fNLLk4ECrgDJ%j;G_y5*)r)d($f2xL^ zZ8nEC^DqA8wY2vP5HnmP9hH=$lC%}#8AzlooM=CTuf6wgJ=Z6PoY#85DXfARx=nva zl3iPdi4A`|@ef4O_i^~#mpfVTwG+8!d^1q!ob2EC(Vc_0pBqIw?Jyi1$I+wPL?VhH z(*5kJuUR~`>Z4!xq23W; zcj0lRr5Zic>0?SFaX;U`tw<60UsrS zFt#q?vJ6f;uYvlBeXLK8N3~SStqi~XU>?`Z*@`KUJq)FNlxrCtdGc5;xqLg* zy+g>7l&@S5-Fvre2m7DIewiyFN2u?nkv}mnr!2CNpyF{u`C_kgrHX=9CC=oY_{yS*}rRuJ$nj!;|H5XSREL& zDXVnjcR6g>QXIVGic#<}3Bangsl0emV@u)qvPzj~Tx=KIQSuMY$=;q@B@6~+V)2ju z5;qKowjKS%I(mw3AL`PHrTruiQe6d8Bbyi{`d3$BSOhu07%)wU&7Qa zVm%2OwhuDg|F0SS{|U|y?B>R=^nB#~w|{>sA*YX9?(E~UmzH4Y4jWU|czPRo=5i`i&{JXs_F$L8bFtYhZC%4}?y1Bw z!RH|#{3QuyOm%bbuf>{YNl>PyQewxhPrj!2{`-ZI!oryelI<3gYGoEKtmNr8GQ{jp zey$N586ye62tF3^ghfYt8cp$F*u{h>$IzKr+fO>>W!~I&rcF(-)u>~XH_hq3-3$)- znN?vCP)*MEHRX*}N`o1+jLOoZoB6|Q$MBW&*HY$6V}&JrE}c2D#(pV??p6q^Cgrlh z#L8Y$s>AMhm{Uf#F@4SuCBZa_gvP^-NAZiw)i^j&GMp^c-i!3Cr91$6Pot!Bc9Z8Vtp9h9q2v1 zn8_rPW9L^&|9>EK%2Gt&FZ>-?rbToxMceKn276K;dF+3KJ^MfUyow@m^6WUvJ|pJg z7oQu%Fdf2OtxTKeVcKLDh5?uUkKhaZ=$>)h@vSD@E{U&yF~a}c*mBr87vHp)b1RyN zObqhjYu!wlUrl^?n049D(8!qd{^d2^t`4!W(WGfdadP4~6t($3|JeYx3GE&KdMt7% za>)b;S7LR>D4Mqp6E4Xn+l(Xt|1Cy103-O9C_iBV9bE>NBY|aLW{TT)SDx?V_&_^h zDMkOm01ds>92ltP*?lu?BUDL?Ob zxg?bAvGIh>T%<7*(eH>`4zl%W7uF%WzI}DmE|gy zVHHLshahRmq#cFM))cpWP7vqx_{TG>$Bxnn_{9l*$4wd=#@(|{9}uD!H`2mZH$MGFr1g9oUt(U>yBkGsUhK-8h7Db3FHefY|}bnV|p zqOpn0hwl?zzn)ZMGj<}5;@GH?gDTm`_Wz&Ou%v4FS&QRzUT(zNdzV4yQr)E)cS zb7{(9w;gl&F-LxE`H^LF0o(x>%Rck}*n96dOUwH1|9xF|J#~7Y+1b9Zg{6Zm9YGWn z6*Q*Xz7ycjnAF_ndQ|``bQcZmP54=`y1e5xoN;L%z$M2TrA_aqhVudS2rx z9=v-wk3BK>dA{}^&f}feXF31u1Rp-DhqG531V^U$*Y8eqMt6||lS#h#jpHXlD^IIo zMe|$%SDT$nDN$AR@UACMVNs{W=y=S5o}X`L?>LfGD{Q{->0ur}njmUzB93Cc{_^HE z0Djq+p8(9q%Z5~CCP#}%We|ABe|WFDJVn{A<=K+UkYjUrB+b^Ro0)F0xN6ZMY-RF; z!6huW$JlSXyzi3Dh(wvuz#tAKTYH;%Rc$YmlUerWQ*>l=G`K$P$pUr0M?3|#A2U^O z*&oy~5~OHwE7TYfjtqH6yG~S6LY+~m?$X6908hn0mDeAHn(sf;vCmNd1Oh)m7Y|b5 z2T&;L1#h}aQ&I~2nB(vTvbrviLfZmIp`I3O-Qh8E^a!7S{wyYL`w0)<-g!I-QO&Ik z!l=3mKo<`Ae9V$X7EeD{;ga(cbhep9kydfb7h=wSmBrU@f?a!kPF?K~)mr3yo6)RG zht*G|e2`m@tm3lsw$t2TFqMmWO=b`0FE24Z5%Fj5(k^%#o+>k)k7wBj0D}`SI27{q z%?E$3^W*LB8{$15T8;3oU*>{K(_FOHX8)$eJg{95PoB62 zdd=x2YHL#5|45O0ZfT>v+2Zt9YU*fPi%IkK7cn?pB$Y52pY$nLe)a7INu)TE~-U`&=ZHKO3MqwIjZ7}hCpwBi03H|9}Vaq3h{h}W$7VjL!BfD6p!Ag zJ1xTy95@_MpP69m1LOSOTl>k#Ng7D=`%CY`bp)O-D3)Scvy;@+B`H@FVFVv|Z!L+0 z;K4^rj8D#WP%rEY_`z3C^kzRcjuouAcwK_Wo+$It4>WMWnI#VN__zxNJ2-ig>9TE;Cj0jhTIHH0X z=h?ndC)JJx4v&BDc^?1XoY;|Ro76P8gcYAFukSzZF#7r;vTZ5sY)tF9jeO!AO`LaC zFE{)}j`~D}Gf#CXS7JW*FH^j*`@|JX_W}n`>27^&JmQXj-pfa}%&i$}vXaLiDX`-? z$?cDYyyhIq{=O4ON4{l|FX(H-rp+FI|JgZO;w9@6bXr@vZgDLeM+^oA!xx=Cy;#_m zptjZ|$j?ooZvUqwUn|CB(vrSjkEzo90Q;AX`2t`*UNXW;f&9Ty;_$>#TW%oF6TU^d z#vonm^4?Rn(_CY)G@G9V!!50u8n&GnV4~C5Jd-xYP=t zt$nQ&rycTnhbF^kyqx4n(xM+lU_(de7;js!6H^M>WtnzS<{7V+PBS7SwYu7|$ZA9C zg1|7UBm83wer7~~RIG6Urdj>nYKLc<`uDXKz>KMo-apz-RltWC(4XGHFJEkvPD;{i z(%f~E7Ezh$_)SY7XY>PNV8%1R0N0h2&6w@G^_1nt4>yuZ3U=@FIc1eYZEeVx{!J%~ zb!WTW^;pE?Pt~*IN6#_dd=aVuU%TchvKfJ6YxKXjz#`iy87f-rAJ1R`Nvp&M-)m4` zWANkq3-k@@#P#*B9xwDW6Ce6tbv(1VLT9^4O;+-?f1l=GKhw;}Sj64~0bl+06hHZC zkuy$lIOjB%i`TihjzJvj)rse6l||3jg;Y&O(okpM`D)fy_!mDvL#8VF_!DUp#})_# zt}F3>MdM+a)GLdq;^Xy?lRrE{97Y&PcTNC8N(@6_N`(=57^O1gBTD6niSmmdSO3uy zlXU2Zl$=`1gD0B(dw(wX@7$ez2Xbt><#dmWTP~&C~qedrsn(uWA`4(-iF4lB3RWIqyHMYfe2YMW7UC zuF&++wwAec^5AV9+;KR|r#`%!PklzKKGxMpexJcngQuq2XgK98 z0#QU6aO0C}xaIoiNG2sC(-CE<$Rs5@djwMjgJL;@DeI|CR(SO_4J0yy{P>=6#wViV zl=(J4w1AZ>w2#4&zL4|IOt5&N$>Ad*30HE>m1!P+yu>#?+e|toNhY;Qq3eiQy#o{N zFqb-JbzPg4n5JN4EaK6PWp4W6+$9{q z&!v+CJprnB^0@m?PRAtNm#+Z2mL_@S)pfk4ZGyj*D>>(+tt{!tFgP5NPD)0Nh_}9H zi2ebeOpW!jk2N3vb<7t4^YNk)colNJFE5m`e07S8&T{B#Gpc*;>V7@c_O{(MgPdD4 zLZ{)eP->x_hXy-YKX^KSu;vApWGBg)4jV^1c~$3rzTLANH&(1{o1oG5SX!IIHhdP= zrifW+Pw?2bPEK7if^?!eUHwQQW)S~|sPdlmbIKX(+?j;!E3L*?Z6M6527vD? z!hq@;K+O8NLselhC!ux;@_&K189YR`OXwiK3n16Z=EaXY)kO3UpaBA^l}&g#}N$nl==4OOUTgz zsn?#*mBv2mY7H9d42}*2oN&zI%KbzLVe7tnb7Xb5dLR@fND`WjJQRSsSVCTMh z30I#B3mVI_mwTlYV}&G7c}-khe}uK^5zHteQi>o}1OeRdck#uG9-q~x|Lb#}Wb^2+$M??`$r`sy#ej5=ImpFE;u}2kt@VFj$mLYq*76QV$&%c+4&<1 z{rXrfQv)yG@#ZXNo$3(Y-$2F+NP35P`cQ^N_AsNv74Cl`AaEd8ifK9$kzUwA{~(lo zgJM}ytYkpdQ{$F+Oy}cS#>0lE%Y6I$IqtguSH#W#HAFTeAy-5$TZdazl(xP! zJ@VHTzxSuj7=~b~kY-@8$XB;@^NFka86FK8pNzTsd?*bkcw}Q4VMw05yMyoCwu@~w z34Z#-LQY=SbKK+I@$EKhG6t7jrza1e{X`>w`WGkq6hC~|QY!fpU;9ol>*kkEN$&b? z8<$?&&-Z^c*F3ncLFE|NzL^!=Oq_RCg7eODIdC|jp}}N-Pe4OQf-MgWvc9fNE+6wZ zA8NwRMaP*YmH(5PT;>9fNviO^8M)-&I?M#dd zE?GK4gI#3WlMGHrjE;qjkHuJ~$15)ErEPU5J9gXTLW`W|GL=ivR_~Bm;9~@EWBNfR z1|pu@USZ=iW$J4UvKh&;#WqX2Ek*L!q5uFO07*naR0^e-LNTVT#o&?tPA;Jqr^gUk{{gV@#F)~@S5*QzWe1CQc204{XQT0U;~eA>_bH{ zZ~D)VP z9lmp04#yTu#gYY0Ca*ZdomKDqgSTecv_-$q4{w;G7p^-e!SJ*oG;KWTP?io$>npUG zd9Hh14aznc&q2CF5n%B2&N069r4c^+v8PEUYhwJD@4qwVU(NYAF3!2U zmWlo{;Y)z1GL1F7@|9_p*X0-qJ*LB0`|VUSY&jM@SIKI$ztsD0+Y-2LRZ%Yx+&j>& zCjix5d>Dfa6pxo%xNPwuE@(c&bVV^-QcQ(VtUB-wRg(O$ZwbYsqFAolsj4cF&J?pN z2+#$GS#{ZyLXpWxk_m~Z^6q_KQK@K2;6y@JNA-eaQj$*U?)F$8ff*ZL(*)br>y9AM zJ_0ki0?SJBG+@`g~oYlzBq$Lv#EsRY> zM3L4qXl*vg7xly;m6SAPE#B4L$JHktVQF&-nH2b@BA<_#9FOqJF{MJxvrm*s4-azo zNrO~Gk-#Yt+6DHMYPi3*kswf{;}Vx$n541ZAgWG#>+1}<+D)XXyWjd+gO6^#kkd0q z5kmi69jzvf^#-MK%)qcd)+ko|;I0A=Qi?Aik+IZ<{Nmjig&7!qQI#$>$St__-S%t5BX#rM84a1}u zH&PUn84ipZ?A%hIT#iX51PeMX)}52!oLBd<^qd}ARvr3PzqG$oF+TyAkKe6}IBm?Q3}JDO;-vwfcBK zUp_@=rl{Tb6j+u4Wr za9VbPrUru*%QV$(%L^XccY5sD?GuEGKYCjY-R(BE5prlWAYTp{90(Zb4e`C|)I&jj zD(3Dx@?8HG!6l1B_6`p4tUMEi#rwNA;b37~g6UjL-(biscjo!dms)6THc2Eji{eK; zi#gri$Dg)8%EB(2bW$)h8gZmAB$3dsNvRZb{=(0bSiJ<9uqi&f`z7z=BrSFf)>DoWluC+oYkFAK zQKnc@90(v-eAMyc^0g#C#Eti2HbIPfm`m#GcXi# z@JPTrZo8DJAY^FTXWt&5qdg%(HTer>)+35SUUAzkc^2AbPHyr!!+4st%MX*QYoMde zq^ZH+NMArltBLO`zIpRBHCc_OfA@yVc|~SFixyZc={6Y}(a#+O3da#t%CPp*BP@GG z&u?Y&2WWZ465N^u>4_SW(+-w$k&eCz!$Js&h{0{`r2XnQ({T2MztHof&pVye8LMb{ z@3quidd7?1r|Y_P9I4dPSIRI}Nzj=oYQ+8zbNut)4srj(1#LGCSal~N2YH@|nhL*XYs)<`B)BrA$!q^NfV9SxG^I_N)AW_&2*+3hwDuQxfMR?zUaSF!jF zr;=$-@l@kkJg_^#nWwsZ`u{d^*_j4Exw+%NWD?BB&mQv=fccn>=}E;{|4Ylib~Fl> zIVCJ26tHiwk#fmpEMd{?R9KXpo|U^Di2}}CzMt4JDH{f-rp8E$fH+ckKHPNlWLnb& zB15vq9_2_WOGooKS2j;E5L%?APr`^vO0a}vB$TAh0U}IQa%>)L z;q=B~a=yXaR&1j^Q^s{fReN7xT4J`FcOAWsh^qoWz7JDVx?o5qA>rzdKCH3_45`QU zGi-txGoNxL#t#(9q(q7ttpTCwhX&kvuLiq?kyhBd-rmjTSEi_Vij<*`$Mf|H!nOpR zZ6>F$ajND+f{*;uIDLa530H918V3Y)wwwH)_t!Hv5mE7U;c{i&dY;``;^>ix{@$o6 zw4_%bGndJkuf;;lO*iFu`&+>1aG8j9u|C&Mtuacc$dk*(q>_@3c9WjokS#kr)~<56 z@#Om{mlZGU@h}ZNCCQ{EH~lcr|Ne&)0b;+6@zyI$rYi=;g++{{rjSZsVw40%PC#rD zra)N&5h)TaopiqXdc3h=MxTB3XL^p**=tduLJ3{8I!n4~S=vo7I>F>!8&Fkai@cLY znF0~SNXsOf(S_QseY$x6@SoR{ZO(G{O(`aGF%R8yJl*oK$16OvUTZL1b$Nr2N`SLfpwi}?azJ^;A*@+>1mFHfuK>U7BGQ)F$Qgc-1A z!6;Lq;ERXO=0nT30J>8aKw7y>8Iq)7>AMb>T2`f1}FL5BwO* zmROcX{+CL6>S3DtF=aKWZ{6W>_qHr{!s4W*HbI~`ZH+^Hok44}!R_}Hc*mQw#Ie9I zvpmwQ00I)EJLh|%SZ}Doq9p#GRP4Cuhn#Z9L9zU?vL1n1(t7X=q&m$B_g=5K8!? zb)!rbBPI$V3l=8XKbm6aR3q`?)a>v7;WZZhhXSGq#wH^2g_xSGoHgf~k6$k43xN3m zU~*D1`ZC>rragnTterTn@I+r5qva$uZooy04)LDFFOW8>#e-5rRWqJYD6BZ0$%t%5&Wa6r)s?`}{*bjz6Rf(_;gdT{ z?Aqs(&&ND|w3hq&+Sz>T2F^b_!J=+Uy8u?N&*PI3o3>PVc1wkAJ3ZE{bhvMm#UqcG zIAx`cKyb#XF57l`+_c_euzmp#l-g(!V`S5cR82ra_$2Vj#6DK!VFo@j43L3OT=5Aj z5v7u%T#h+($mi;%1+MKH!b=J)UuzZI@MlBRXC>#K9rMrzt-{vQWb)**72fpjfnULy z_un5*TLV%m6q+WYlz@f>)V!)2+l@)4eN?O$BylV$?C)oC(++~E6NQQFl*<=BplFN+ zxh1y(+o)Dbx;jhkT zvXrM_+7tqPD@R6kWi&%|sd^ClfP`~0JzSoi#xN`_QxL`Q&|@Wv`H;t-j>%>uE0)-E ze~T@UjPbcUV>UijX4hed(IH(BU3qDWFWlI~it~E-$G>T0{lN@{QpD7lF7y^BVDOmZ zUC?O~RLy=K{*O*Z$0Gc2E-9aXc^!RYimsJe&ba{k4;AShizzFK2Y6BvS{?(V0dKiB z!-3H>FHAIYw2KF&7^r*TT@dEWk~Lnx(JZ_hmcUz=3T$Ilk?1;Bg&5LI=- zg%mGJ|2yO2CNfP4vQCbS6VO+*cw)GNYfnDJ5Bj@#chfF})QM9mNC-vf$871ZB`gX~ ztL=eUQfn1CT2`b4G+25Ee`?bhYZ@n6lFHBW#x?TZoE7u|jV|xDrzl&3y0T(*>k+PQ zJVYubNhLK9Eh_^qHv0)f#n5z$k6-cAst$md<=vOPs@$*8;J>sbD3!FJ&kPU{H0^Ij zHPAA3hd(u~n4FGqTtRJ(fn^CQUQD^9Sh>RH?9*M6u3&#pz$d>niDe3!n+#4GALqvp z9RT!m`o1EDS*C$y30`|eigQkPsd$RT-4=~@fiu=RTzYm^?V#jhRfqWt6;L(WH(Ewv{J7HiUgbA_E%|!aE zh^c;0R9&C6<^shq!dV<0fL%TQNm=)ghfu(p%lU5da)1hEl6#ThDpb%OJ zp=M55grV@I!D!@AYYJXN z1N!}pDswczeD_+E(gohyRW|Rvvk`lu!W;khGS-kI@&k76)oX)KeY6qB7UT;tV|$kH z$m1o#NKu=WEL~)A;Bdf`k4zCqiUV>XvGHDmD;8u(ol8 zMOnQwpW)m~si|*L>O-0l4zeuxz{2g+m;p&U(yD(|k)9~hox11gWM1nOt2=q!r3*9@ zpjeD?Y*E$q*B6^|Io4YJRX|}}HT?mN127ETedO~R#hgx6_4YkIUiW+rhdW4gg+uZw5kvo2(-;dv<}@g^v2R@RYc4QMA>OZT~Nt%FgPte&4wq-Y#FZMo8KPbf^!l~xa~NrI{D}&CEopk;}rulX~`9@&+^O@69jGz zFv0z|cJPUB?ck(!x*%|D!F#TnW@;+Q-eNu5hi#s`bIreo^1(i zMH@J)P280#P)l?l+?Z7)4rEI98gTWSPvW4XWz*p5N~A`K=eT;WMA%nftpK z9*y|s_i}vfL-kBd#ti!@mM+yR1=p>b5>@*;DIs5ona;t*RFN;Qh5;~`#0k~_aN zR|qB(k|%eqV(FqSeCy_EUjOPeHCc)0DRTMa)%p4l$EXR9N;?G24h`9u*De|)BP;l^ z;I83z@C4peOl2~}4ae~Q(>3Us`@8t_2bZ%hdV$0Hi&)h{{nk6@CK!P?Zw>s5#C!qp zJ091+xgO<(q*H>zi_mKqpHk0sT^qe##42ZsDAc1wfCJMh;*N;tC)-%r&_k_MRlR(j zRENAYy9y2TRnja=j*_w}><%0G{rX7;Dj7Bv+Uax)1V)bTL|#kD#>|pamC~z;l%u$P z^D0a#T(G7OUmCn+;eH&adf`Qz`beq4eM*6wkh5KS80zCG6kT3-i-B?0OjXPL( zj>F26B7S(6-b2hf5+Y`)fvzLy?zG6JB?o%~Yzs!mBc9z_;evA$v@{tMNy-qB!l~xGt_!rr^f-e#FYwG4@xQ zDfuq8P}R?Xrv3>8B9{8As@erhffCyE3FhP~m97*}LV$|6Z)^$4bjZ$}!Vob{c8Pkk zL_@kvgDPWt5r!8aZ3{CDG3|(w?~pGisrUk;F64dht|bgL;Pc+c%J{uKY@9gp!~EuJ zGW_7jb2x{8{agzl{lpl_7IavA_brDIVS=8cCXOrEvCBWM0GN+oEanS<-|;v+VzO;- ziJ+`d(edM6VH)^L7V|=oq029^B$#5yR2|cSMM>GLUA>>vlM(N~_cF}0$LY2T)S2KK zf(S`PY2@(Uo_gFBB|1_u!z1+!r0SV;BYJ{b0%0Hx0Vuki!ff)7BY0+{jib|zykX@& ze%jl`Nv#9C!SQ%o%RwHQ?qsdV6Qh$p%nHf><0V$Du(3=* zGO4EuyZ8AV=?ggPG)K$mTDses(J3gE6{T`qU7@Hd6G1UP8Btp+IXV#X*uf@h8{mnl zlUSM9gOw~}ghlX+B%F}u4#|RcNt10bQBZiXL2TIU^)pOYlAPoYa&2x0o3{qs|KRj* zRq{6zDXdfiL0pxlmMDE{RnLtXqauZ>o-aaCy-!?C${Lpvcm|c6OG=sRJU4-D&3r~Aw|>11*AZNKeLusGY~b6z;;nDYFw~yF@JoFEFYZ6#bxtJ=+Ll9k zDrV~rkJe_BhmO=y@+B~G+j}8xf+X+%qguK;OzybnH`i0*e^SgB0Kda=-LHil?F=3>E zfQF$djbWg11v4uNtNQyh>xn4V`~L-<7X5=E|F(N2yPvyySOF8)zO|B=Ss1iqq}^b=?~brG0(2Ozdgwx zz{%9k3Li z>ZUkkXQ%`MWeAkiLw%$IDNsV`BrjjA2&jJ5hd^_pfa?SZrS$4T>E{P7Us0dj?yd@<(SB_mqU#3s>&r-oa(G2Qlu_Ar6n%F)&uoD_0x=VBwk(3c}#s>z?JI;iWv4 zYvuRq_OU88PB}D?C{i+Da5BTQFLdFTEtYqW)7((u(v^o$C_cXKVZJeT8n^d%(ct=A z)iuIElwqN*JAs+{MG9uU2NdA>%<7Z7jv$kf7=~av7cnsvQ&%I2VwlcHlq;&r{htFB zhSg3y2s8z2#`ZS|_4mUnrEt*#i$}L-$aU!B*n2R*ttqki)D&YA5zlY0(A8mb)nzH% z#GGBR#vrKK$%T~K!(h<@YgQAWw?E)WUqJuC5&C|#od@f~Kr0SKv3v0mlq z@jE*6Dv5$BUP2cOLV^^!0~f@^6`#0J!I#jP@~I?}422G*=~`Ad3=`E)GoE*uEF_4P zKuJLAE?>-a`?8wwkNK+Q8TB!CHsokb6mbj>6~!nOBVvVA3K=UztSzn$6j@i|7>aQL z`-^opJ+*IMN2afNOh0rdJQ3SRtNTnp@a?IF7 zM5(N3YA`Y4BJt4yzVb9WO)UTbAOJ~3K~$Lyv%lA|z^4DGE*1~=1bq5kqx|UJm?%=% zwmx4gp5mPi+j-_i-j|ARVJk%`jxhqCvB)A6HLUB(W7SF;t%y1!#1z_@Q%bn#6$#cq zrPmmLbVCgbTMZO&_N$IEG8XaLtB=PC%sT=8VliI;{Eo&&*R(R&7m%x3w!T>8i!puE zHY3RynA}>tblF!=qaZ*OaU?R(3p87mfaf)W!VbYGtc@F<@@1?xw)lYFK z&SEz++Lm#dY5CpKoouC{_d}U(GkURFSrhlN071>de2_Msq_*%~?tgIR3HI03| zwdEj(C(=mMWJ94HFE;2j3sq2+4#iCJuFCb6Vxp>wUot6YL4l>J;+ZtWYj# zZ)SoJ1k8$>IF8UJMyB9sU%;j<6*g|Fu;yf&Qdx1s8#7#WZZnli%)z4}IZqM@5QdfaZuwHIHPWmv|YdtLB=tLim@b^BE~{t356vUt^sLR@QvQ( z4CWHrhsX+POBJZiN*d}6_8trv$+QFG46BXUg32L@W7&56*&A7QN{*4*&4s<6h^TdwudHg;$9QL%m)b8V&j4H(tCWFP9|B z5R66^L7XO3ipUUbFW0jemn9vFHoHihQ=;AosF4BoF#<-fe|3uCk%%+aT8vJ_T>8hW zSlSiBJ@XTQUpD3ofZx%`S{^I0y(aoa=U zSWxGBY$|kOn~?Ql7S&IY%NMzL#q)G$JnkN7V^L6IJTf)7PC~-)F;#$NL?rE)#*9yQ z*Az#G8p){?ixw2wRcWQ&Dbt!NV4H$v<`e@d#ig~=OvsRW!^AKomL(aTa&gTPh5@#v zyZ(G3Ca!Aw!<>X~v8s@lNo(Z)jN_laRh9p$3<1kJo*7y3G(gahU{nEsQV7N;B7U+( z@E^B4FniyVmfE;SwPo^>g$|yldEr5*ZHbjqvzEhBPi2t;TvyQAY;xf%5`-bNwHWmF z2b79Q#$Fg<%}NJhNNmU8$#NsZk;9`etfsAQk|oWftVoT}JEdm+Zj70m$t!`1V&uUjlWS{fuPqQ5 zU_~WTD#FS-gaalW*(qug0U6ULZTUt>1ej*XlG-6wXNP!j zWFd|jU=d>pg)I~o3R~)%jAIT?*ReU@N^`?BP9mUam>?BbEF8ixv=+rrHoe zTVj(d#GH5LoK(o4oFvac(}SU#xUpg4bUZ@XtS- zd!29pRvWkNaM`&xOKGyg)Nql}-U6fj$I}gujp4imVd+VWX(9+A@KD8oy|F>sb+8-K zsK_L8J(OADq4jePhHF+jJou>oY)hdiZ#~REf4%gQ&o>`Gd(0OAzk~6LYZp-I&&|@~ zUM#GJ2F|*uomZVSNq4${7a4qT^=2mfTzca)$&?-;E2YR!+%*db|2?9gn7$K$#aLkA$4LMw=%voK-Y%H`hy*oi$Ly-_Jo_#vcV^92cCVv1oU56nh zA`+;AWHL$-D+83mb^@G)R~`S)oEOKO`!nKGQ~`iJ7Q!wuluOW_nr6^584D$4skp56 z09V)UrGib$3P^|wDbpik9a9Jt6UXoS<;+)20bJfRz~bx>DiZWh*5jHXwlP-(SSm)w z5yi4(%WxeS23IaQM8XZpSuWF-Mcy)bJhy<$n|cYue|Z3uYu@jPDN2$BEt zIuUkjs|M9d0j5EnooAw~h$|r~Qbfafp4%d6IZt6X2;wLvRL4{LJWp|;C*a+u9|qv5 zP4mhBzbobofZxF|2J`HB8NPR-wUrAmZR5(dQ)HbI2Pd)=Bbyk56$>WlUNlvu#p#R1 z(|cAB=WKc+8-XNag{;nwlZt$jR)xv28pa|;h)qQad|{x3!j>Vco5!e01Q-S^OyoJO zWr)rNc|1>dC*L}J3Mw6Nra8iW!wWF1fG-cO;zNsfYs9ru?3k+MFRpn6rQn`iE3b)% z2ttKv%^l+murG{&>c}6Vk@po(RrT)m{Vi+8+(%0R7m9JU^Pig(_<_PS1W~LABN&^E zShLb5m5@|Com|pMNvsqjqk$GNGWAGW2>ox+AVgPt{ZJ~!l*%zt3`crH_8jmjm18bC zFUjFT4eM{3rjkqW?SV!fdHxjIYbH6pyNA}y1fBVQHm`TdIwjIh3YP#Y42g;!&p(^z zryG8|FPbU06)|d~axq>~VlIn`l^_Zweq`h4?P>u4L@}lvAdE9iFq)d`BJ*Emt3cjt`08)TZ z#44t{agYV6L2AtkW91sg^R*}xj^SgA05b{@flvRWNgM`X`n>C`{dClOB&&r)KD5Y( z78y&UUV~8a{y(n8HZ`l{(I-k|Gm`JzHb?o}zPG~qC(FG3x-99WHn%F4wa&dkyXKj6HYly6Rm29Ds|ZpIl3F_Lk`$r9VgVWqiNU}OF#yK^V|%83cXfAlxvcar z-@8VC+?QG1W3Vf_Kf3!wWW1_;Sy`2tukLs5Ip6udv)8lw+da=yXA}Bo&i-e8E(aez z%34PN2B~{&b{%eL!h7un3@wAzHlO|43?F}Y%pe(7X3=_koN^8k~jBQ1d6}4|8~?jgXQflN+QmEIvxXdk(Fzyp^%0 zQ&w!9o=d1|Lq!=D$*?|Vty!ldVJI|dnj@rP-$X_uDVRu#vGwkQlWQm()r#OnJIyEe zoS<(#mrTT~gC*Wmy~@uXcn({5CyvG_)dWWs7RM?Fe z6NG%MWoI!W{9Lsfss)2VQ2yK64umHi36$Jv4AJC!AcPryCByFJ z=%EO$%V`8HNRqJIR61l7y@99O4-D@4IfdS%x$Qu3<@?slLteR1;rZk^XD;m`!*Kup z^DH&C*k`Y>{(|9|O-WJLT!?2Quf{31rmKxn-qL(4d* zQ!0;jG7tern|>uRNGPIlRR9l|f;l$?Eg5l8D<-M~9$H$Vl|^*2nD#)k z+KjM6gEtu{#nd#c%1P>K3#UAF;h2jJa~Sf%lVzc8rKCseASwNm|9Fy1tA>C3?{^Y_ z-~8P$Grzp3Xtf=Mfiw=nSpuk31i$@%?mT|)(FWf*JBcwn3BafRuVu2#p$|O3@uSCB zd*UfJPrXrWXcWKvYe)I*FTs237kF}gg1nvMb6b84qqdxY+%mY_4Yvoq##H|G2CX42am&ki>T?*d!&=F48 z=2?q{hd1*e;nytqEgsiICT%4x&}qXps4#&%%BM}lXXQ@r;3cVRwQ z@H6{gbkDc<8;nDhVVyS~n^pTC#y-uF!wqCUgYL}gS-R5FzEv(gza2n7i$ zsifBr%794{#-W^tzdXIfdPnid+}Z608mL{!dC`|_(k&#eorgz zJ}_W!x=!PClixYHhY##ogV{-{`Hw@*yTFDQ=q$o zxC%}L8(X0~!g{Qg__F$gA8|d(504zh zOU}PIhczGkO=U#Nu`!5g0n>?PDlybW_)v9JR<3LqV|?(wl=BzOTM|9fM6y;`vdmMj z$uRNaLK5)Hzcf$1COCV+u-*cj44An65dY#Q&H$l$_SB_C$RJU0?}y$-^+RpG{D;rd zZoY9-anVeoZ>#al+HJ_`9GwhtLxU>}-uUs1uJ>VDW4PAhPrf{dsNM;@cHMLRt6!Su z-+d|&Smz(P3qJkS_}u*d8n@za7Pk_Bn`+JsHxUa)nk+V@qnwg*` zJX*o+GfkpMV2tOErOSNgh1+@mu`|@`1y$wv-0EH?)R5&`#-T_ zd1P#;sEp&&L!5KWn+{LfIgaJq)G=e0ecwpF+L`Co%X74s625otEd5@@e%a(~wu@h1 zzk{FI^$NyVwuhE8-6?9yUfw97XOCiJ;eW zB}or%(-+xWgW)igSMhhel_RFZ4p0z#|ah} z6z`b5%*@IrfBfn$9^IRH{uixJNoDdPEgY(?Uv zMtRTS$qlxOl+7YR8jmmzVI)=t_rIVXS-vmMbO|yToTdz2g`7UwmJK?|W47+~X;e zHBe5VtU?q8J}-hRW#0uKjm%LPL1DoSdp!2U_1434a}odhrz9)y{6TtOd4ap1z223w zI)AGI@HdTH3BXMh8!xWE<+(;0L8IR1kvq=N zN77ZAIa!b!!M#)K%+?*w!HMk&E*BM6WrAsVbj7lrYo^N)tUG7d$-N@q+`}K8KFW_B zdkKNSIL{N^S^7fq{ku-_YlF9OtiHt;&L7~fHg_?9V4J;h7iS!U+;Xayvf0&qaQZY0 zNvLdyQvJ=2-oU%T%Ufp7?5$yQd50X4SYVVQkoeRV$J~X1V8hLbvCb znpCtqj-s%fIGMBEwp_j<_}Z163#$bu+Ma<}b$nu;O+OE z!WoJ1ax7QCvsNUWDyE6N zVzm(58?7UKLElO;r)bpsTY%a@K>3Q47+@fDO0fCszY>@ ztOB3fKFoJl&Tw#IlQaqlJ=#Sp6?E6V2Z^9o6Vz(~@SP1MPiq2@YZX8}wE^Ij3Zqi4 zv9D-|TVs@tDc|)i7z` zpw)IFEw++tlYU6fTE$GV&Xw^hY!TJ6KUv3}Z=Z>A=Q)>q} z*%VaCsfe)auPK8Z=A3!8$75gU((T>gb^kJiQpEe0&{tFRe9qiLi#zMvY->f+q-@41 zn?=f2k%oj|7+m$N35jYjWj0Jzh8(JQ*;6Svy88s5ySSgz-3B7Eh}a?%PhZCT^2(hI z=X+QRv@3V9i4qc9YF1bW|J^^C<_A8};J5#v#czCWhfQZ~ z;K2ytsa1BE{#u7tRws^d&e82T(nJ#Jkf8PlJJ(SqmE3W3h1FhzQ_UHgy@X4$ffg1e z9J!Lzlp6(Z|C@5{?!Kf z;I`Rl^{6&Cc+|$W5C%V&gTN6x5PpcPmu-c^K1brl#RK!)sx4JWY)5S~*%L82HOlOAYo3mUo zRkU^-skgZ5s$^c$a}j+Xv5@AZso>1|9A9eh;T<#UeC+s1dQS1+#3re7oXae??_Xsi zvIw8@;QsS`-7T<`=2TUI^^PB$JWH<^7B`VFX4fZ40Oq17O97OkJ9!wtOOsFlwl?g5 z1_L+l?FVvRSWKHXVavoEiMmKBXj(_bLsd(Jgt-~T;(VyRURpK0@XCzkOM)nmlaY$g;Pv~Eg zbTdzFVGE5#ONW;J8sIOF!l4;`?@)yARpLk^L1c4U*(@T7u&Kalk5djSExH&|w_SF} zO{PSbSQL1n=z19x0+nZBvBRFFHiZced{7!^*)W;%_*a8D&wYE`*urN#x;pYgD3e%Y z0xxAz8M^&ommTR)0etG|Y;5lfp$g`ayyx}{EH=BG+?wIF<}53sfft@{iV1qs@pgHc z+El`%>rs!39q^ZqFiT`n1mKs}Q5Z)zb6j49lcx-qE)B_smRdv8>llgx2HP1Yo?PX( zp6mjDaosiW8$bCCe)Y%y0WP*1Fm5FPH$go3V|Sp}H?O--Yr*oOVzcynKYk=;Sdx~E zfy_!4)PRN5vs%=7^7I~>6DcUmKxyJhOEn!Z6pGEF#;bY4Hi}NK#>@RV?wMNW-l=t( zF2);=P!1J)oRa+Z<-0l5=<>m-bG-lFvn0}SB{#HOmCPk5@7c;7-Jxbl_u29ZPi*dC z*i$?-y}^BrHI$GTuf40gu8BOM5arX>7%7Z$ss2<3m*gLaY7 z@q!goqiJFWXdZ}LoNCv(ztO{ck2679__4uZ-dTHveCS!6SA6)MV&nMuBRMX@h#@t*gbW|!`9p;sqsYBHfgfU}0>BP#*W z6lmc?NxKC7NL-T)jFN!yeQsPCh<#2qv6zO(>jIBK+nmH0v@59jKB>=`N^{ag5J||L zr{xvW1^&rAhP(*LZ;+4Mr-)m(>n?O$TAy3iwi;vOyG&5GKnq|g1mtK z!0`j0s53n!Y1CyP+zvf6an44Rkkkbe&M;4x)po+9?(^u}RraMFszPw)e9nxwsAvc5 zm2-IK7#L{fj=>N*UB&vE;KD`E%F2LdvtT%MDCucbp;uCt*4GNw*NQj22Fy*sbNTy< zTM58T5Es_>kX=1>-F=gD3Ef@*i+4X<;n?9gnA?=2(TCGEv)Zy=uhYmgR--znF3lp0 zq&AyVu^G02wvBNLVr^-Qga?*Z`Hs0u%tQmUa;y|poV2uj%wMeChIT)`m46Sy(aAJs$sRpO1gI&S2m;V9w$q!60+El7~?yu>!ecj@q?0K-jypSA5To%k>os|<@J8SZNA5IYVn8yT zvvNW4yH^e)+M4IlMOIAAxUDvU=YN%qmhiYi8$At=GDyx3g55O8v_P|Fs300 zYmgM&iofT$l>poXvA?p#H@2_Oo@J!z=Z?JxQto|tid;)3Ba1bjRwoFAOxA`dEEU(I zTJ14i8PY3iw1*Wsd5S4KJs&YJ5kI^CMYM7x&d`YSa?}d;M-GXlrYz`9F83>RqnKJ{ z$VLG*EV)k^x|Dvu%Icub2j(yHmHr|Zdy}j-CzGeII{Ai8lB4N>#L^xqUd1hsY+ma17hbgfh^~W8`8X6*Ju3k{9mRy$dwKRlfpag41 z;{;<}ibxBFd61|MrdWbs{-wE~@vp_WkMDgLpM|iS4~awge-GoZ3SDw%;wX&H4u+nm zTeCbky-BSqsMiF4@Td2}ydf$E+%ftA$p(_G4a?fpfD09auwK?uxrO2S$1(^pHYF)Y1W&$?;av z?xr-i>umKWX!aWX>D5D=i)!p|T;*eTtnfR}zJsN%fk;3k@m0yk51r~1^Xhy zq&C6C$HVDtg40=pmpcuv;(2%?V?Hgyf=_7#voi>W6Jd->7&tby#&=Aw5E0~pS31)e zpfS^B_nu9@Z~83W!}hjgZrEZdU|~+t={YW4F`PMHaOJ9DvuQbfHYfl-G`r2EE5V9Y z>(HmIR)QN_ujlysa~U7~jv9$s4^F;Q0xCa2i#B8FE7f$%2{ zu`SrVobiP(b~wLsb1smTxn-)Vpmj-8X+%s`a|#DiTEZtBfWIg-#;EZ57cSh4z!yO{ z$QzGtOZt;BQaGftm>5LlNVQ>ky3O2tpMK_^%dM(90qQCT38?e{V8S~xVcB+?Ssf}q zVfP;@OHnkcgoPh+ek2qWcK+6Sy4_%V9cf8kc${NA<&zhlcE<&tjED2Bm`k=nnlwog zLElI|{=joQetJK?5G+;-S~BKpKc!XFkV5k5^PY{XecG)6`o@IYFjI8fj*Y7s+Z%0q z-C#u?MUqNIkfxG)RZ_1D>Q$&$;m%_*zx98Ad()p=PJn;&xRn6h;IY@7<%OrOF9+Ux zPlbM3Vg1TF{cXv)7Y!~qNbfncS21;8iuHn`5JAPvO01I@BWYB!lGGG-G<{O`)q4z# zXIKQC^^y6GEJx}n`;STrD#{=uL(f_q5(#XXgw?#lTDQt(v&zG_zsg7FH~GSq{e14y zQNHKS*LdvoL2?sFUB*cgZTX?2C;8LMNBPF3Iez;7=b1}0^1^d4YO&stxR%7lU9yU! zP@dfHa0OP{HLUeC(l$nf#c&eKtTIRmQ6%Zf1e72b0;w%?s?VuGon28M@1df>M}h-; zBNpbvNZ)qbQL73*_1P{$1pCxM<{9Rp1u=3Pj3U9wGdbUQHsiS$hkWM;g5NW7iC9tXrzm5W9lywE>TRvBRlzZ{8QZ8KN;W!^TPlwJ3OGag7y| z;G987f%g()6h)!KEN_jiOtC=bFTtp<^Cr8~i( zNXUw?!#7r;y&;yCSQsK<5h5r+I_ZP;ui619$=GNQfDbR8=gS+rLkmDSq_E6X3Z@#O zl#AX84@@Y>%l%naY{H{cE1_-Y0*6J2opDvz`5jt^DuI+i`nsQa(lkuFgozo?Fb4=z z0y~63*i#>JynY@X1=FjqG?$sst7IKXH8N}tQ#N#kzDelk305Q=%};T8y+OYxu_m-z z20hr`$Y^i0>390z9Z3?AP9{`qDkQ`eLA3&j_GsnM(vifT|KXp`@W1|>tv9^}Za)(L zom@*dYurizZs0iZUw%K!SI*FT-ALHo$0E)bIop@}w0!8f2|NpPn%zs9#)QUMjdKcT z%A1uGQc=;4>10SEJhAn}4utbq;}|M|a}F&aiNu(kJlw^GG9zJV+sMUa)2= ztlKJkk6hv4;w7HByqj;X?8Q|jjnuN~Dr~uwWfzefNh&XNDLd$+TwREtPU{19<+*9IviTF@I=__e`B;qaV}h%rLZyxaN5-p5;5IF0p@N zn}vE8Kd7)64Oor`Sfz<#H(tn%4W<40S=biuKUN*Ka^8Bj>hY#f}EokOuB-M%- z+YYCxU}0AA=p$A7ndjiXh_wwvJeguGoL?#UsUMl7*>bq89_yP0bzw-op}n5*_!m2z zIQ7ro*h9Vajt1$+=m1B_i1?6s^>%T7m6Osa_AyW~W zlsFRfZG_KL20rD(wF~^}%XiYxHL)<|E$sA_v_VWrhMhhl{67i&H7=9o|C#jT9sMZY zJ9mi}TC>E$A*2nv|9Hp2a`bpvCXWx#G>Nc5#u|?+t{w9Wl{YEFO2G()dwsk!R2vm46-A{YsicxL@o44I(v{Vl9d+hm5e%~4D!%{wr~j@m zt(z)tB>*>Y%+Th-iM2P}Ur7XsE5dG}lt!(jl0*A6`}amz6;oJ+ErQIcw-RR+h0qj3 zPenPVq9Kv+B-Z0lNIXh-241jX1j~^paj=?cdREhjEi+MGiu4F92^E!-8y~6-DxsKE zm`;tyU%H)VuIxiq1r+e?#scr&dx@3p2AeKriG<6|lzaAXf|SH1st>bZaVlhZjkiza zY!#YBLm$nAHfRm~T+u5+3**jO3u`C&{b%3KY@Bh4Do6Kha%X*$dC{k^@X3QGINh8= zImhlaVcN~p5c3V6q9(o^ah!5?Ls8$5q z?E-58;p$Yc#_)8TZqH#1yzTCk$6u7}MbW!Fpdt#o8yR1GqQgrkZeH+@;)K~fdr&h~ zp5ChQ-p(dT1bZ{XV5sODOV0@Ug~b>F2BZ~8Bap_TjKJj%bxnfc3`DJwDnP=PNZIrr z?<`JPwyKIpYNvRteGnfJz+Sl>U@wnQidv6_qRRU{p^^|De7zeXaM#2pmj`v?*fG-x z{rK=?!Oq$tXxo^+PdJct0zhkl&HWg>s}(Wcy^jQ^ybsKNv_O?5{yYyZfKvK#a=x^n zn4Az?-SD*9j`L=U2bVix3Ly$w8LPvTA6|Hdr>`tib)Jvi{Y|#ANiOFpsdH=@Nlg}< zx-d&~+tJ$|l65ldpbyRy*D9omB25Lc2Cdvq^5jS3jHP}DL8`eZJm)vwy2W*+a|^=$ zHgW6SyFugdKl&(#+(n+d^2QQzqb^a+VVMAg#b|L}ar=>&xRx@o5ymPa1Fn!b8*T-f zLVI#)NrYueXT-wOw=se+j+c7a_JVCBTZ5EmJF|3r!hy8Ip-Pv%Ne>}Bn=aveHp5h2 z@P{YwXV>m6XbE0z&Z4{1Xr~b6;*D`UxxR>5XF+uV^h zIXkRijpq<8?wnmCjs$5UkW!S^!J)LOw4hN}Of(d?9gLWslzjI`>)dlk!l8W;^}56q zp$#xHpUX1G>2sD}{kPlPduPJcrYBdLfAK4!)EPza{7XY#*`8&tV(_wH&>Zr_mpXjw zrMEV7x*1}6VTQ@YI=Sre&i$v^J6vV68;qd}6TB6aaioPn8%u6>R2@fF%)#E-(<>ap zXq55TLJ@ZzP6W{Rk;6%Y(4Lh+#BMusX-b_MI)ky_|4~14AE1CRgPjL;(2_(G^CO zf}P~R5eR{;wr6A0(kfyWD#KC{GYAC<2~TYAW|5ft8&^oAAQFN!>CiKpNnzL!lC#ZS zsIJEMGTd+zAr0xoBuOlYv?r1ltt^oWItt-viIhVNOQge5D?y|nQn0c4);<2M-mOuA zzfIgq0B+ET@tphSg*QD{Nd>l-2hffL6LrP$!x7W-X<*q~O<^>V1#j4C19(ZUG=+2+ z>8Qw@X_b)(j}#syAXZS3FrRq7xVpfr!+B;J1BNQciGGmg^{`uY=}AG;)%lLMJFCup7-7L z8Yc(ykU+;s-d|nkLRO)*5g~0@c(k=l$YC%q+TW%v9Y@kFT2@mNhN)yoLfBcwr3|pO z;p^EcMWrIbeDsuJm^;p2G~9kP=DR*n3yUlPkOrA&ebX{CEwRS)>gj^jtAvhoDPCX?Pd)u20N?$QI^CWl-|VrL=M39JzVxLw&wT66O@f3w4H2~7$5qiR5T+c^iaObVp*V@v2ayf!VU)eM zv~99;^a&rCIL~nC%hxh;mke^i!`#y!g#QJlLgF)W0LVZ$zrEBd?Dj{T0k6flTF~h_ zmgY4F4_DFW2WV+X6Gg2Wh=MEqDrehO26CE-wBYtin@E?{1uMw&n9iWiCyt(@(y@Gl zU6?j#Vxki8Bk9MWt`)>Gl>Q?HT6!YoNg_xiK@x?>^?IJae)g@$TdlqYVSk&r_3qt( zar_6~%d%+l#Zzx|{SzXneHGoDiH2nNqUP`c&F;NbM4ZqoBKkJM7)@k6+JG-4&IBWs zTtY4txeSTGL^UT?8K%6H(nv5RpeQ0<*jVKK$5t4ksbG*Q>}*UPL|G2U9emnjeOTe^ zv80nKY9^xVs|Z^Xs3?1wLa`@qF*o1ijsqP&Jh?(UPx*s$ck$!LUc!6$e_uL|L2~~5 z0`ES4k(N#X$!E?U<-b4vHJ49ghm8yb3^gGaYno){39ek4H^ zFu49nC9uY03WsxoI0hjlX%c4qf8+OC{PAZyyz3nmjPYX&;$iN&a@F8GOg1D^32IvK zt_LeYe?3w3nhn0y*)>)q{Md(TJbe=O8aRJq$XCDI;p=bV@OCrB!tM#Cmm}VD{CWC4 zkMR5+j=85)K11(DQ9 z+_;p#1N?pr`r({^T_W(-;GZUfSPPUe7%xc>qzG~$DWoJ9nt`oRANV@1E0n@Pp1bLR{8cxZyLSzxm)p{j9Ur74H!dH!?#|3({uOVoe)K^JPQjG z3AY`HnVytnXtoPY-&~UbSWtxvu4g3?Fh^1-M=r~ourw+;PVFp&Q_^#xpKx2f%^j5{ z1Fy-fVqRrXP6>oX3ARj1+osHDfvMyaWb|B$wW?e-OW9KAc=XU~++FK%pfSWd!S*oW zL%Yu*1U!EFHom^PAKmaYr)&K5@n=~>aCE-OFYJDbSNaLx+^%rC)u6U1s2j_!sXqJW zE@AwTUwF@7krj%GYAAhBeiusAxe5LD>ym(y zk>-KZ-B})(zQRkrWwhMc*^9DHu)_!NB^eOL(z{|LICVV*K&b%!X;Mzl2v1cS3<@hO z&RcS8iL6EAn2B@ZSU?_RrL`9r1B1*3Dc>lNOh?95N`<-l;V|_7rQGT38w+U)6Lrbe zHAD03XBe*M%-&by$*sLSR9m6f_Z+WoQ%?okQjthIw!!WWV|qnQZY$ij_Y$|e4qr?( z`xiR2@;<^B#5z>l675MMOC@n7;9Im}Y*Hj;_zbh55OP1+4kZbw_Pd^t7OvJ!O^i4!zHIV^r zeMka~3{wFJa^)zbBNrhFD5PVu7D#EM-Ob)whXWI@pvtAuDUT^&SUQjuUQswf-zs`G zv>gU6LL;fAhQ3!}H-JSsi}Qwer)y{g!=bZZ6O#RVBF%3gpW9w$kH5et zTF>xw`8jPhE$bJ0{P~|W`O;tA0)K${T?xmI4(Jaw*Z}$vT15Duf_MEczf20eEN?qt zZ(sLVgg{A;QXY%I2|4+j*E8%#5l4ZS z<2?+Ar6Z-Xq`n}RlC`|T)vUt96Km{Ad(>n>o`=ekAhe@`7eL^o)T#mel>{fnm@AMM zL7yY^<3%|U6M$C;xO_E~+K=8*;iO*X(y&6E2@a;)++S_sg{LhvL!rmhG`(R&-d79; zlK1PgJm1z#RV;^QTU_ns;0=`$?9<4Va@`}q-vRFtAEDcGoW86%d(p76l5zP>I84`u zXe58z%j%|%TM57o7FRn9+}8aDo9k~Zo4x-dQ>1$)SRtluVrqHLvS~Bv`*;M_OZq0F zS7-_s5f>h1KpTfEz}cV$P$)eH{!tac#vV~KBuN-IGS-)4N24-+_y@NRp6f2Kr_yJy z?om-0RV}GlK{XQ8onV0CSf#@Sswg~(%lO37Szag>h@ImOy^ZuXv=n6M>t=)CuJ?h3 zm-vhI8b;SSmrc`?2@h5_I1mk(YvkNt+ory7n)x`xc~5S3%Co@O5z*T48jJ~xs&&Du z-Ldm}f{LJEc3QH%?fB!*b~$+_=fV3D7UwlehDA5;hxcN7Qt)8NMlFDy@0D^W<GgFF2U9vd2 zMx1PL#Z-xertd84#!|&G6C0G0SnJ7pg1ilP)h^R71RJ8tZkMxfMo?QGU z;9sdqmhPVBN*2*76uVW2{c)d$&cO>rx#;!EHb7xDg%Rk&p$#bGaD}80np}Ab;mD<> z5Q;)+E)S}>bVwBSgZ8=H*%2uz=vl=Rt!1ucRd%O6daj^rBP!ZZi!$m?(zlYnm*h&Z zzt-i^>P7y%zeGdje182PY1MFNz0E!bEL8?XhJlictR`$=QeMV>Kn9N(8v?=!~ zPgNCEqGG%~v*nnSl%X#>iV^sa#`d(PRBscQ5rrSdi>eSXHKj-sL37)(XO}MLm_2zO zwADk|6*^tdAhTn^pGqqD@NyefmO!2J+qwmY}Bi@IU0;S9Iqhp!CDP8_9Ev|dK)Mf8l zfhoW`IR3Ik{)7D8>+Is#M2D4O zgE%%MXi~4K5K&WxMA@M5r^2=D3CX|+pp8ae8eJ zxAmXq%xiB1j<^*!PGMNbn>?B+}rVr=~4p5R!mvTL8-Vzy&ZtJ~elmLU{_g z(+0TUYbc=*(E#fN)3rV|Wdg8C!P))yqu(WiDPe{K~I4*|SSCF(FGIgA!CrCbuX>UX%*J zs;&iC#n>E4mt%^d&pLXJ@H{$tlgE@GJTB9gU|h;Ygb-npgKO^d<^52e^IZ2cqaEk9 zA}`neeE3>i_n6UnYXc!H>j{h#2uIlQU(@%OULgEvj{F)fz_oq<*K-2O`-JeQkyI}} z@LiBt91ej+mX&}J`OdSE)i}^-akXfmOO}5~0sXL;Y!DqkBM#Jw4MkFF61!=YonCAqK>bADCOYUT_F29rCsTZV4;KW%RH zbB}z9pS}4!u>Q{CRswK?#7BPWE_SIJSFgUYUtF8i9JynHexWFxQSlB6<0u5w(mr)% zn3%~C{Yf$(lNm)+fHt6wM;VXK9oEY66hI~%S51vhQDIINI3?)|K}Skzx}c$hZEb@d zADTSFxj}=A!wI@3qH80%CSuy=q{>m#1p_~1s5E=y0sAX0sxtKd_Uay=oIS;sO^CH( zQVdJe6L2^R;qb91Rv>eM$ps6MWmnQCaRs*EjmUpZ_)%?_ot^Qh#4kixV7IcEm$v6= zL^%~zP*H}-s$+N6QJ5iru|C1dmGfk!Kf7}~B*Kv~-3UB7y|7?!H7$kltgZJsdrq)> zVU3HI!^-}#4_5ie`>PCdPd4;eC&rb9>%miuzaA~PuRrhKaNjjY|GK|VTo1k@6*;&* zL6X-z$Lqm;RQVYlgb?9x^E>yAp8pyxs&TtWl-p=Qd97L)WD4hkXq399^dHLqfD?$< z69C6Rf6cCcTpie%0#M42Z(2CC_824@fe|4Auq82Y^yY*rfI>PO5-1V40BTGO1W~@g zQB`0B&fbT){d!3ZmE~xjwE@H`uBIu2y)ZJIyQIS^jGE5I=tC< zH2PcwG7=1B#HNXYeux@S^c^kd_`=!U)a?LU*BqW)3)lYZ!u!Cj8DyRdn-Q0{B39M} zo$ZVwvk0KycWgKR^CYhQtpdQ`DsCkJH%PpEewn5AOPoLVM)Kd$2WCkt5$#Sw<~*^7 z*au)wl_!ZZQst;C%jCjKPIh3(z<50#Utfa^VJt?{DCTYc0ywnWyKK+5D zs}()1X!s#jQ&9CocIz%jD_b0`Y;kE=<6<^Jw}|QKh>nSv6hTX%s)r1{;$US+g<=fy z5K8C^YDgN1a`Z_cw8!8WWR}jr;tGL4lBqgJCvucBbh==w%wZLpVprQ*VYo1+G5xXY4RFf-AZ*Q>OcC2mK5?FjG)jE=6*?ImEC;}|Z!z1-= z?m4Rrmo!dz`(^HT1lQZYj<^;=1bJs;Ldjov0^#qP zoxd-MfYS5kyL~5kJqO_0W2_uXItO05MfmJZS=P5DXQh&gG0-~CSj{hp28H*xk_Iy1=P%ClE58ZHDlhZ)yA!PQ2r028*+IvW^XH}9_LI=Y>cZ!sHN4kBaMfoai5APsK=J+eSO$UXn4nPTamXS^0vYjA!+CuO@FbV zaU|ox=?m<49bRtD@N#R0ejd>uN+wXGE@zK2WI_^)u=4DRd+bhnbZpGZutBHLOh zf?R=OK$a_F8IUN zTxZ_XS}Q-+-shaUw{3F}618&|P&id*uD#D$-}k-W`@UZVw<=$a)x@;n(30i8-SGaE zf-Bbxx*gv_(B2P;uySJywNC`{!jmbf&;TiF07ER0F5T?4Wt9xn*ugBgSHava1m1aa z!R)@rZm0-tGjyR&JQj~B$5Dj(@xm<}=erVy#l&L~8L$S@Tu_u1>%SiTj1Tx!z5hBH zpbqqRa{=53`2k2z;*%Ds{G}5<87Pg$AgUj#ONkMZSon5;#*vl^DN4_}7d{C%#tEp< z04e5enkbUAni8!&@ozBn-;D#B{AqfgzklllfA{%6@>0L*^PdB;D|oK4Pf_~MsC9LM zTG<8LYVc3sM@~B+&~pQ;+Bhf-y-MK3nxcF8h{oX!CP+V0*gC#`aF(@3&f`l1)&lQ3 zDWo7Xf{B5Va||sEtzc*aZf~AqbJF9NpSjK>%Lm9Pq}w^57|+jbQ8>mE%guv`JNpFw@M;q z35-Bc@IPGp2!HeR+brvBCZ|%es(1>RWN>Sour&?Y8fhNa0}g{Wry2$8NlBi2(p_14 zT{Il8a$#P@){N@Ks=xlJVeure{M@;#v{c5s{Z($IJw$(>o$(!pB4hJT$(d6D>#KgI zy1lFFwQddpOC^Y7*gG)X+$_*qaQ;ldl^X?F?s)RCn8UuH(Qt%Sf8Sa+({|^%V_G@E z&0u#ihtR!u>g(P#kL!EUd-g z569aJW#KrShS*x~eRmRI=dA!&@c@nm{d)+3$1k@)Og#?t& zO9X}Rt(jI_;N*NB#$o~>JtM&Hoq17x&jAN;rv3w?_7Z;>5U|>UDAGA5^jwlQ?T}I0(+Cs6arX-T+xRj2MZ|wPz#!B$hj~)`ozRKF! zHH^nz-*x)zDwl2tJU_~4Hs(o(5Z>2ZD8YUj(kN1X?_1{?4hDSc*}d83yR&C_{v&N( zda=#UzD4(8x$6^wDDcUO70eQWX=c48tYKbJS3T!iwss887Yi+b1qV9-xxf_=)$KkI z-xaRK@gP1+9NZjrm_4x*a}=b0e-+wd?eU;r-?rE)!FfnHOO;ProWNO!uyr=!nf*S; z5tR~P5$eC&7vOk8;1K94_-o}*$|9T}L#vggaAF||5R}!W6%r=|A%;*oj1ed*7rR*- zz+Za(zat7E2qKb(VC$&G)@F;X!zOo*noQLajU|O!+9w@rPPQ^qX(^>S_VBhZ6m-4uP0S;*^4AQr@;5zBN2WA_|^v z91?|sq;g10Ge=hVp+f*#!ihLTNQfec^?VxIem^8s*({L5Vx{c=Hq|7*II84S#!Iy1 z!u^9l5QTnMEd{KvDF&l;UU|LCxeJ$=rX?p&DvXls78#5RhGRh^@o&I1hi`;-2Ij=f*cftI}$bDk20yxG$5MuV2#q%(00n|^m z@La|?(%h5!ygjTZ`e8xTL_o^|8PI1)?(U#p`vSP*34w?IYXYD}Nl)a{%Al(o#A_=_I95-tefFW(qOW_}7-xf%fBjYYz*pDw%BI zd+*cK(rS8@$9U`^`RASr>2)PP_m`TaspFq~!*HTJpdI*6iK-MOiz=B!dI{p`F!O&m zJmT=yAY_A#gG0;yq2=bTWa}toe-d#t4cYBWhDRCYGy`joC5tAvj0PEb_QUu7C+fZl z&`-MVCjcMvm1Z&dtv#~wy%XXddGQ=8Yp~Ip&`^e^wyY!>X(K0X7EFf;ldMIzJ>=p8 zWB$eH92@NObmxd(V@PHd3QJx_+&WxlblBwT$q|>cHour$@iW)me6R1Qg0anMBN56F z;^F_)YEr96ROPEy@A9=?V4nyhuP|Q~7Wq7)hX_l2^r4Vx;hobP1yS4t9=LL+%mhse5LTErNHNCo z&igr+wj*vI1RPBx4yFNnM}o;9#iSWnU-{Q|!qz&{Ny&KdqfNOS5dTQ`zrXHX_Y;5* z@p|kF&#{z^d1vQd*sReCc=Wj*S!2p-b3$8;(9W{no+6AQkkATCPOXo4?16pyQON5D zEByZO454#;X6X)}>1=T@YOpoP43Sb$3dO)ijBLbF z8PP9&5^zMoxB44=^zj`IjtsB8kr8MJ8ZC~71}zmeWeUqbWV_tf;Qzgcdde~mw4scG)jE?G4 zXR4E^ghy%yBg=NN#P$UChnoF9q=N}I&kzfrp{k2RksHQ?l(P8YHGL~*dhAW^&jI*J z*ZlmqR8#5de!ek|aVp4Dg;Kt1cLuNbcgz!kyF3A0b=wEY{T5Au zYRG>P0nk+fprs#Pqm(5#+P45IF92Kf0FX0ofEAKPTl3Ia_`}Xce!jWOXU!hh4jb6K z1Zyx>V{E~G8Pk`VK`1dDhY2K&R>AJ1!z=ra_YRl&*ohkqRl%~(Xe)mLot;bgJe*&V zKwV|=YlN@z3m?>LEq|Q^IOkhkk3JM)bi(CJ!`;-!1FVB;Y_t@DhC`Kd5^9m6X$683nI?_qOWcZ_1|4)7T zJfrOXjEtXj-A@2Mr0em|KF)G(xw3z+VaEBhAuqm^Ad?g+4S_64Du3}XP#9rpw=KW4 zagA5sTSvA>><=QEgB*1z=tYi8gEh8?ZTeY&lVD|86=0ILcGr0-K15Y;=WN!w@WXqW zp`jUM406NB7{*4CT5sf1k1vfv53Q?u<8ZzQfBCROM2idUsqgV4 z=o1r$B~zgomx7yH9WL+m=uaYANs3j9Ko;x`V!rqONgi0=W66zBD(6I!aHLz@wk^)M zDIoa%?gr=D16Jb+tI32DjfqDaVntAClEVPk|O!+3&_Ww)ZW;ED^8-!Cdwg27)y6Z2?yq#T*ab%*(iH%~}Q=Zw)M>0pME!cefPk z2iImrcM}lrZV}Ydule&c%Q9@~IGhGpr;teB2B_cfLMuR2!CqFXfSD5DZWVx=2Y>4X zK^^#2g|4W%8=z9Y1)!~m(rW^suJ`~(NJ?zg0uY3W;?&uY^XCk|s;+QtGa=nAU{Vrf zF<~xPiKfIZgHYpEOI%#yGQ&rEJN%9FuM$?e|Du53IozPHH3x;JCkwhVr%5@RnUGcr zka}#c6uyc#r6++p=MjQMOKT}Br@%<^_5GM1eD{#`*s!uBtM4T|aWS0D6p*r#o(A5| z+L-wXl@d^r-7=&ha^lFMDy2bL!VktvjLnogn+7``Ls3+Fga7WjiUDK=nA|WOq?qzY zbM)D}wokm=`jH-Uf8D$8CjcMPwO?!y-hPF2aPP2~PkrtLFF%qK=zUb>6y}Lkwf(DS zPG7#&nD$Yn%nhAwj2WF%Prw;PY~ zmm6xF4~oiL-IfTnbXX-SqM1ND zhm~IUZK|N*IRbOlK(pZo%6KbSp9s|afSHqE)#RTC=y@(sZ^erN+%Gp*3M^p2HL^2P z0f_3l1s&mH`(Q>UTzKwTTcP^BC>;Gf@O^wEec~|d|1(bls_PxTQk7QDl7eF@fQ26~ zDXpOYI^frB0BO)l`4&Kx1RR$G)_;RR62t-B6+s*u`iD7t6UE4th=xTE1;G@;z91+K zMXoTVrYyaaX6WFl@BnEYY3@HPMd^4<4?szBTOZfzGvp5>*16dyJgV1i={pQ!5GHI9lg3z5RtVKR;nw3HAxOoHf}n z@IW*5q}8ge@OZP&=FvX4ONBGmCuI2JZtEv;4q+|XI3xW4{?fl`z4^^sY`pN)kNmj% z>-f5#0DK77f3NA!Ctqyvr^RRZ)$oseR*ny@wHyrxX$F_J+I(+kh1KOT zZ;X5VNpX@R5i^-aI0-0MRsI$9xA1f6pvSXmOzeDxE2Tp@M?+;ar9?;wrQyuRu$To|?^1p9}UrJm&E zhUV}8!Q&kE4{=s{n^#B00r1t5bpj)*V6VJ8qZ9(CsuqE>C?_!1RvZCXsS*LLp^;Q- zi@=}zdT0`&@+z1Gm*WY6^{0M52&cZS{(D9+TqGXW7QXHnnow)K>xfead|5qamV7vG z{xg~iCYi<|5l$Wt`kt#-p#h?L5A|RmF;4)TJf;GePyVZ*f9>vR1!(D=0DbUxXa|{@ zL;W=;z#x&4pwW{AK|wl9*^`bjiiwb9fy1umkjIEIlzGTWZP+eDhLbkqyhVJV`1*}= zTzf>4woA?gV;+i!Tx?9xx(eo2P#_5^xtw!e23Q|3DShwVf7X28UkHe6vnl6DVmU(v z4*HfCPV6)7L@f2>Ofp-RbLBu33f>#6@j11R!&mh+;a4eNy&n~t$yAU?QzZ}c?@l{( zeCqTO2aj&>`Wq3>T5tx7n75e(l(``trdac%RsZ!`da(0jJ?{RRulotWhj1O`tGLa# z@2UQUfRz_N!ms_*A68#lSFxXK6B_?T|IVnzKfQL5t($AS`0yrQdGI~nSQ?@Ca|Wsn zO86u|y6Sfas!9Nqhw%=Jn2>7X8y4+KbW~6{Nns?hGDOmFEpPMpZa&AyS9ds*jQCix zSFwNR^KZ99J`wPU05e3u51w&^!Q4n_w)Ive-Xa=MTL<3*^|MgD zi}~%gdX7cKfsl3JpC<~7ZGk%QFSZS=fx~G)S!kqGSS#l2|HZ(+zyq*OpehNVsK{z} zs{rcDs3QOQ)`~mU3Q)=}Bmn+rYJ$5^0D%ZJaa$4uB{s_lqY@W|7^6u`&D2OT1@1%+ z6dX=E++v+~+-VMr1a&A-yO!M**dMHLrhQ1RGE5lYltok%HACS@r1jJN3T9QdRQ@E{ zg=ESnZ9e0w$8ih648qSUg~*9jKe(zU@71zFf$#grk>uZd^sSl4Lb>2IAcP<*1fp>4 z9k$qt9C6quQRQslR1gSGM-!eubI6Sw2}gSpob}Lul@MC%$R`=ualx;B{LlE~bLTiQ&xfrv9JiCh~fgY z#uXZ;B~~k3rU-ROs0>k1Rs0Kwq%8d?)GYT>zA#iYVgZR~R7)wa#_`du5tmKDg{0sF znyqZiUe;hLB$dcFrJn^DqY=`N z0G=_&YPSE33jnn`z*#Db;7SSb0gix5x>=upP5^YXR=_MFFm-#tAdSKxDV@O}APCS+ zK^T@Oo6~Ms+HFa$6~jV<3NU3zS%#ECBhMO|ddwf+InS`4ptcRsPCts)z5$E8=V&>b(y@m2g$tfMjkB3_gjH)ih2&eUPdZJ!=RmVEpP_}ZVfaFY?v zme}e(Wmb?4r^nhvKg#;@FP&yp{rJhl{pHvF1mHuq26>O{&K0uJ@#=qdS#ka|r)b6# z^1SlUU#QM%l8zAYgw82S`0T0c{PWuvc<|I7Ywm#G{_dkVEoMR92mY%6Unc?8<<_W4 ztC^vthyHUZD6BUuDXb)rmRK3$$ni{kKuhFgR?YgEb!};l<7lGU8#cJs?-5m1e~95k zYshlb4>uA5a;KOUg0d)S$&y2tu&PFEXCWJ5N+ZfZm7q0NNt{wBD~LivpuHVyeL(!Q zKEQe7l!Lx!RxdC4z#K(_B$kXPe)OxoX*(Q5^K{NXWD!k@m?V>~%T3grWT71*uy6`*x^h+q0ZZ*A547ryr| zrSu3x)eex>Rhz!QUp)uFBLZ%g2n2zjMpvT&Y34AMZ@q$9TVQ6~v%nUdley--d+875 zu_R%^GQh_KG3O2(^Ani;;uEjoR5Hl5XWCndSdjnKkUwVO?-?1;zpr{U@CP{p2HI7w ze^u*BEg}G3381AT&=&11+WGsJ&NKh50+~pn$dG75BLWphmuTi)}fpm{oOB%1Ulo zR29Bb%mz>W^UD|b`1%bxNm0oh>$wwx=bw%^92oxPpG*m>B-K{!gran$sU?ae?UwYT zf~#brZdrj(EQNrbQG=a9lf9!RJ&~a>Gj&f8c-KqoVEd@U+G@d*kF|K?jR=?dR)8^< zVv>?ietZ`HSAXX=U;Uli{HOOpfS+jHPXInd>zPlT#!OlaC-?Tu{)1oX@TH3rY_&1e zG9B=+p3>eZ#k=3ld$x~s4;UsIS&sPZv+&xL2g#A-c(^^QAWx49h*=VVa2#f_huFi? z2Y)9ik(4Nw;*1usJ8AG&dY7=qk_2|HORuUxV_@efW`C5hGj4LCF(Gn3_@7@snpG4A zNR4Ei3ZimKJFpn(d!y%)0q+dfc)WE;L-{FyD23AsS7-ul3ALLsq-zV+vT_2fS<;iK zr8JISH)N@+7z{1nx~jQ)tE3Tm*1fLgxW|#BT}&v=nBB6$q>wZtNu=tk2@-8tX<2Lt zchUyCZ>tESQ0w{%t{iQ`qj}Xj|0%tJ-RJEB) zVJohHKvfKJFDtZ^M$8d`9yAk=2y_BT6j;*SlLZ}BfUZ7f^8me||Gp=H&k+L4@yFHY zaOMzLGs5rQyq&`oj)Q4PR%o14SSLNn&r)SW9^$8}^6TG0yp=cr03ZNKL_t(%Btd+@ zpA!M=$$(yNqEyB7pH=+FX8|Z>3AFX+!1#86QkK+cj8cdwAW9sK*x*!1E+i9EqBBXZ zA<-o*mC;cdU7gdG5k-*`*b)EZn-6nh?FPdvVCrJ5hSTkwFJHLC@O;Fr{T6$HBP|kc z=MI@Wo?P0e8J1}6A$)7*{gt2^@mo~xn2)U;&~27z<&#<|JpX4pb>yYvKmK1n#d`67 z@r9QgNZ~)v-mBlAh?<{MyJrensS2nQK|hsIVd#WYdfg$LTTPzdD3MaoNF-6{xd?gg zC`!$dh*(`Vbi10PUEd0@c|ks!!2RI;|M|L~0DOqn!Kg*HeT#g2JahikCmOu)Ts)gU zU(4;lZxUKFt0bKB`{*W(mkt4`Zvt%G!ND6W! z1(JgzVMR_cLStlr5e1nGNrl6Pl1JhJSL^|2qXGLyacS729ghjadQc6FQpxVH!Tub`?SB*fJ?!f3L&#%_*a)w|Mm%t*9R3+11VoA~VG~NL@fI z%IY=z?$(qAQ>|I=Iij_gUD`}rU6z(^@N570S+dCy5{*?BV=LL8TTBK#{BNpufUWFn zr4VR!yd7XG-hi!$gR}cNiNL_6h^#tGAFCxFUe38 z)n$$(7n;HeG=_GNvt5Q<+VA0b!Tj`f26@h!thDeIG07U( z?l;&QCLD|#L>N|^W6mufvep_epan1~B*Te7o0LRZPBd~3@)ptwLg6?Wjri`tIb!K} zym^3<1wmj?S`ukD3t-+yz|COQMJL0g;bDGbV1K{O_uhA?fg!Y#yf73cB-L@B6WM(7 z01%0iRE9KVfvaSI?a0wv7i8U#ArQI)&MfT^IN=e8LZGBXIUmTC^uc-_LZl<BfNZ2RUCoDTf@e&H!r*xDuUi2#8DQ-LAgN@5UI`FLNu*ckHbHpY`QF|MK?3NHswO1*;E6Y`t4g#M*)rL|B}3d~y9fzP|S` zkuW^oIwVmAabSsqIU&mTQhg|^pTBMa42G85n}&DOCf8`v9}Nj6(3X<4F!PoH_cG&! z@My~&+vL@0iLR_$=em5aHC!de*lAq$m2hx_|E> z{Y5lDS&OM>4OD2)ET~KWIZ#euMRgl@wF4>{p^?@*+tp-2y^sh5f<`PEO{}jH?L2tT zs`y1TVAdP>Ad;ZNR#(*MK>e8d6kEU)a4^-3%U}lob2s5{q33^WHh`P|ZWrbR%;0~) z-0@GkSt~%^r3BDbVxW~Jz>$O!7sM<#1%)td9<{J#j?o2HNsKOVC~^gvbR-H6Y{K=; zb*|oC<8M9pJ(AL)-P9{`tiYCn(rI!l=^N0kjJBE*OIXumt``ZPSl^^W!DOsij#8dl z+Nu2geUh)FZ_CA%p;kSq-V@)9{w98n?b8o3_tIxlwc`?+kjG~hF z%@b&?pb^6-f_?f$##5*IG#dk2Er}_8o1!RuQt`xkpTG9x_xT6keTs6$5$`K>(k2fA z$bNhTpnYbYZ-4VWKKc0{`zh|PYTZu&J_PI2KeNhUw8At!uKR!dV#pVND&odrle4`6 zdqu+g<0XFU`a}G8kA8zIJ8Mic$utxE?(_jpt_``Gb&;~C@3!E)R1QMu2%JxPqCok)J;$}_Y#9bftM z>*R&za?$1SXy~O$Rl8!noATJ{Lz;1k5+x6=9ua9X3p4mKxY(_J11fk^H!wwIt zBZTu(DTHG+m?E6vYPw825 zbRt0_tN8&Xl-8l;-QnLaC1EE|`0ikpr{f(iCFhu^h^5h6eE&+oySq)!v_?F#o-sMt zVR)D!g+(iY4mDw@(OS-exJLkdLMLQp0_2?U`CA|Kt#n8$u+p~=logf4ts)E7Hnzr9L1w4}ySpm0ty93^DAzRQ&m zOj+{6+7{MCB*HSxLw2VL4=fG&^y>aBFxLa7gn)xIVl~Oox{~pgt_te@(j!y;7<&}3 zG6=GDp!XGjeIDaTc&qK$a{y*)f(5`)N>2hg6OAihi6Nr$wy?%?Xhst|dyFX@H+CcR zUP@^N=0r*(zCfcJGrD@2$=)H({P5HN>eXy&r2gRV;QjTZt@{bUhhSa18DkD^QKZKm zv!4Fh2goAD*`ep^LYeGB3N5d?Ch0{ zUe7pJ9I}-)h&z%vkffQRC_sO}6jCW+*CgDYBs?tl=!l%-8cUHJ_)Ii{M>Omk0BTWz}&+L~DQ`rL7^8wUiJHR>fIfhR|(6yXCE$!$4I0fH{9+5e=!U(~9SJ z&+V{~9Ly31-zq5#p8cO`ilWk@F7*Fr^1r*)0QVpR72B}l0@Q&v(B`hmfAgU4hXVQQ z0_|vOFA4qX)yMeTFTLeCF{LE8n#@SLI_16X750Y-Hc%L4m?}+{_ehmw@34)?O8)Be zKVUyk(Atj}Hi=Nt*5)yY6Ezui?ZwzV@EvQzy3h z?qH1{CHg48)>RVh~Fe*ys$7qC}0hy&+kW!jJS)c~hJSicH}BgawS@Xk()zkKlq zZ(dvH;>km_DmW|(*3}VlAc-T1sz&WP2XW&2wr>uW5YCbd!Kxn9P*Zxbp_Led1X@DE zPUTmw1nte4^m3gB6}z-JX30m+%v-yfhGuqmqgWio#d?LI|w(pChdV86I5{6&K^5 z9-iPAS9WM5f`9n>1%CF-rJ3!p)`H8W z{G+uVx?|Cs3CaipZP0<@>=O_0@Y5%_`u%HMfByh88hwB?M+_Y;7> z;OpW?&!Yx8CZ7-eIdwu4KKnGyNb=byuhFwp{_*CMOv`|4{T3@}gEJd@JooS&PcW0- zFs7zr=NWvtwMC*ONJnY~!ulTawCdq~clRVn&4r~tCz~U73c+D1Q3}pAVb?_LrZJDn z3D!E^8^=5 z!lR9SevqEz*S8+$=aw(=rHz}MSXI7HDJ|*L@pqoM;!Sr%!CFJ0RDdylDqa)_q^hMz zenZJjiAGoYd^acWRY^jLuWXO{hV5O009KY1jYQI!77V6>Q6^`o1T6Hx1r#Vwv`QZA zj9BY9R+bg-4!dZzPnP>=B^naxG*7SIVy~|$tw1=35)P>qy6p%%psJ(*Yw0cT^3=II z*wQQSLk%|%d%S&nl}U+b_DhIDNf=4A^02Ze6#8I48z*eNkGrY-28?w+ahQ<>J;y-W zxx&Cmf4?mEi9ovnjl|mv>%jMXYvNgbJQfLowWaU}EdetFqM~pdrJ9KeuvQ_YTp;?* z`~1h?ekXjCJ9eGFUR0H+vvRKUSAJMh6bTxMqM0~4O^6$oC@GL}Nw;n3#F{hBj3d(| zFeQO3Xat5Om7#E?Mslh(rW@ouwXx3=t9x{UF+#xC@0>zf%U+(Kg+XbHHWm6Hy&r{5 zEVeXQV==}s90$B**IBXx9p(J$g?D)^UFG)<&+yB=_h!aS)3^sK1nEMvRMCR6u_64wh02sbn19{?GiBe5OEI9Nz2Xs z2G=G%zI^%`M2ewRG)yr&&r*Sqf*VII4%38q$B?aQPCU8Ku{O>~9`tyRQE96MX#H zlrJXN_||);_`T#LN#KY|!|id4)3!~+DKc4bvYGOooim=vEd+sbyx6%qtBkb*=`2n` zZoDk$&D}L#IXXk2Eh8H-$RjRv`ZOZLi3I-P;Bmf`T;U0|OI*3@oRuk)35+MMlF0c! zd$HZ%KY#rN#8V}IZR1Uz)_VXHM)I}(X`Tx<`R<^_i{SxzK36TAkNbRa=?Y3Z!d6I2 zm*j=7=tSUnqB|v-Ird3k;owbDg!JU4nvtv&C{#rR^JKkxS>xtC(ZXw{S)z1KQSvI9OA-3YZp_Vd`hx zOTlcKqN1Lt=upC2EXx=giN8(YZ+oac;mrCq%G@*av~*-;#M^h)I2?vPDG+`RKpaVe zpswthB?G=4P|q2t+6v6u0W})nXAHQRZamE_$pqR>h$8>L=cQ+lTQd*%$6){Jx_Yj_ zB1ur-d(wj%{?9eGwxV1d{d*4=K+LQDYE1CmEdkF}Py~S>iXe_5X$pddLpKYeW=`Ch z(rKkM!;)^K=ruIgx0iXaHRe)(1!Z$0Rr()k2xUni3c7L5w`7l(+jnqfL24b1L?DFV zM0db`miWO!#=8k>wIC~$7Ta(*W3h$d-+AdW53QxVk#F$oL6`qMTOuzd|Hb)lF`dqP z`lW&Y^WXt)zju;nmX1ivh<2QN&kw*T4@s%Y2Ou}znNkX`2oS<+^`(Hk@Ck2G`oTht zM6#LpxHehg^BY&0Of69WMk#8T$2rgf-nqR_SvGn8(OcF2;r9-x+6z^(a;iOKIT-Mb z_fD~Ptsryc+qQ63s>IVZ}z^wF44f6_5k8Qdt4q$j$Fa! zxXG4l(UOXBqu>!eWI34f?cGyc9`yLg@-`QPfaPYv%8DY(9K%uN175kqUA}pa2iG&U zhY><4ESjm&M5Q2)EN9#y-@cXb((;6rWnYRN$yj!8F zM=U!xo3-FAm#ZWpa0G!Ps*(kXqhTB$?`)H27Mw>A0_C{2x6IA`Hp2PW(^ab`3gxk( zLp5)p9z(n+G4zr-fpeC!N(82vC5{B0rhhN3hM#>fo!*52aGVDq7Ji?RJWI=A8Zjz8 z{BP_2e_h$vaR1$`e~0gXRO$kO^1W{<5kevz=-9B-F6eb~dYz1JJEhx7X}6|ynkk7C zI5asCE{f^)Qf|Mq#?5wbdh%zT=#HnBB8NsIX*C6L0&n#@ z4D(=?_y&<=jO309=*W_m^q<4Aj`_qh*SMC(oK{nGg{0NeW1p<}mcw^;*U7aa!_YRC z#&U)cC52Lq#)_jix}01e(|I@}YFZw9IONfX)_C*vkU#wPlyv84Mii8BM6|w4Bi`bu zo=bgy{Y2}20`M1j^`{Mvwy(_oUR_cArJp}VqZ_b4P`v&6c{Vy@Hi9X6tT`Ar>6dLb zCr!GMW+yH9Jh0YCSxr(}!4AK0{xZF=q}$a<0b2t}Y)vIWgWcUOpGsQ-sZg$ytX~Y-Swiz`Yt%fF!1pR^WV@Z`C4s>nWqNS$%gIAwpIWPFR3vaVC zHvCj)pa1sSOZ=CQe2eVV9ljtZ+)P{S28Mrh>(hKr@9^)oU-!s_76gGKNql)zYH95?i{MMCIeC*tHPK71wu@Ay4(~NJvzt64B7Ols;$G}mp z$*i3PZzZY$FV1hUH3}ewLz#KyFNHgPdyN>l>bBSs##KuJ5Lg~cMx0setd$ED{bJ>hhxna92#wI*DNvwjYQu7vQCdN#IpxIin6GDN z$z#pN#tx6Qj_5WgB#|LCn#{B~G@9H65N#t0!qkj#lVs2gavpLL#g} zIEA)q_J020|6%V%QN;_pP~AX4cwW)m!zxOwTlC0|ppiKr93Zf}%lib5I0> zA({%xR)nmO_n;889JcJRY*DiHf&L&fOgp%U07VgmNn!_q0Wg?->6zZQ>e{Pvf7`v+ zAMVSnZit^`h?pSzPE=%ezgO8gxxD}Xo^$@^Y%FnnnDMPa#Byz$OWPIa#)%+9BCT=n zkugZ^OoyEQz+kJER*>_;YL&H(7~>V#z_Jt!I2by+XenHwK3YxqM0lO$M_%CzuP#%o zBz)!iA)YyWWAgbOI|Ggq7b2DAmR{c>JVH2xFV8L32;#_n&!lvkiFeK1LPTIj?)P#d z8KCG}kDgVWE>kYEk>a<$bDGmvk6 zPgmW)Uh6>v@HSn?&drkSh{+cU;3q#`<6FrE?pz7jy;S8C+r0DS4dzvX4Fo%3mEA#| zyGflvLoq8ycnW6X5g*!rm4=r(IJ}Gv?GFskcU!!_y+fK?u3l^K@@unPzB0w~k4TR0 z?-SURYqy$QxjM~TZ_V-QiG&|Mx=r9)hC@S^yG-&wzIK=&S-wu8;X<%QBrHoan+yWQ zfBwfG;2(VSS)ypfubp|8B8TO8gf@c1-hlbQ)alww*#h3>|zOhJyT_-F>qu zsm7Akje;ySv|1j%=b-JPa2K=jI8>fl3hf#K<%tR3S!jdNuE7up0)IR+?)YrVQNe$5 z_no|Reus}YE|=+mOp+Caa3g_hJA!Ln!L4q_Mn2*TFCXXZu^pZ~yiKi_^ZEbh7Dbvn zDj^mW14}3OD2&17vy2P&UP0gcyL(W9ak0Pe+`Xp37596NjT;8fqG~=$j8O)QuMLk@ zS25b546@V~Tw0r^KU4@TUYYC`_$AsQ-D@j_z0X+4i3J@Z5Ql3U5MAAt;MumsgK2oUrAb(fv6)1^N5{ZWrg6YutP)zw5h4v_nqEStmZ}pjL z_1QNw;Nq+MSlw*#<<%zZb8Gz6Q?Ia**6C#-BkhrBk74dnsF0c!tZ2)vRm)PiK~Gj0 zXod6?(t0SXk;Wjb#fGzZv3`LEwf^m;ZX3-BbON#AEt$jQ%K?wNdh1a;a)#75m zg|3XFuE*$`+nH-^s3?!=0f<7-83cs3#-XKcbm61BMcDwT^0wgC?F!c}9p&t)EuMVp z4t{9(#UE2-AF1=df40W@zJo{v)yQ&S{s98;{a6ngfVb)D4nlgHcP4*+{K+XUU49aA zazOv37E5zo4z_lgYL94%lrS=MD+6{%176#n<$QgMzrFM-S_{H7Ls-k6`{rI#@xJB` z+Bh*+zwhzk_g~??@AGL^GS+m+7d!j7*{t#O2eu-?)2-m(yAP)`eG@e)?Gc_Y$c9c$JX_OFOs1h)5sVTy z8W%-wH{Kh#-Lw=A0myR8_Kv3AQq-zqG6x-of?CxzH+pP_gd6n}!c7}wxg||q!5n+3 zk8P7p0cqwO@J_S_e6YUB6V*HHbX`;8DJJS^fMjGD3{!$Z!c1Ktmg`%ExCQWw~L?be$r4Q001BWNklhD1ZqyS+}NeA3h z+$neU*6sGk=)go+DDFR3ioJ$HMix&v*w`!mb&o7|(78caT&944ZW6K4t$-cV0T2X| zz_XL2LSqd29%>+tAP&J(b`p#1-i!OAg28Duwv*s~_+K~_Kqy(l`X2Q_GaaT(hXbbl zgjnX}T9Fr$ypYV*dam%d`zUGH=u~*+#vCi_5rvnMS0D{FX(7pSNhb|hPb&0_kfiWQ z2-uhPn5!59-*R$xmyS-@)ge1sh_tQ=kX9p&hi6;^AaKoz^$h3RD`c4=^fDs9;Bvo3 zhP=mD-k(-Yh>qUf+Yz;L!hg(IHTuKrnNVMSU zFyv5}uru=b?%f5b_=p)FA(HzM0T z-uK8o$)!tEyzz32M=z}L=zH!ER1E*|&se^=yTePD7Kl7eMY(%nx-7#2Pw+SD04h$cm?m8yUEe&3a0x`etGd#@{CC_{N6F! z@yvFbS*~tX>GgbyiqCVm5AoEo+b9M5Wy+aqpTb6bc5RtojknOAgBJ%QL!qJHH`J>G zK6&&FlFZO81W7gEjcS|6T01;3yAD~w=Pn&0xM8`w(_r@DoF9AWE&2mDiyioGq|G^< zS+X2vXB2ZY9vfQ)JG+*8T@sZsrJLKDqJVnMF~2u@1L}mtv4hw(O;KK2o=i1BotLx-x#`Tw~Y~@gyDUeZs7x|8t7q;A?+fonU8Uj_K z0n+Z#1%eVzzNerIvquF8<97P$J|y9LM24bl7@(vT%BDeCr01FeSq}0yLQS>WEhY8t zTwlS>ttl9%6iSJwEP-G0%mbIK7=;i8u5l7-FLQn(0<`Y6-c5W4Hb9FQ5!dm;fSGE} zOqenq_LvSvOa~*B(WJQ~%LPTD5YjT+Oqp-@Y1T)q?Nqq7HpTjGg{^LlYuhasP32om z177S;^UAlL!ith0v4Yvy6_S48(`@aKhj3c|a&NgP)@@(MA}^Z&PfBvB=;#XHdU1hoytbeEp@jV>wzw6= z*ilA4NZ|JUrf3h=zuS7y0K5%Xx9>69*#IDnBuh`s^5bVVxe<7L{?bYIx3;Ot5q_8v zgqh>ZJX%3$3vs~+QcIgh&zTua? z{KJUqh!56Qxf3gHzrK$T{pdCHIGHdG2m^Ov>-G$BB-p*f znoz4qwts(xt3!*hl2*f@u2$Jzt5VxHq_%&A2z`hHiqeQ@>|Z|~kMo2hKV64G#6BxabVqgQ(N*yn*bFh)|u9+3u!HA|B zqH{}84y@Pt^KG*5+Zks(uHae22*r)e2>;7nzgbCq4BbXN*v;9|GQ z>-||ahYf!E>_wi~ze;apU7(5tDKr-2e!fac12ga2Xm7(X4n!BSh3W|HB^((4(v zYfYY>cU{rv90blQUkHmZuBf#_?Ht`oN@I*b3{syoGlYS_ z_soPZwg!eGL|5yK(l#I2f0M%cH2fa>f(}XU?0^Gb;VCykF;yL+4Xp20`OaGl> zz?AORN-UO&%E+X2vCL6QC^Zd{l39;8cbg1^#~|^zqpILR&Ch5?DTiviTpKirMTVqc zUJtnN-gQQa<(qeBc_W?W$O0v@XOm<}~X=9A}!q>wyZ z>$BDk*&f8ayt<$N&a^o?)#v@~+eBgRXt#1;Nebv$*tyaoj3BLMn7qJ*Lok}|Rz$PX zL89mmYpf?VHv0{%l!%H1Ef9rk0AwL(WCf2_hg?=|-urZeZ+v~{FY~heS9U#U0N!S+ zKU8Er_u)QzD&`k|BCU}H=1YOTs}G3Lb7HuGVM0G{g~Aa1~ab^6b_lg4ZeB+qTmFhA8LiY-TF z!tG+7rxv$Jb2~u>{31ZB0<<8_d|v7-a%AQ@wK(U0dHo4~a`|OOYd&6N`I)n?^GZKp z;p{dadw7k1@CP5_z31LyS16Wn-T`@TQNG0+ZgXoTU~^mZg|7~&Rs^hP7+Pjo zV{xm;8y9Qb$=WO*kQ_R210mqd$$)lK5$+W1YxEiAK2sHsQRdU@M|55Z@mnd4r9LY1 z2`hms^1}HZj0=?&ZWPZIEhh}KR!|s4j=&U5ngd(Tj;nhphI?9o_uY;g3B~x_ zd(emS?;QG|CzOR!qW@tFCF7QdgwW{M@ie{Y;-Ez+^I3t9wY5Dj65=5kQIVVOHwUJ3PGv`sS(r`GV;WS zOhO@$CGu4bQumtLu`m=8;S;Nz^ONy6utSFzTOY{rqCjIJ9&jG!v5Vyz`_4n=5z?Mq#L_~2E&GQBFNNW zeM=Ea`bOZTKDmt9=+xQl)fp5KR3t)6L?PUGrv{M=L?I~p9?b(aq7|QqPWtzWqaUn) z_w}FwcpI&QM{4xCj+>jB4*BGVBDU>5@?w+EzIdF67w&RATIa;V8oey!t1IVuX5pKB zajV6%x0i@tFq}TWL5gHYDeig|L|t%ddYk=mpJp_o6(mH)!Le~H`K9I8xN;kwU#oM4 zSw6N<@Ri<4mclNdICO<-YWNpldOsgH_4}->Wf-aOvAposA~VgLM^3CFM+tv({|oH* zC9mzwv0wD~%#|b9p2Av>fBdh1h)?{`i+nrtnH4F2@996~Y8rB6PS9vbdOgFd-8u)W z9kRQyr8DlX6?A)s`08PXpZY^iV`mHzpkXk6xSEum6z0>kGn8dh!@7U$Oa-0dsexv|Yx{^95ObooB* zHllwkN?1uaF*V>uJk6`Uqg?k3PG5kJoVW!*e_+`+H6YU&qughh`;0P=sn`{W{>Z2E z+7#YY&eXv^GVlm0ez{XEGZ@A+fu(E)99VCRne1dKO?<2XV@t1jBS@{9?0UyYf>Zmu zoxUd>W#CD3PqSbr6vPBIxUZ2g6ZF9=(FHk1A4*E%K2)OAIjGX6FGKV6+$!zb5Pv^h z?M!3yh^G#`$(t$s#y8%@-}%6EWCir|5K$Az!l}wBi-gTVm(jSo7~hw)f{_~+)MD@a za%}JN&KwWT+yWsd4ZzV#hfW&ONg~$(Ajs@q129g+tcJPE&5(v`8`C^KdzX48;Sb+D zi6}CJF8vBzBqKrro+R=L+VOy$G@z423`~R$e7r#M-(2`MBNO10K#s) zF1NR)C_+gQNIHq6>xFc@h>EGO(XWA0h>ClUM2?#o&2353xlbxWuywOR=owa5{{O`Z zKUjO~K?CqMTD^fJANKG=kM}-R<+b_KTz}~(w(=SuKm8_JLo7x}CGkAPlkr_{WGRo= z@9@;AE$j)6m6Fwdm2a<2lckoUjTH`7yPU4}Xjc=Ia9?`mIe0wH1i$(1_i^E)TfA#_ zoA*w`drv$IK&>iS-!%O5XFkJ=FMHhDO8J~U&99z)9TWOIx3a+8{vAGZeHp1FpE~wB z?Qq1q4qV~)UOI*9MhGELefZt4J<7Rrw^&In=Z~)QqYEi>GagB5*y_P$(`}zCteU3^j2$b6agCGoCh|dMl=k=W$2dX2Ka*(O=94Dy@z;k~1 zW22zL8ooWM^PaHJK7T|INTd*4yHRlQqTnlUo#0&$rF?4U4gQPYb{EA{#{wREB;??t zKQSc&_jqwEXiiB!^zI(-%U|Qji4p%|?I_>A)aPi_=U}~Hsnutc`3!ThCQ4j;M9TkUlMsTmoG6&B$b0p|cLEV*?*1(rg%l*B?fN4#URTdZ^&^a}w6 zuDFO&gJ&EHFctzDD|meNHtXFfI%)Vl2!J3L3ZtMF=ETwv2}@)(Gf~2yy>XnMJO4UD z3qT>QK;-T+3-$pwi7*!RhGFS0DFvnI15-}P0FzvRF&$>g=@uy+3XmjD3$wVm z&2FBN=8Dy{L54#Y9%*jy$UATFxj%S_S4VS1y#bHUU*lBJ#Va!i#86D((r|$6E zU!1}Ql0q2dP$7KDAga?3D^RzPSZr2)@W$?sc5F^uf*VTm<(Cf}`}7UU@Cx z(wj8~1CMvUd%&;GU8EjM{?*PYp4fMdom}(D!><$iipyD@Z5>cafNr>F%ZDN8!OIsH zNt%|~+AH`zd}(c#{+$kfk?`&V8~jQ4Fdu8KBE$%#1d9tEX=XVZ?()z=hoJQMzcZ>a zACK^baYjI%n>+r}m1Ry&ukro^8~C0xMoG(^jxkPXI8Ge2!tivhi;r@#sTQ`jHGle6 zn=^~s#H}9hIJ%7|Jbw0>0ZCftM?Y94&1~t7?~HG16-gWmH$o+0akl5)lXZiA;Vo|6 z7;*4GN;DJFsHRwx(ocN`na42QYX&BbLF_ZAd-O+=-kS|&<`&d~P@q4I& zJ*og~88K_hUDKEjaMC_oA_L<%NL0A({|FD;iPt3jW_$j&VTsICW^9*-DRZy}HE1CpMUGjUW(gB>}g)5k&$f z7x+OAzT)b3oAb@>`z9my1qE;Fkhvg(d^~(1n2It!e&!-~@(K@!Ox^=oe#KNd&N?>Z z$;-Ik@p3{(H~y-%vu&0Nf-)OmY>qW{!~E%E7fH3pl}-yGC8uV$xjt-gFzQkBat{2> z@AAd&eqP(0rl~dWII=sLuo1$Ab3HM&#`VrL3$uN06-|gFPtM%t`AhpiSxgPA6j&ek zWE3Er)1rqB%SgR=GgS`S{yx5?V=3W{{Z>{5%L`&h_my5J9P)p;!3J<;)MC1k(AO2n+`r`wz*B}#J^ChL;F_6p{wga`!5y0X`km7pvnvSOXWdu^ zL(5EC5eAll(40BH!{z=0m)6?6)M@eePJfrCpSngSha@#Tz5h06W_Dd+D)YkcwFw4y zfl^8kC=Vr}QgJYFuV+Y7%ai*ys8%FrkFUAR0IT@PkJspQT|x8%i!Aos*&1~@QDKk8 zZH0!rYnn%^UF2Dqs(VBgi4g*nr55MZqKvGN3^Si$<}pb9k{WQ@fMM)2toaNQN%wM{ zLPB%7hx8?W?1L(``nH@JD0l5+I>3&Fge7WVkr->rLeE4;V5Kvg+M^BbE%K%2U~=Cz z1eWsOEYHh~M>=jO?m4dRp##(yJs39uRukgUFACZXLw^`^bY_#fXSu#J#qstw+nM5x z-6j`T=g~?MMmc_15JovcTo6RsQN`mQDKJPLZfufULDdxa+K?5v6ULR+xVijVn1K)+ ztM9VbY0ynw?nO5ZC$N7MXd;=r;x7!5&_rlLH1)8c9gTSH@(j~)7nu~a8zUC&4o@xK zCJQ3gM=)+MeHRB`IPd=9$daaeu(&aRbm@3$WWcaKkk(Y6EVB$p z1}uz#$EG^$CIPqa)>z%BA%tN6T#v=6J`XRgacQnex*m|Nc@!g$plf*lk*in7e8ZMLfe7n zj)Oxj6ggz6AuH#krQEagHO|T%gfki%3=M;!y8!0XH4@L_sT@=K0`v!l-9dp-nnvu= zh&?>z0)M1*%4^SeQwIRMn;Or9Q%3`QA3C`!{<(F$X6Yk@A6S}RN~@AG%sl$3YX%%O z;4}imich=d(@zwg>lHF1X&&jJ0tl*sE8r!UZLxc3K>0e1bpw+f{X{!ZQU&+y=sEx{gmV2SR~p)QK<4c?Q!l#TVko`8cRobJl?!POEM4S29$j6`gwlh=y%vneY$BtCkyDNA)Pd$5Q18i6A8ELk1#}{yp@*FYNmpmRxo0= z(qVNcMA!zi)gDhhb`@etKYFJpOERjt|vu^X);4^je*tdB&#`Nxm&v1c;oTRoH#{M6Dl7Uy5#ch^pHqS<9IfY&z~RBu?0 z9`15*dYAw8{h#AMd-?qkCLD`;cv()GxsHD<#*xygk@Gx78;j6_eCP~)jCH)|7&^={ zNHS+s;(2iVc!D`=(Q@dx!}5Z$)vI$Q_335-$wtWfN*y5-T1yV@?{a=|l{g)7xmG2O zJ*th2Yr|QGLZp%;-Afn)c=yaE%k?3nL{a3H$7;L0Vyb*#Hbobf+D@O&PRLYqhdZkg z-x|&H?&Iqy4-|r{of+OtLVjZAHoAa~L4}!0|9f^jdr5UsiGhdyVomCES;WaWmQwHNQ|+JQZT+Cx1fY$HH~qJ ziIP&hg(Z$OaZsR*U{E#!gRE=$i0GJip(aBfE8{qa4aMoHvnQxJ(#`oN}a*|jf7$BplhsUUpQnl zkJ#C&^5b(?_}Z1j_{QUJJan1o-rPrG1KwDfp;=EzjAp16eJ=tj5Gi;agH1w;)S&BN zTb8TKK0nsJK@Clff~wW{+E7>*y*&P`V_Xwj4H9}ujZPN2CLr-i3Xf`#Ik?}t9sl0W ze~$tPF--YGW~%~I6s&cteEqHcTv#$JSxvQ?v(-~{Mv_4$8KlBRZg2af^M)+2?BqV3 z-3kvcuXE*2i{JUy!+h|es~nqMo#b3dDY%-(Jl5Qy`&P_MI4q%hG0~xz!cH79$Dhrj za7ICU{J%pJe9yT8I^@VwiCu!FV7w7<0>kkta*NIdzrDK5d*U1T2euguT|+Sz7vAny zS?|WYwB6>NtyMOLihuLf!`P@GPXykq<@vYfiO$_63lv2ZU~P&u;vU{#Vl_}&RENS4 zD9bQc1dWX98v=hiXZ1l8@b_)0Pk;K;pZ-BViNEgafBt9xoUm5o7d~0x^x+&z6u8RbAts}e*$9I0sX%yN6RprR9&r&HconS&k0k@+4c7l(wQ zd;YWjkfpFo#e+2~`NJ2^adoT7$%QS(6IH(FrUQaNAUwfF<`Ec8C6;IA@pzvz^BJE%b_P_^`2&oP$?lS$1Cs4QYXOp@%EAqF`CUZ;L+Ay zo|?VGYQN3?#UAJCyL|Dx$M|M{AG^bVxa0wJ#i+xSpP`hP=z6p+*^RO> z6Xm3fYwRHO-DqGGxm3&X1dEh{ICP1SSz#y5-ClzykXBPCL zkO*$pYka+PfPM8o=azQ(^UXz0Ha7YFYo}T7O%ddlQ6d`28O}FGTl@>duN_s zed1ZRHzHoYvWPghL7G{5xkMF)*jS8|6sE$vAHKrSlT-sptSkPNik#3LO2I-{&@1%= zy@5wN&uIsaZ}ok5VH+iutZ+O0YApDJTc`QhzSk$bWR{f?tf9aJqO@8C1ZW{Bw89us zg6*QT=yf|_S?Zi60*UJcWI3QG1o*zD?P=Q8ltJpmg8jssj1e{~9)rxIvlY-!6}9~% zyh@HADuhzs!_|++0*#{$%su0T<6XQNPZ*R)!9+ViIXQ3-bs)#^zr7zxaGm5tKQPu7 zI8?zQ0NO%kB)Jxxt!)rh68_b#v$VHUe*WYO%pBPvyd85q-lF1LMn)4DMW7X-@d@LC zJDY86xt%X!D5{o)N|#5bR{7%k0aRh|C8)p<%N(6bQeBRgxOV_7JJVuxHN2I?QK3bdyW6&ch2*} z55K{S$qc(z@xEx4f74%P-XC(7EiRn8&Lby5hcGghVi@qb%ga1@BIRFhEc40Q*J(~k ze!{=bRG2ZdoO9~fMMfjb@4aw_rBio#SZ(78_kmszLzZGg5DIkRihsTCUXP-%d}noz z%qSk6UZFoU7@_#hYv=j!!etJ}J)&3;1(N>IpiBSyAQTV^E-bt;Q9BofyQo&H(($yZ zqezOv@$n;}$jTtxaj`dMZzC$pTYXP6pl~R`xM7f`FtZvL)S`l^SWa{T+oF+Xc_O z$9YQ>#h!TAeeOypJ|QM*e2de-7~FSbEHo^1X#!LVQpU}J5Tbk>i?z12(RPh(?4<;O zq0=2t--uF&0I#giaBa|J|CDB7-zux4Y3>ZGoN8^dyl{s<&jrJgWV%uC+F+LNbQk&X z!gcn=DHYFh`PM97e(?}O6$E~YPzBP{$iUzQ5QZ9I3~^|go_;_9e81KM3g8F0&Yhp8 zh>!7)etDJFlq4?`y`o5>yfGAKC!E}9WI0HW-F}UehqicVd6jlJnuxiSC)r;g@PGA= z@zmS~8%fGOKgU)gw)249B4SsEY~(R(d4+XdWiCv3th&xSt1Eo5GtU8kKt&pwBrI2U zxhQI+*2mOADal%1=Y`!l2EBmG-6lg3Beeo)Y4{1}W;SU~XDqdPe7!rvNx6xyQkD;N zIXgFCEw6CEA9AU4h#xu!cQ=-}zFX&7GQ)qkeAOk`NrC4Jyi(NUDVG%Jm65iCq2<|IGYFk?rm;r9 zZ|MvrpV>Z3%gcG+{@eJTAPC$@-)`5CWtM8i75GA0d00E1F}Nf1HPBO5Au{lwq&9S^!c0{WwNfI88$2YZ_5T>=#77qX|M!Q;js$ z$fAdm;Z^`>lq&F4Y4|h#5o5K_K!=Je4_!zFox%nwc3IE?i6HI7@duW1tX$E2L zIfu!=i!SIUK7~}o6-z79e7Jd?S(%UuLp#z$o~9Dyc%DIe8qYU)zH=RPbbzK-OVC0v z$^xv*ydj<)LZDC{cwYJcCk;{>j1*{GyscFND=i|o$jo9Z8YwJO(TFK;#FQK{r4kxy zNJ9>pQA4K10BbD0A|TO<%y=ZmV=3zMrJY4CwATp(!x{mF(2NR?c9hZ%6WU=yD@LN|n;Wydyt2fh_J|9M8~n@dWnO&i04v=FgEVArxT6K2ME9qa8+jy z@H0TKpTCQD%&hS7V{Z~if_kmw@=Hbb)oz>1-8PrI z)9fT6A3A)4UI&I{`4l-0?Fo9le{Jv7>^({oNsM&w9?@l zJ1w+^hfZuW)lmH2_6a^-xkzd)KYQY3vJ`&&!pmH|wZNk6^Wx=M{>PR5{Fi6H%h~B< zauICQCDyQaf_?k?JT|+^+)U0#_pKl$gpnY#2H$hAbY^@YcY-_RJkf_k!#{iJ z5suI9aBgM?&$qN|mQ#6`BzKtsLb%s;IC4pf%5z9emRo!)hy%&MD(apk(U$+|;sxF@ zy~B1F_P5tcRkkbe1Fg{Dp#5^UKNg}%-0o=%!wO*?DlkS0aJy4g?y@I;KYlC$ajc2M zf|g0|6Am_GcfU7M^jAW3y&!0$c!5HCaVb& zh_pwj)#NtRIYE>W#~HVG=6U?!RUVsO=gO$bwX}}Sgd3Ms0xcz4fl(Hng&4|k2yL-D zl3-s!D;hBs7SvTnLuE8&LQ5u8MNVRTwoOQGJhUAf2svaW!{Y-pxB0*Cp5%k`D^%qO zX)XKf10tmfF!+FH1%>t)8c7&xR=W|eZqKqg3V3{BjkecipWh`f_KbS~K7V(aA6~l7 z*#NLWPrvFm4~ZSBB6p(sGI&RpZNfO0!t>l`MO!BX4*b1Q;PG`s>(Z^*P!@cbA>bxt z#KaR|JUjXO8%xZpAqS!qp(PzrL3)N*rbK~dP1X>naz1$WHVXLU^d-J|Y?o`-_j74| zmZuKhV53sw%e!;Dt9^@;^({XC`7>;8MF>B|^9qz_5WYtF1{GLD;O=`qgkeT!J0_?V zG}VJ7!0*?3&;a~^)(?Gfflt0ib8smrdE_zY@5a||bt7&JYFzKNu*$NPR3;*}u~K&@ z_wW_WghO_MkR3l@d!YEED+d@2Ef=P)@>UvSUaL@>PZ@cVj)}0^@MN^YtQykL2`^mT z$5UtSa5U^;a*OZ{M=E`eR0asv@%CN8px78yxU*YB;1NE&i!Tcf&8(xOW_7bo5=Z2X zh%>D52oH?ID||XlA)s4Ge{|T?9{q*Xlo6*m<3!3XF1*6L}rqD zRpNV=&)i(((y+ltj$ftkDF%7Ktz?Qnx_O*`_|P{A0wdX8T@ z`xYV_(6W90bY+QYx^(IRvwl9Az8D!Xf%~;7OFf?3}G`vc@lptOq@6fmc?4! z(+t>qp#5vOcOHEK zB?T96Eb@Hj^T@&~=jYalRL;m4hDs9}MQGh^WEG(<2rC7RnUv0EgC%i`{k6Ny`9t1J zrx++7e1Y)w_kR$mu7VL1gL4ip^h%bV&(aKPNSU{X%g*NL>O|$pK1^1$^Ap zfJ4Aaeaz6WNq?qN+Jj>iL98%j-c4;4f(XKfn9yJbGrp z%Az+T@Qe!|TWao%BHkQzxHjqW;SN%9bDJpyI% zm2q~%o~5C3xd`?@I!V$zheqHb zaQ9nH!JV?joR`yyp~IA&ol`uya>Qt47*8C|`ITXd8|gf+WIa5kxtFzgX}H9aKjHD# z9@=y^q@*hg8aBuG9XZh1qE^mmm0Ds^I)=Z| z&NrdelzeS#jX&AFz*D{ZcqXS67)tGP{a}%e-jFlxgrb1G(qo7^7SKVw1^%~nAf@WP zSG{smKVJ*>2u_gojvucp@w$!R*o~m1B??NqQSPP&lqL=gjmY2`K{in6Orrc8iN#kH zv@e+H|DS~RYq)gAQ$cKK1U=q zS6Y2qGRH{ERC-8JjYtY+a>7L^Sn7^Aw=m>lXT*X(;e2pNOXTFnW2^%vI$&Z$QsYq= zMWz+G^~kLvGp-F#AnAHJi$ThIE92SS(;Q_@`Z^}_d@`v>@JR7UtVbpkr6;gnN*twh z8dFA6p~4bh7@DG-1#}?<=UO8IVVU#Fx3KpSZw>x_J>eonH3G;ti2u|9o|!gAt=d;} zj_S7AsF3unPu~W3)orj4Ex!&*aXuQQzJE0Nh;DMDEKhUT^qkQdJ0ihuXH8Kqb>{02@CO**GCJi zcBU*frYyH6wBsCE#Sm*@zx0_ClD;-9MNc?@+dFlT3Gd%%eE5xAE7%vTpMoaAnjV0@s+q$KKPNKfGV9y4ux zgS$Ty`EgZ!tqtH35_v)lNF0xk35Q$EG}XZKJ1e}fy}~nR?(wd*ZPq&jB4bF5b}c770z4W@^Y_25*AQZhbPN$c?Jrhq~%NhW~YS zJJE^-6>~NkbIBjD(cN_%0ino@Pi8!(GGZb^l&6U)$jULPZLu7UXxf~%b*h&0s&HaO zClvc?`D)LDzP2mMgsiMlB5j+PjhC?;#uqtYk!Bs#KqWr>%=e3Sw1cR##%K(T9RiiOQ~ag2i}Z)SW5>3GpOIrp0hJMVk<;7Dd5C_s9tMo zf@5`@(pXFd|6AelbbE{MU3vr0<_M8H*SkpzDgv98EWhE^ ziS;Z~5ut^`2bRIzOcWFmqFalSXh|y@XCm17KTUzN;yjQVc+l%zy1_yyUmkx`}kJlRii^n z30^BgPL*Rm)V|L`*IiryI&H}$F=Uymo-L4?5UCoxue(R!JGQ?uu-F%|hrv{%e_<$D^fNHdFi9yv z>|0`$@y}m)n8)YubE-RK&-0-+XE_1i*Tgx*iO21+AkZxMa@-zp%6Z}~?Sy)Ipq?(M z&423l!12#J$f!QH?-}Btq#foof|6!rhj-TQSLt#9+DkYYhIjLv*{&iKas-!G5C*J{2S+e<}L(0az;59&eB79mZ;^U3&YZ)qB{q#Y?UN^OPbl)gh63o zzwpQ=C0Fl6ym8Rv?sT5j=A<$q0wE;*Nt1i~UD|O;l7wWFkkL`hAZzf|aEZPzDP;=U z(((-h-Kx|!4OU3}z|d$Gbh{}*sL3>L3Lq*uJnG@ek|!4SdFoq#_M363{`X=%KmfkU z>+y>@S_}5FkUP^Bce6GE`1YmytTa+e=~cZ&r=PwOPFZVAIn~YxtKeUjf+&V7jgrfD z#As}2#FCr)^W53>I2yxwdxY`~&kmNbfpGJ*i9qxNK7Qs6zV}_%$TLej9Mf$;mP1|& z+OcFXRvhXGUzY?%v!8gpI+^2wI^u;%kKZ4xBUQmimbVG#G-sBlT$vjpt-BZrQ9u!x;0=c{eVhOK-47nia=PQ1$7$%X=z3! z&8VO#J*G|9A{b0O(n6tn8A1p`<$kkc`J3a4pIY3f()Xv<6gc)DFbJ@fU9onE0wMxW zAuMDaOG1lh))LFQ4FCWj07*naRQLH7Ilvc&|Mc+}xH0T-Yux4_iO35@o=b|tcL;>B zBv$e18ykG_-f6z=>}{T2zwID%A+bWxj&dGd+~s}})8Ff{pEp?Oq)2616$N*8yG&Gr z_nhD4b|Kg=eKH%6+knLSvr$2Cw^n&+f04(R_Bb~; znN|MvUe%hnaBkvQFxOEWwS112PVwD~JEW-#?m`+~y|u)Zb9LQcb?OWl4tcQS8ShqNt<~;V|8xA2N$={(yOWsmjDz-vffI# z&`kgrq@6$q{NZbl@Yuye_I=G`jRWFH@bZlw_x1$iRFK+$E!}`jqQ>AQ0+m8gXaZAE z6b|-lgqo#JLaCi}ZK(xbqvQ|w&v9rXid>LeMQ6N(*BtXk;&Xd{0kPF!?!&wEx(QE5 z`v~EnbYtBV!+2^LiinS`?DFQ}Dt1)z*{kOmtqF#slJ9K2iPe(d%OB=HTfD(=BfOxvTh|Wh-fOJ{Z%CGg@)ZKl|ZNGoDy}?vFo+ z@jY5`l>o#ktth1#CA8y|&@Z8?`kn2mgZBk?mY_&`%mb*$0zImLE}TP}OD5{$oJa=5 ziSa^CBpUX3x}fHgV8QndU6nG|fcb_qB^o9kNv=@y1yTw;Po5b0tJ@AI#swuOd;pH8 z09-o&N(&S1{1hfO!Wx6jEe*fq3wvvPpm(2%hK*>Kjc}K884ik=TjMr&rX40lKv5|2 z+;d4mp(skl@83Ab&4VU?^@&#~t)SEbX-b+hqo)kpX^XG#tB~p~Kl7U(=i3*bBh266>YYU% zTHhs*mZkWJ`HA83J2rXlMa#}!Nf-*27Choe@Jy7F6ge-BdVK%l4c@o5O9_0acaKkR zU*O$h$SZf2dFGvW*gb5Mq&`XJ<7>+_lTdj4;phzar#|~fKAZbJWF`@%L23w9S?O97 zgkC}9=fr+N=#{h@CAWqvJkdU4zj&Ym_;#)b3BWgTtcO_q&{lN5rb6 z>6I)sa=eQ8qY-4<`LJI%U6QfltQgFCb)UU{kxy@3;ES@uYI}>oOTfbOHyRAmh@|vL ztWRIZ_&LbJAToo@1WU~+Yn>@kcx>p>Y3AhRlt7heBUvgZY^x?OCd>5mh%>DT54ZaK z{MVjjCvS1#+z~(e?pLs($H8RGog}9v6vnvO`7AR`63a?6BlAigv0G%llFfsNkDR;4 zXY>*ol3ml~`xowTZ>snozxGak?&8&}&I1 zsbVluq@{xPi9}##0Cd7g=q=8H$K!*wK7h~`D?+RanCOsR+~>^VA-#^_J?%}lQ%j6R zR7siy7G=U*C*hrQyW|MAlbHKSi@a1-GN8yZ$?Dn>Thk^>ttm^YM3?|Ygd#w+4AV?7 zG!d^KE)fMenUwtJ-}k5d%k4{iXS~C;;TnrxO6ZM|3fgf&fk$p2H@1TQA=e&xB{yxf zBo`iel@N?d!3#S}+&Nlgshy!vq&ntY(dS%afTsk9J$Iw{O_lD-l4uC0Ur492^N=dZXY`4d>6eFmbzRa&i8M$KoRn ztWQ^Il(4)uTqFycoLLx=q&}0xXOj9%Q^hn@^hZ9w{^~_IU2<{mE~N?BPUl_lFD*ht znRrZ$$5e+XG*OihgeqqmYP7HMy^{U%K?3l0t_KOgH);Luubkxz_8d>n4Zy&o&3)G! ztt>i4rDP^$WuAg2_B7kWfOUqH($1K{PoCZ6x5pc3k>E`yyfRX3_gh@M-()H}*!pTO z!AmW&08xNfz;bg+yOAR+A9yKU?|;6P5C<9B2udS)huY<_aEFhsZ?i8fFWg^38qN2g zxyJWjxrax(zCSVoK7Pr%RL5DMN%k4v#jJ{oj6k+;9ZMD zRtU?9;BRYpLW@)}Ngkqe%Z24F-h28kB5!lA(8S)cf4wypPq>zl1cHita>}2w>`%!A zoA91QNAS`kM86cY_Sj*6a42pn$u6X9sebQVI3=Kd3 z{FBH<591ferbU*}lqDsW0!xlZjwKg{Tv!St$#nIcwd6<=D;epSPj6nqn<}y*VCef8 z6_Ti!yIzlogyG2J?!g)jFXr4c_gIUMC`ySjYSwSxm_H;lf;W!lxHIjtGi~xx-en^k zu@vPj1UX99`}=r57~}RT)&4f6WqnN{n=!s9NHfD9Z^rx&Pke&SYXfu%XBYQKQbD0% z)Yk;Q!y=_?=w@1so@8jO^b@5?H%iOn|eg*M1}hDXq6vX-9#HnmO4iK zd?Tf7m4szN6yyv{$ko9*W7}}^!Ls_{5{jM$X=%M7Pz7}3j7D6}nv2FjT1ehF>aaa& z(DXG8zu?m14smEO#?sVqWOBae;+w3uB}YYrPwt-O>fQq1y>^|3a?!Rv?;;QRKer#} zN8@XJOLLPnvz+RU$xHbB_9;HQbDCCA@c5}dU7PbCuWqtg_6YoH^sa(6lf=ClwmR9N z$no=#hsY_b^VgNLV9nmoGB?{EgmqA|WR*X95FG2J*E7#E2(^M)Jt>GHDsdYp{G&BI zv^FA+#sEw*S7H0UdyUg%x7^N~?CO|Hivx0-kQ9Ma=PP`sbs|tb)*p1)++U;-X0)Qj zB?X~F37Sz#w~^9`Qk1kaxVK)ld!wkP0|f4mxAl8sYd?Xy%CC`ww+8x?34*k5Z5LRo zQ8XY34a-3WHe)!g`n$1nt&~!p5Cf{l|4xht8Y3w!qRIIk*!8Zg&~)Q97oj4g(VjrfoI8u zWl|bS1Ec9Y##31BBa|gIl01nx)B#erkh-K8!@C~7$$xn94LU;dYAhYHV%eGIpOJtZqNu)T4~Ie`^#*!COkd2gJ(c0W_#9s_MD?%sVzFStS)#J-xc*v2$n`j5K#PORTo6E~}Y( zvGGuMV!q?-P2#*nlmd~1G`30rm?Z#G z3q*wq2yO8U*hJFS8jq5l(SwlncCH8S<~Lz|;djnDXDwY*z1>9?st>Ug$FQ_H4*pWW z`{oY`L)Tv~tFrHvw8!7N@D*-NJw~QvFKr+L4}9fD1dXlywSf|J8~X1vY~0Lfw=~^$ zIja=6$1!jAJKP+0xHIf9EkpkM_x>rnB4EjP{cuxSKHBLMhk`J2YK2d>w%CspO)ul9`oOxd6~VUMGzL8Tj`?>oDTMJ7RKNk zOQ{7xz)Zrl8B2urDRPUi1X1WJbvIRD(WM~Gj!jCerE*cUB96MQJ6EH1(C(&Cdmkd;i zzzh|r6N5D7XwY_vT7?EQqh!`9=rl6AaY`%7t4;G3?VoBNIqrbv1V5nKYi?S=?Fs9A zagXJQu``k(74+`S_y{ME1nHjZv@{)4k`;nU=3_%Ql~zrWH0I93@nNDC7YGA?|+ z!GbjX*~)nF&-?P?ep|| zo<*l^e(x(A5Lp-KwL@Hl7B2a+7GD@VrJa9;wfF#5BGCAMe^U4y^yj!&EPVrKph^bfly02TjWdEk zA146ca0|zY!0~-MdrnlF%~Z*N703H<<~?Am1ffn8tm`Qw1wjabpF7`$Qc+X~Q1XN$ zpslT+p|lMiIdzvv3%;|l%Wv$SSm5#I0aj~%a^WiHTTV{aXjpCxc;%pr zL7)uC5?ZoA<0}0^S`uTK7(-r>9*f-LXwYR^G_XoxtfbSPBD6-97Mnvp^eIz?P2u$F z5$7YHPmb33=)!Flo*B`NGnQHj%Z-G&aLVUjI>YVOgs(+D8*0j7)}Y}T&NNbd^}2 zAB3#8b3J%BzX@wRF(}XV*Wehot21_Mc#8puGnAp~=efW?P8>8~4E)sEmr0CZS-^hc z<7Ym;4v@Le+WL^YX$NUNL@Br&9q{nV0cYoSxt7QL+O4yEc;OCH3!gZ=gtn5!C?l1E zfAZAlW{|h6nDbJ)a~bCnWi{6aUCzdRtaaRl)7{j~$y*mp|LW2Wnhn7ZUb#b-l&;Mo z1!?uSy}x-xs36N+piMGMw=L<-OVZ52|5Z10*1MP0JiS-R3#S##4}!9A{m=S+^MkiC zGv?DU_mFu+OU_jS#1n4Mv@stX%x9u zl*Xqpid=gX+NZRNLi=Qe$9JE-jh2cib{Fc z${syZlqdN8;VDjugw<|_+43RqvC6{}l9tqHU@8p87)Plz5^FVS;&U`;Gf3N5>0`8q zXG>a*G31ctiX!zfnZRZOn@i3v4ftxl!h#%eeZRxp`jmbaqYIC55)kAe!ODa`*gwO? zQ%8Iy>2Q5IPd{q$vH7b6zMv7igg7f)LYh^d&sHU_RTOSNO9|y*%s=}}&vD^Wh*Xvr zZZ&!ScEqiL$9QU3S@3w`@|YWGn`_qupMEhWP$N7qMur)R2weh0*gMd?zt|;cIZN#% zb$iI6dKKbhpvVQ;RFO@6@+6>40*WLcp9Wk%IK_wFbCY7N$<2P3j?7WU;9>BqfbSWN zXAM4<0E@Lw_0M?}C_L@46h9CGdOO#H1mMqk{rW#W55Ffq$mp>C?9))< z)OB7ObouH*m&bd3&a^Y;l?y!9y3r$7nF~av3m%28S}C=~_aP2lg;HwQH;)2AyCv~F zL7th){`lC)q}D1IQedj8$CzU@BCA>b);eobuOjqmJA=TziZoSoE}VvdJC?PTc7bFD zapt)@RusCHfRrxTiDJR1^!UWfXXqcc`I)C)L@C2*saa8#iA!0F`nafD8|u{1xG^Bn zG?byCa(bbp(2{B4P=ZuDj=+pBP@w}!?$Cn$X^;D*N3)(PsBY^-z$F72kzZ1WcDL*- zF>vqeiTg9i|9D>tT-9CC%qlqE{h+N923^5Sf-1=p5E_lJP#Ou+@c|}SBbiPkjs`KqNt41vSkGg@pK@VtkEu22L}C+xO$B5E zn@Q#y8F{PAG!eYh>+|g4GGDs4PHsKIu%Hp;G=dBnWGJuT-R%MI?(`|F;0uFw-puAv zR`NuA#58fAC0#N8m2wGll33Ezkvz&c;FIl-@xPw?GC7C5aJ|Xbu7_-nnrtUN)5LIk zNh5^dYX@z9@3kd1nn!FF0Z|rGBoTff@S`rO*JOOE!{a{?v5}8>{l)-mHCM|8S`yx& zj%L!lWd>y`F^P{J`;_C5avD-h0v6*T4PjYmCmf_99WO%}gR*88{1q188hioPI+Ce2 z2BV!1htM$ZWpD2V`rq^QAOZMuURpZ=nPyXB%BsSx`q9$KuUD#H$D$r#==z;nTkhOz zp>0mz2E1~#${YP2H-^OdWn%hL7on@x#Q4kf9p@w#&Ns-WU2 zXDU>Cmf5*gkXvu7{LR5@sd(50SRfVokz?jBb%JO*LItT=vmLv=v-=Wa% ze2cLqO*N0lJM>pl8j;0V!K!lSQRtdbXc59-tv~C;IAI)Smbp~KIj7lf2)&#@mUKw5 zksv7*X&KBCgJ}^k$yG%nbdOCYZT9=~D6e!x!iqQ0sagb`#xZ}ulQa6s38?)<%iu)2 zp!&8V7asSQ>tsOS{>P3qSi$oZ1zl8GhLU#1BiPAWeBP{b7&ghi9PqKruW);Jf$g~l z`HF#DFxE|MZW%b=Tu#R$dST8m&#MF=XOv}(i;R&UF)2ds76H%QJVSpJ;zv1QT(GgW z&8@>F4yG-DY7|X^FjZn*BK(8{1s@Q*WSJq)3rvym^}Pk!%JQ*GZ%{}l)`W1?zcqr< zsKH@BVmN6sNV}kXE_RN%ICscecb`AFzm7~m<`SE`%HQUarC!R*gAVVXyMrzbcas)n zB9Njaw~*O@BNOo7UB1fiY`v4GdVM!#Vl3Y^e~(Y?pXW|K$HUQ~L+6C!bSUN20*k^) zUI)J5;fDhL{^;9y?fPk2?@TGuh*xh0+#NUBNm^`A6khy23t#1Xo;Eym`j9wqq`)|m#E~F1lGI2hxtkOK z$V(~G3DW}HObsvVw#8Ns97<}cvJ~7zf{mmUNbrJ{Nl`a7N%U^kfFMMT{^BeoD ztPQz*{)mrm+=5$*tJ`awj&l4$un-Ly4qE80pcIDZUR&V1-hGSLrd_tC0gFLKqFSuZ z8@^@ffSyWc6@)SHp{4soq0`AXl^$)DTP9O83x+#ci;u0`V=i{V^*bNBjjD`A;FugY znHpLR_qS^8e%9DpbADRdtKajMM5_u)^=nG)5Q4HAFAJ(_zpRd_yox?yj>&_@xFlsfF|5P`E_Wu?$QoE# zy9mB@ibKi*PYQ}s0n%021`v*suc-ueIH96vtH}nTY4|CzmtjIlX(VakGc5zx8mRdL zWk8ZUlb_v5gTi<;qvYpsVwK}6pxs`0X|l?XKm6Lv zjLLl`p`ZE81(`!|D)OJD`Yb57H=_`OK=KdnKE{u|`|CXY_?OtveD?09^rDEC?{UY3 z>`r}F7j}?k$qV~EOm4Um?y?#AG#VaFU^_kG_8n zQy4mdA@E)B4+6Ivic&Hxe0KAI`+0-H7hGA{V`Xl_uikms?ME-A<7HL9-?adI;ex;G z`CGI$7$ebIqK%}}G2FN_V(9ZS*lRf@}rR%(>x62DR&#<-Mp{o)u zEoIELGcGib_?e9_(LZhID9In^3w&L!@HbDrL@OARCWa)nZpK~%LJ$S;XmgCL0;)EU z7N#OALKH~+K=8fMJp|yLsV5fdfa)sg;n*^n7zgbKb|#_&j@zJ}yt$JqEiI}VeJsoR z7$?}wFk_sTRS>VCa~D{uS@#)Z+_e~Lds8{Xx$W#6sVY(DWni77onfX1C?&i)k-sEf z-v9t007*naRIC&w&4%lP#-Tg*VeZlOH06nwL%oc_ z8KlSp<+;(z;k0q0*Iu2esHAkO;6Ky%HykGv))II*zAWkZ4n0WA6a0ZvG0g*q7UVvo zahsi^9-+#scEOa+3GtzBBd6O)={7TbS@&Cx&%LhjXM7d-h899SidYj0#W(N`oH5L< zZg4G`<8LfIPtmj-CXzqc+`y)S#Ueo(kD+QpX0d*q0Dx7NqcrC3q|1foh)azD^HG%u z6gf$eaBJFPE02&yaH(^I3N4+2=53xGoX2K9LipebzBXRwx#=SQUP0TRFiJwi zRM4^|?Wm+3YQohCTV<1GSQ7XU_!gac$f^*q%1I3a2VRlM+(8G1SW!eHMEj*KQOX9cez)J>sXo@Nuqw>{n?RXRx%|NSI`OB9XIT0={GJ z9*YZtt3`{T%xFgmQDkV89u4c$wlSlmfz46~T0{$Te4Nj6^4z@O9lt$)7G7{yMm=oTg8q0iy8$@S49A9-k# zcb@Gd1kB5fbK#gYgI|2*VO}3~`Qb}f=`AEY^MO0;zj=wrQL$VUioZsiM<~E)bY?eJdZ^F}ad%Q;sn5I5&-e}^M_V6lBtrT8C-1q6 z^r#4NhDoL=jHT%-r;A?^6g|ak{#R5p^HjveqI4vkI2Oc_yY!d33i6U!^<4XxhgDB~ zG-vPl0}#n4?ajsrPRsr$XP8a;mfu_%tkR)Rm! z?>G6iomC$0j`@z&J?28^$-X^}_{`oC|50Zbqb!|{=SYMWiqz5;<&1Dh#h#lb#uqU1Os11Rtc1DFvc(SDFYho{>hoGEs3ET)`Qq<(a zi0yHU`v{uhlvb3`eoHdokb${oMl;M^u&4eb>|02Lv&>*t``hD(PAn+|p)Yvha2cC; zPH&-vrnPJgjrIpYNVBUw0fG-Z_cwf|KqJ8Jlm&2F( zrzw?BVI+|*h)hYYeIipZ)(t1v<0)(aC=1deJwu6LKWif5lC{o=)o8-0aLTF1gw-hH zGcP=Z7=%3a$e~MU2~b*KJ&Bf{YoT}^UrW|`qPfp6Z(ZQKR(DyG6NC`FZ+?g0`s$-x z_|{h)X;=y#x#%<3m9$$DUkUDS7X0$7%lzZNe#nP6ewk0cc#%uZ*YP|-C(L=}%?rHi z^gT}XB>&g#_w(`i4c@=JO%hwK=ZdIV5bJ`5$!Qp$_Poz9=^^J@eCs0u~mg~E1wxrKi*<#DIiQ1Y=^M{;k^=YdVZ7n}^<{F>hS*NQ~Ix=&? zzpnhP#9B#NR@Tc}pgorW7%hmSoWJw8PxG&TZRg+Os{CMm<9d(){5dUcV7pY@(-99f zGA{H|zOQ?W{%(VhK71chN!Y%pu}7L%b~2xh-X_;FpSx+qXYysnMTA}UnD+`szUA7m zL&M8Bv$)I4_uF)%5lWOizOqFeOC~ztPTAq*Vu>G~dxLyp_}8yqrvFCFeJ>aV^8^SL|gW zU^p@;<+u&wiFTI0)+zs4Yb%2l@Ty9>Hs>j|3wrg8zYv0=G&8GTd7=^#vvDzpbku!* zs4Z4q@b70KRvRKe#aA9r^oG1rtnlbU%34QbjXSS(Z^U0)dkw8UnhiI8m}SmV7~J{H zet%WL*Fr|NM#X1mR5`U7OXw%WVM?hz4#zD7^5jgYs}2Q~{yZ}mL2F$Au%=FCm`z|< zYY4r9P?mH8XIPUKYL*Pt=)hpqW$R#(ILK&*$t)4*#0i}^qub18NkOReF?Zku0#mmQ z-oh)WUjvo)gW&E?m;dVYvor7cKnvn>#AEDnZ!pimYhsm!z*YU$2kKV9>@mwAZE$Nc z$E{?@rN)5sQJ*_$3mdp|Fu><;pXG-he}(16A(s~i48|Vs-MGzG-efax@%pI4C2_!q z{4r}Jq}Jne`=@zV>mE}r*cvR-@CC6hD2z{GN=j2OF%9-j7b`rxNF#lVkQ(6`j27rZ zBCJFi$);}3`qviNn|4Se$>x4{mYDv)l~?%my(cJRiD?Q%%g}0<{FRO8IA;w5D~M{L z5Kr(+SFbQHG@b`WO1?B*;#_ioHkLPU72LR0uy>&Op-a2W1ucH_Q|tVre{h8#{ksP| z)!1h=GVC83{^wWT`~R``=0TEW=XKxj-uu?vv-Ym4-n*w~$86vXFd&G9AZTs^DUqfr z(sn3pF=(5zsW7cDEjz+ugd=RzcG#3jE4)Y)3@T`dCS(N(ng9uq&;S@~q*NTY0|Aj~SZ6PRD z3;wP6Rr=bJH!TDOo|hBLfMnR>%yLSr?y=g;`KPb8`9NcrTXzcf4>h{cM#do*>xU?z zd2@G_f#=a`C7g&x#3H3gB{o+a>N*sHA3SlB&0&p>N}cDQS?8~{!Bgd~1uZZ}m7@Sb z5Tpzb$KUN``QG`Ca}NM~56(aPoizlOnk_h`CM^3JuT(_;(z(l6ToE}KNk&>hZn!q7 zbGN9`t&e%`V3phkTx=d<3c>AOo3|$OEC&gpNa^T=BN20MZ9o_}>h*tk>nR@ZZ1ZZ- zm z7*s!_Y~CHUUkU3dSDOk-rG7bQFM&$+II3a{l)KEul!ee{ma6xeZ$HR0C*I+a9Q83@^ zAtdz1b%v9;RNy-wJ(PuCFtgX0N;}{_XDZe}RKQ>+c2uIE;cMz%hK&TdcDV$V?QoLS z*ci63!qNyQ&R#eL1Zg=Y$Y{q2ty<|nOd9|%BsH$b9Ug497=u_Q*---c|w$p+0N>`K4|iZmga2DAOeu&{O{j-KT;%khmv$5pa^_&lT(-i zZ38mxabR17%Nf!$NZ%zEYAs!oR0)s^qj<2h&+CH@RtSVr42Df^?R9x_eyg18TOO$& z@%lr3cBvD_8DX4pp>f1X=`4pG<(3X9DHvMCg|k~cpi-jHp@NUpcc{faC)YgEC70mW zYDz9&$@sOeoaXbrhf$Bl=>3qF_vYEy$mpsqe(J(DzklO?WKZC4X6SB?-4T32t#T$h zViZWmz9MN#OatOCY2HDS*_>Wm(%XUG|4PO}eaQKBMAHy&)gFLzSrj- z0QeppQx?pH*wPF#0_9Tjv~~eCE};%y9(K6AH_s0=ZxW&zP9m=L7fFT3#r6@4eu@!- ziAqRq$Qy%2E_HT!d3&9>?XfIr?sf(0TgDqY@Twq*lGFR(yFfUnZOqg_F zzBOd6HX+HFUdXHN-&(=H^YClOX6UmSzbOl$p4#(V$FAh8Q2J_CXv0v@YDxlM@ZWsq zL;Np|N4Zpghr`hUzg$1!*S_&IpQ^pg!^;!uv6%Y&t2zFRuBY7GcWmFe!L9kV7GF6P zeUXFJ)u=*Dzt31VTb#aM-v9q=^DM9HCMVK4o>F~;ghoyB_kRDA{QA#*36W>+_hkh~ zxlcrnXHDrAa7>6SBU7`?a+nJu9|GywGopn;Aq&y z6fT*_>e*<(=t?wLiVuxzxg9IuK!FAjWd^^PjSgH_Y6xY9A7->dg*AetnAr{wdL1_Q ze3vy4mmCLXt~KM)|%}6XT(q)rPox!srYu?`S1Vi16*9(<=MqodFuQY&4wi3dYeDI@hIK|a^IolP1&M3E*J-bapaM4X0FLM-e%I4`dDy4^qohQ+yr1(l$U zV61%R+Znr?HIh*BA3psO=fWP7i7CC?&{Qdt)bdB$r@1ojGP>2oPb_UO<9zp!`}`h} z(9?)vAq3SXJXxmTN#SPtFe|`S@$?W=5tuIReHHDl%D?x@rl%zAjRgPmtIOPH{+z>i zBn#`iJk{Oi+i${8*Zz#Q@X0bo;0rv@PUDY-tyl#2vqVvCmUC};W~YL|LObn%5T&IO zH=3x9Dl5|zP?Su^x;NrOj!3uL)vG%9l!#tmK?F0kZ{dA7CX z;&$asI0_ZfO2dw8x@owq3W`zK5t7Gf|!nQDlI|HOR9m;~ED? zZG_ZQW}sbj%s{0b=+x6GQy?YNRl|91X*G8V^oI zv`?zltl*cQ(}fd08ttB~Smt;-ryVAA8!27^WD+t-XbU8Qi4N!|HFl0#Y)|TV7EZS& zj77{}Y@T2v7Hn+I6Z8#vLsFa)(1d4dcUg@FZ0xl7`n8iRUpOK=k<(B8vg>CC018pr zIlw?_JZ>D#@sq1pd47DFzx~*^NQ@<-d|?VFE}ioV(p|wpzsA$&wpsB~d{1DkVOZoG zJ9d1f_XXT}wkH9vAGLY&)+!g*k62ke#81Jb7Kx$dC$xiX8m((eJKFK( zFwUPpqtvGx#>!K5cU7lX1?fbAqr$VyVzHzdY;EMM^lu{Tu(aG@Z&V}BB!hlNqpqOq z3ZgNymA6$lEQBlkRo6cI`R?l~mY}+?p67i36=ko>WHQ0Kv(kOffs+r^_As)>CsuC~ z_|Acml`Dm-&>~?4o-mBG;%(F9Lt+cxgT;A|G&KxIj$wE^_bHSlwQh%jTUulB3yUuW zzOaPKfdfgI@+jP6FDigl#uZo3K}F#UG;_@!^Q|8JNsYr{i%}Y2wU}B6rw#(jB|I<_ z5|&v5rqmglQq*ClBZe7hD4R|P5HLhuN}zJup}Qz2h0pdu#H}5ldNgq(f$y*vcIwsG zAVpil-#GsY7>if#w9g9{M&$c~q0PBEnV%N^kq#IX9;5R8aiL0ftL%siN|fa8dn^lb zDH%*cyaKv@O1Czl+s>#9P3S4MCp9+HChv?ojK=}>TFUjTLz4O28qBjf2sqgofM-Y| z4;etzoZ#0cc=eQL)^6~`V|RG&XoYX}=8y(QyHwPFA*Mh;R0zyUt{={Gp?<($UwxBr zj+TgQ&RQ^}>5uT0d;D8Rb*_DTi9`w>f8r`PWs}R>3p{#q6FiSS;qlsVj+=IlkGJo* z*Fzb?^wM>Y-!dQO!TWxnzxyj)uH7j3N59wS$;aya?w6k6mEH3kyl+7Ld4nhnfAPv$ z?n|$7)H9q|8uMhh&x`BJr~@BcXs714io^;E<)bsn2Tok(`RfmmH8h6<%_CxiEgPW= z!%Hs@_>sjrtE&NzEWF7ZlQs=6WqYp<6Gidi!3_PwTjrZ(2eebw)4*o1HgX>u2El3N#RT3H6G*?z?Y6xwz3m3E-g2}n7cO&>4*)fmC0 z0veuK-Xx41^O2>N(b$2R-+J>2e&p=8SgRGRhGWE?m|u9}4WJA%^5O25q1V@R+Nz|$ z1!FDI0(@E8ny`2V_pf$7XJ5E6L8w3pH$qX$jV`o4#tc;11J;d;03v_H`ofq@2OJKY z?Dw1G+P%)E0YjBQ(9XsI28%_`GyGfLFGP>$G>j|3bZwNGN%=4w4YOeS0>V};p&2EdS~y^#F+`Tjl`5vl_aPAl@7!(i+TIeJ&;=(~5>Pf)V{pvNf)AyI(`+$^lT9k6d>CRq?N$Om`ngMZk@t zIZQ+FX3}CU91}=Oqy-lmBYaP=)Ee>N>ORKvxn;Y2`SK}hM~Vkm?-DDIOSKHdhz-*s zjwHT^`+Et1bi{wR`bEBRf5`JMrUbs=KmMgIbC>4%^*@|2-W^hV@rdAJ9}yRr7X@h? z5J!S`Tk`mWhddzfa_x&NXbjePm`G4W8tq%^^G9rFA>J^fFqSv>mN{DABi*|WxP;KB zAB#A#?6JFFupmdQ%aFSxpJME@qeF~wV%@3Rwc6t3t}blsEzoWz47`-SpVISE_Wg`I zen!(zS@crcL4h`I9N_yIchl&*zewLZ?>hGYzQ(Y zbZU>~_JFUxvBJ&OBOW?=$e&y}$M?VQb@s=KpMUUq9_`NYR&SnnMklyFSmIObZ}6l& zBo+l9JadDiT(Zy@^3wJ9@rg&Tl|@d_wFN&awt2h0hqwej4s$jV*KinSy{S6y)3}E9j}k1T|G~G2CHm(BX-N9hUqFU6t}camZ&6&#~G$WX<+D9rq}5#n4KIM$(Qm z+R=!K6>N`NY>%4UnKTjQ??jaM2XjmVP*|!3TrNFPa^q-$Puzc#&+lCzw;sMHStLga zQF{C>YChvr7jAGR?{c5&)3|>Zt-!~PQcnA09`t+VC{B>324h{~=AT|2GuM&4b3Nz8 zvZCH?a6N5u_UVjkJ0sGAA;}9qX(*8<=XT=+uO|cUkG9yCW8Qe{G(jUl^xfZ~Ees;F zh|JPTHE(U4AySs8Zzx8F?aeXS;VuB3w#O5XhP?3YHLhQOi}|xY>nhqko^yCo_ ztl#G4>q~@Ia?jlkSxrp(&FcULsh8Zc=F%uXMjZtIrZ%yx1&{B1R*N{ zpnDLqeAu!~4io}(?jE!2yj}4>>S>bHF$o@;ORYWbZ|@UI=W*`T60+QBEt1r230#_Y zmcV)}s3}9DArX)mK?$pLV0qIIXciXmT)UlBC|Tng2{9U`ugiRop9J;8<%p)euZw1+oGS zatcCh`MP))lVHoygK)pk-Zl1Xea6NOOa zjcdxH?+U%uZX=;B#Iae0AXb_}E3C8_-%#WVuK+K%cs>-`Cu$jLLesPfS9ZIst&eDi z1?OA4wCZDCNjseN2AoqvCYhj@N%~r#d`%_V<_|?-d^1p6AfRgzXyjHQ+{b8NIxVFki&fVtRgS&XKV8K@;1$^bs zNuIy8%)j@@H!#*ka~I{puvWt*SH`hqWl0eRg4cE;F5eN{B*VLZpGlEpn;s|CkW2IX ztj!I$FxMl^EdSuS_oG_}q^3ZQe0)z5&*kKJSYbKJ0lyyCj9JqpXaw0$&La%ZP7k1pF!fDsA|qAECe2LGiN>=6U&U6Drk5GHB}JH zoSMw3c?C6*6RQGW7&0W8lIS4#9$dNqa-Vwu;5|A&zHkdEJzm|J=NC@A#b0Y)E~8LI zN$;ab{vU zb7qH*uW1;|*g#`Szr%2>xW3t9p*`V|lXtPw=fpx$r_h zm9tx5T4a4(Cxo3vk!G3epjEj|uUb@6ooY@$*HN5WlSH8?H~(3uVn6nEQMoa(qQM<) zFP+WM3KxZHoC`r5zkfJpqbMkaZ31fzQn(p^UWN^6ZCzlIfbrNc8r$-*y3gI$+ULIc z0deFutc}Np$q?dlnUkjk&4!>Bxur*x_P;a+GET>#c-7W25QXO{DkH10SCh-2mK}o{f7Gu zn~DLbv;s3h;7ljLyT7Jl5^TjJ$dU<=Q|E!x1PoA)1R`0G7s@Rgk_C8z z;UIKckU%ih&}{b!g#!V*qlgzaPVmELZm?s0HqwY^nun}1W}LxD2Y7ZuCa|R-`ehCM|(5}1zu>aic8EDU7>K3DbDksAeh!7rH*&t7$CJSmRr|xM23bz+PIF>1~ zptW+@BpBMtW4)cf2UqHQ=lHn?0N#W1cmBcm@$t?OYb^J7?lKx#u5Na@nJw@G=WbI| z>2#@#^W4M#^ZElk{_qZ;eCQURd*@+3`0!=^{OBRJ^brrv9q_GOaWnGxm7n1UqJ) zdf;75fH?LsS&l%9!cO-D2;puw@SXLoC@f#yT;Xivki}Xu1rbS77Fxx0qaVybhAC5X zrfYP~>^@Wxzg38k&fKV>3Y)Rkb?|Q9ZKoLmP9*3u1JcZevv{8Co~&^i1Au-Z@TFib zfX7bs=(H6|IeV4Xb}G!PM1pZ3@O(jTe787IRAT`5IU_2;U^VtGfrM~+0UcPpfuyyRur&_Z z9M|}8^ENLH7J00`L#`DE{RYEPNG-SSvG^`kPe=o$2vcZB{ZGdM8me}(C_In`<9VPg zMoUyK2#jE&Jr+fe2t$Y=7MkmO9lq|J;-?>Yi|2HWp(z-rmQiBa9V_%eVj#+6AS*{IOst;(2K4;>DiD|LcPFU9op)g$A zTjn54S!y4$d~TQZ#UsA3b%~!ica?D=xiN@%f6!-H_jr6|pA%t%QnL|I6uBi;wnPQ( zrebGT^XlsN}#5R+I`0eCrHB$)JSm`r2$Eib%mxf3mM-#RqojHe#h;Du{v z&>pUl&=yh)b&K&WD1r19_b=V%jK9szyo0uu)VLre56`{crFEcKDtruH36SH|qs!#I zy>Q<<_W;0qaTYhPlQ$eaC4@_~`^{IL;iKoTk>wLaKC`lI?6(=Gl1I;P^6Bf3@Q=Up zIQ~*XtMFKwn=n_)uomVT6V9DF;!V5AgKL{C%ADthCkSN1W9oo~sBq#rZSj3Wp#zfK z=gwY>QEqtlkyq&t3`tt5>dQi*l=HAxi)(yelpQl5XN9+-(A{I2Dev>b70$qS*#=p; zJD`dI&I`+e*XJ{DUE+M}h_j6&E_9C28nOlK5tA( zofeinD+OO(b^jb)o|mhUkJ|=~0WI8fppQi@y=@c3R`fcEUnOPA!~iYRmD2 z0~lFuk%hZIap)2b57Q8x0oG1e&{rGqs{7@6f>=Ocpr%~Mp6H4tuq4uwSwT9kvomax zT0sDoVbgjwkYj{g@J&e)D0-x%ALJDBmKLMJSC_B^`CF`>((NqAdQ(Iu*W2)wx|R z%=c&x3lx?Ms*j>UDl{MJ?9wT>-c^p4Dp4~C1xpKx`L3h>zxb6gy@BE3`$L|3JmhoV zT*rC^E6oGWHa3}S4)_;eKf|4R$}j!sE9^x9(619HK$f?lja3VxV5lfyS&beSJ(KtAO05mx#as!U+3YILtcMvk*ymGoO|?; zHE+nD?w;gh%NzV+^DPPu<3jK!{dKw`;mO7!iS>wuCNC^6?JXcBoIl;8-6-fy=Fnxd zrnR%eTokVOD_IrQnJOM;1>XoqKX@YKbuPZ=FVp*#XdHUo5 z{nV#bhgvL15=#^bq!M5q3t=pnaDgEyYPQ<6H&hwmruh2$XW2Jh&pJl=H|> zCJvlbI|!=#UA-ygMhv=e!oU3@uv z=2@~pkl?dz0-jmCORNgEdI5n=kWvu%@XGCVR^|t2p~wxsQO4Hsd+imG|JMBtCSlh&UF;E82UrFek&>&@^15$iOMSu{h4Ainy2EZ2{ULKtj<_=Fa$|Rnbu(tp z8`B?4JX0V9WJSp4oi?Al_ArYdd4WsaeTHMp*EbjWR)2+VU6ZMZ53FzU(76}!JeZ7~ z9kH6}MKq?=OMhhPeHvdm7M2vxW5#uAj8gwmoY z1;e4?sBdToDRE8lL#sVnO-WHWw?dkfnuhZJrkSM{yZh!T$$k;L?msuv-@DT|*{Rk^ zl$^T|Aj@`IP&z8CE7*=%kp#GivmqvH~}EiIw0T)PzEg}gF#5sD;TPPNhsMBK8HeakXW8Naf^CrxqG+G zxf7`aOUh$uDM5>f%%tQdKnh92D`VqSXgtP8DYe++)S8DPQM&=Vnt7BER%r`-utwu?54L67sT z0ZJIQ(gHIPNE0wHKB@JX&!JmSiNbB&DoKRFQy?pu9yqLz}=?S<=nh*@xYE*aD}+CLiyh8`wgJ;nNqqE8~Q=TNZ9Fx zP)K**suFQ}Z69kTTUnEy4k=0iP!)T}02~Javp0cX&{AjbXenlzp+2y)WVRZ&H;zR zk`?GR+3(d|K#)EbDr9Fl1C?FS3V}X03aIi1v@#fF_|WnObBzfbhczbsko{hStmm}4 z1KQ1$?S73+`z{_(lycN+lcQ(BvuGobnLrj2>6M_fv=SOEvAO%{(;W%EK)Qtu)ICiR zN%}~7g{5Z-eynko6ZHYP_W1HE53sbbM>{n9{k?}c-%iN1AS*mF8<5+8hL_Rsa-^@P zX^9w#=Xq?K24kb}{fsaui+`Boc^TM@qA1uN`|O$yKec{^TWOP)O{huD#9C4VzJXBA zY_z@)+mkwL4M%fVL08olNsIfNIP4i(O(bJFLheyqXCRgYRM`RUVl`1hZCn#

z55zk>I(NV$%{{V_;&u*`bb?NE7NlXfnbN5pvOQ?JB5bQSkjCOmjqwYNpOgCq3V~;p z%VWwFVbmrw3B!?Pe$mHT%YhDwrKS_+w4#(wl+p?k+EGR;OlSou^M1;9(n4X}&y{ut z2Bbv^CsS2IBLz&1$HWK*1Nb+8apQY&3I9ud?g4=B;{57wKSd^5JXq`Txt;UeN$dR5 z+N;#bh-Ah{7@}T`S{pD(JOZ23NDAKGt+O|=tXz1TEH5+UvCNB%NN9|(c%kL}O9yyD z(5WSiTOm;lsx@YF7_tx~+#mJ%tNuIG>V1}LLyAJ8G+fVOCOT%v#_a6RvGLkE&z`@< z58QWy>z?GkFr(|c&3t)b$@MJVud@9ex7)48V>A*e9fe-6yZL>U+6Ob6TC1rD0yjgi z#cnQdd^d^^(iI^0^<|+fZH!X50yT9Ih*`oQPfrAMHZ zLHi&Jfy_W!LtfJFSm#PWmZC;qqC$`Pm4yG}&!6NMKJ)_f{w{&)^7_%EoLo%sJi!ZZ zEs-@8lQiaSt+?1c;#6FKwpCkVGEO{3DB@s37#8?` zjwHkAlu=%wLg<^A9TW5N!6ohw4tYCmaIwBcAuIt72}%o56$T{*w?-{i>Jx-96uKPa z=!!Xl(b#ZvBj?YrU*tdg*$t$GdMsI9^tiKGaPG8EH%K{Mn{YB3aHiI0lqhcO2I#^7 z1x6?|8ewuieEto-dgCIucNd6)+!g-{2pCHgWMoPseT@_ng+xjVg<`p3*}anS?Y9i? z`~DXH^y(#U9<}Kx2(6)RHBFn-5jk@*rK?gp{+Ju%COb(9Iw~EDIL-#l{;z~ZTK9f< za_PIfSl>JU(sK_0d>1DU;NfVW7kW$dGlepGw#Qz=-Mq`^UVDf^R^xqVZn31s?BxyG zh2`u*e@eSbVOUlPp3pP`!|O*&%(sWUzq5nXg0*_efBE6(xixI?)}&6|W~}%r##)}} zY=H+p9#(-b1Sh5Cj}O}nvxv>ZIUZWw85qHKFQy)P90i8REAUN0KFNuL8C^d0 z(3f4=Bh5Wm%%s4R?rSEQvmv=|-d(JsKvJ(slEg5Y82A6oF$!TMU6_t$+6h;yg^dx@ z4ks(^PQK^#0M(e_NC$jr3K4XMPbXG4dwk;5+Yn+Oie;z45^kRS;$cmv#kX} z85*$z0rhAG2m)E|U@w77wdt`_@6m1aSZvog8nxITwAkx4*zY&Va#`v$#PoMn2>^|; z7;Dh7z=#4XE#KTf!MWBJ2hzvuCiuaGdOV>ia||At2}rd+vz>?%;8jnAt)39!Y@a?D zAyL9Z`#!6U5vQ61PBwd-Z4Wrr8S*Flr+BF~&#FIWSxk6!bB#yWHh4<*xpd(L)*A&v z2tM@aCSUHH<o4;=D&337%blo5Q1!Ja=qW91HqG!=HR<%t>(_Kdy1M-e)1sdG(;f!5FNm z0Dva9IZ3XVq;TKTUHqWn%I!6}%>j|0yVt8V1b%_>EYde6NHO?ej9!M$kDS> z;7tpg>!^R{wZ|EHfNv>u$otQ}O%zSIJm_+yBkqcV$QWuWr=!LQ>(sVBmB)Uz=%-== zGNVYd&<%)%E1F4S2m*^T5R(uD?!tA{H~iV1(~QS;KDzpC>UBw;yRR>_C5|M$G-ffF z@O$5Qhb01hlvbKQ512WBjSJjNEd$lG^=2&n!ofkSy) zQb4G{6A%kaAt1Agbl9NZYmyl0fIytmh!SdHLOq^Pk5U4q@ygCmYfC*E(}>3`b`Ch2 zYnDKu$$r1V(Xdw92AQh=4c2x3qQEL3QarbNimgmhc$U}ita7q>=(<6#U}Pdv?W2X7 zA)4v2(+63tJ{F!rcnawSsLaC)AZkI}v^3h5R@Y$SkRNVt@{^};@kX!9<}hU5pMVT$ zhXviZAPSvUK`Ho+hrdpe!fKq;ZfTwyo#ex<4dx?(lo1mxDTL2LoMHmwG9rA*5k6z> zF-8)22}&f8PnhT&Yc+LWVguNt&W@=wumN3}&{S^a^tJHN3TDghmWqiBWqdWeD5adx(MZOLA$gskr9KPu{SpXR{Gh;( zzzC0g9H2)5I+V1#KD8+3W6$`!Jeng8bFzboAku`w5Gq6HYofpq`5IpqNUOOvZZQ|8 zfTe2_663M&)reGzXEY|0*i2zkNs;=PL}D|E3lz9_-s^J@0Q@VSl`x%li@qng*ywYS zK1W5!8^bwXA9T4rsPoj~F3tLwFK(XVu5R;#3!D6Fohyv#ph)1=G=0wD?_sS(;S0`IcsmDl4@?la0lCV4sdlr?|EyiCZoMJhMfS2gH%6$1EgKlxRRfknn-nFA?VW*qoP zU56h8f?6aALpP=vjSWV_=e~NHh0}L=u$fSgB-eDCW|1JQVRsyp?5?mT#|)(JW)GFH zubTODO#D~=zbe`@hQK&?K>6fObpZdr+xX6pm9BzgS_4PEGXogMK?iPJprj=ff`&1q zS;SFNE7<`}qvkXN6+6&~5^7OOC^gDje3h}(*=MnHz+udkvDzcUl3f z(L&>gn$wGWoISD6%def`cHTy{HCYkhr-F9d0f3{_CpG?TZ6LEJ$*}+ep(H{ngy$io zLf|2VB1k}VGW@6@hzn{BP2Y=2jCE>pX;_(0x!CDra#vu5aSN}i{0d`SzC&KvsV?Q? za~l`~n>yfDQln)HM&2PE&tpqT=9T3?ef1;!$faA1tfF6l(qjspFxDC?EXtEu&u}1W z?AjWIP|V4M$mX=wguOgOAh?}&=;j4A1vL*Q!Y6x&a7^8eS3*)W1ei{(d``Js{&gDdx7Z1rph=YP*5pX+gaXW33NuM@3T3DpgC>5Y>PM#Mm z`0sWFj2+_Ndn!VK&gboETEUe_5GL=jWIQ4SYt%w zSr+9OqaD)`MWRevbQku~$e@iY^0Ev&(gr4p!5Y{71%bfx93U7Do$9|R;Hmqrb3@l@ z)g{j-E4+EsW+fRBc?B3tKaEIxA)ZQ0bGGa11!WrHvG>8c}4>g@L<%iTT;ivu2Bx=UaaUJ(;A19=g-`MrTi3u?-e zXrDy;SmiV!Q;D0Dpgh;@D-R(g0_9?MjeErYz#z`$1aU#!)EufBLv2vj;%S4D8edvG zQKO?0mi&yHndTB1frLo9eGdJh!Du&b8IPS7=BV&t;_;2QFEZC1@zB~3Eiz6;25X_& z=<${9Hag8QlL20lA*6PeR4LF>Q20KF)KN;%Q5jK@5!l2DKn*A>*w5>vg{B?|A}QG1 zZnM2#1H_bZt8_3zmCuiXA6>ml+fVsV*1z@tvG?Y|mYnB#-|t&`IqTh*xpQZL84Q5I z03bjNNCM&}Q4(d6(Jn8Ntx}@MOIeCtD$7nqv}4QVy^_-6a%3ec*>OstXi-~4Q4}fC z3<-h&2@u3Kz+m?6?%TO%?f$m>@%6bg0AXz<{*`&Fdb-bG;fl?Ut!*ZLD&*=w`i*90rj%Avi(ctcT03JW$UED{@T zZWEJz_EdxQjVT6$gjCfKeaC2!V>$`Kw zSvf?WI5KU?v_%Vp2$qY38K$!mLbwG0#QW%!9zhdc?a#6~s*`I&u1m7Kpr$Rs-A+~C zh`4=L|BPX5Tt%d*WOu*LY`xE?caL!1Ow(dW?KQ>wKX{E}tv+W*(>y-6!}lCI4_+V4 zxsONa5*=k_T}6-^m*0&E44tn=q&&_^_DfCAC6r!ck$9yz%HW`EKO%~xI4k;~8B-A{V!H~)LGYnc>)Di#yAOJ~3K~$}#SETe!Mt79b zD^vP5V>rkd3=)Qel(GaPH22MIk$HzR7301pc%-q)&YtIQy!Qo;9)h|WAc51$QW$vO z(Mt#sn8{2WArp$8%@~**m0N~Vvf6J`&jJhCh*ZEaJ*a?}Bu%5D-}Rk3Q&|s)C44C& zuOL)hg;6!v-|y9V<&{Ic|NWO4jsm@Yobw--TH7^vH0dLR;J2>dff_m1OwM67tWvsU z5I|c+sDzzvxq|@$QK=}&$^tDWX2F3jz`=8G*bd716=)bgUxA3%PYr}ysst6Fs9fq^ z*Iu}Vk8r?MxaIF9k$jpEa7D)6sLtA!Kxuox8)(*ws$DRy77RHuH(+skhwW~Qt%)YH z^^#xTzK5T@`+0hWr#lEdbl+vRZHpVDX$GZ7)dFb_UZk=A@6%Z37cub*twC#~)KO1A zL=S+Q#yjy`Bz=#l8EUP-wrtRGHxs{-0gXdRkCuTonbU20zVhS}cHt5y?~bjlRBj-M z%zGhXdHvidr7hvto_T<>my(ZqFb6_cBvv1wS5T`B2}Ox3EqITYP!Pv1L#o(GYBV!w zsgjyEXg4Ak1^d2MeW&<+)@e}dONv2Gub<-Uk(}x(M@(ZYXH6fKHGP0@Uc5oKr`gza zJo`eyYZppTlF36Us+rO}+~CODrg&g^msu@%a&3~UdlPIAOaw^Up{%2{g0j?@aBJd^ zRRCLUArlm{9@EVUU4?w|$B0D-+x~x5YxM z$E&M{4p#pnSe)}{>0_ejCIAQ(7#OpVAD_HV&!xQ7ooBshP_u@SHq^5>=!@TuKbN=- z0REKmd%t=Zts`0&z#m?`n=f5E!Vf%fhIcNm^L}-m)!iEJeg6&an20O$ll2`QZ0{nh zie@@h;vbK*jmdUW-5k9uD?*$?qNHGCG(#ilSWVxi486t)g%7%7i^px?G$IKI9hlFC zs{!RL(x#v-K?L@lVp=%*WkRpW=#???_llHWnb9jVhNG0h zFmC;xrduQ&o!n)6KVzynWXI>2fuOgq==C9O!k&ko(KHnFiiBY|&U;G@*E=<=6f!|-bi&p6K(9}Aac9g_bvQ~nBcH!;pcIv3o(P$Fq$LmIUr0)l)P+XWT5@U3_3plpMG zyYM-N!$9sLsqOw|$#q$vMm7P>w2Von(bl9ou)Xbh=@qzkZjsj3G2U_NB8QR=m-Z4) zPL1#~VSTs8Z@h3XkI&s;%I%X+82n_(nIdQMaz?9dxE>}b)-&>!r`>imriOHELaHM} zT?mECB-5Froj97(kcc_%?hRB z_s_Ffxk@uZb2j7c?@4L26|E^nJ|U5r#L^-0o~wfvm)nv{&t;gACs8Sh_DEqV4Hze| zNZhyrAXKFG2r69$2*Jm8odnYmB-SHBz*|kNZjmA9(o3GrzUDvt)H=^>Kg4Kjl_rAe z+K_KKd5*KIhsd-;1pad2K5+MIyyy5eX6hpX@Wj?UFYQnA!;7!7H%PeJo#3HEJ7^^iW_qQd zQpAgfE}^m#wKg1R*pDi~oQ|t;c{s^TQcx3?o>cU$rsor^&>(dD#jQj|`d&vN4&@O# za4e5@qRV-Y!&#SCyR(!gp-_&Yv?R(QL&W}9_S($M?w};>4s*IiRsJtCdPPzJfQ+_rS%2&8=A@XKF{no2|Y>JO)y3vdI_P_I9<|6EE{_@4iCEMYGykm38f9( z=wxh;>TH&EwoHqeddU$}u-mU!NmbIWJKon=#TFUAQ7rO(lUI4I<@oCMB2r2uf{D$9 zR0^bY;4Syw-QoYf^bqIw+Pt-Og?sG=AG>&h_s(DBt#cc^u)l!zB@I^5D#X zCIy|n8r@!#ObL$Nca4|qG{Ng=S6ivH;d~_zdy^e^)4v{qyIIBsQGtO73SL)5pgImW zpAtm<{R1#@pw`3zXt;GRZvCu-bU*&2H>p6p0W^$3$jw#;^agbX{W_~bqO_%%$BIBb z9rBk>y-X&$^ap|d(4&MvE5S@K7(c=)L1;S`!hm1|&t5)+?>O9~LJEn9o`=y%Xe>uq zne2q9KU{q`MY;m!9gfkkjxOVfm{y)Ncyep05vXZNHUZtWgcm=ZG1|C_FZ%T750l-f zbLrY!NX^|W%%5XU-Qah&PO%;`j(^i0-@UwsHwGasol>zm5Nx(#?~>rb@Fdb-0JZ)xsv ztl8nt);^;mice*?@PViVt)3em>Xm@5+nj+6*fr1m|<9nMu`I=N!8Da4p&e(l78TDvrBfo$G1(cvt=@V z?cyowOMM=02Ckb0F736kyB%gSiSZgAG$$4}>2z~~wK#HSveD}j{n)o-ToJhSd4TC; z$OFxFUh2+sroZ^d+?j93pCxVsfImh2?*DZ-vd~==j`{taJ9u!a!~GNc0Q};M5A))% z#djRNNSX+q-Z+9(f?93&Kw8&T2|>76?xQ+3Hbl$Mim7gF;FaDSFZUK`CZ2v;FeOJg zpqEJcHi_l_kTUcN9paeNz^8~L#Q#|xpppzuf@3-Dv7C0fG@7K4nnEO$!Z0)`ds{Vz z-8z0y7j^eYa4l>rh9z%ChI+L%Yr?6N=1Q6q)$34TLU z)Dninl(J>fnWI=!_)?Oj307*1NGNPzy_d3CHYig;W455!sWD6vR%{((0vN%@zQzYd z=>%)0!T)&vJv8e3Ou8D4hM_jpY_Dphkw|G-n(wezj_3_N3u=?8WIzy-g^3YoNVn;mVss%GCjG05;f`zf-m=*{LSo=3Xi>sSp{Y}6y1_k4az%3vk92gk9;W1UUVSI}`Nb=(KkH0SYSNrkXLKw3T zVsa1Tm&w<^=hn|Z_-Drliv4Z_K?6bHLr2ab(nyB226PtlM`_^tnJJDO>T$Te&L5mP z%4L)Bp@+}%CFV#(&c4a%6dB$I@R2hSqTFDdJQ=q&bb^o?d4G?NYmjYflylUaBQut| zQ0Uo|WEM6$4c4Et6q{Elc6Uh|ZCc0gBAJ;Y)Fe(ghC*}RoZ!a7kZ+y3#;;#H%2F8c z(Q8N1!k{o{H>Bo9q;^QYFl5&ma;<1ejYLqBmPcxl!HnJLLclvJbT1A>ajc7yv%^s&94Qq= zRzACVjD1&QCMoIZg09YJiIRRI=xIgYCm>V$E@e{264}6O8hMGRBsp7ZgfXyfGlouc zIO+20{w#&o6ll7g8iTEj-d@H|uZ8R9oIbjO2!T5$JG8TccCFyj&NSz@GdecMHz8Pq zbe@oEjvwl9ti4N-r0kEH>=aGpKtVsH*G*BY9zRv$wZj&YW`Lk`N;D;kkqz{MCZ8^u znkuNxSyr#MS#voqH)Pf^=+|&kW4*!z*rUN78oSYC;52DsNY^szD;hBbomlQWw#n8o zgzm$%JKE^azI*TN%VsYjQ6MKIVk*VVO0KU@6IMIe&NW6`Tj(Tb=D~YN z7p9?};WT(;HmjQT|z@Enku1{1*CNBQIZHr z4aIerK&l*159UER zPHWGdqQttuQy1s>&FAjs?|s{|9ML7tg{oB;)ggR{qFq@QRRv1}S1#t1W`vaH!1qFH zf#@6Vox0BcK=J(A5rh>77QN9OvRe38<^M5BM7Tg}YQX8)Z3bvgHhQEf<;%W zZ1TkDID7jkt)xR)dV~p3I^57vC`(;9mZm*vTDg!C6TA;MuiiieA-)0tH-|&S!F76n zYwM4?HQ$auW84M+f5P~Kk1q3T=TGv)%3=QMJ+F{wf~90gZ!6^sy#-c%ldX2nk3Rk) zN=G)lsd{=v8E}?^e!mcvt*yK1u2&RM3?xehtG>psUb`PdO5rqDMmb$w(9;=HdWevo zUM?8u1Z#47K4nS`8F|SdBs8-Ep#szCkknf|fh}8S#YAC}1>L7JOxay&(A)MTJAr}J zOw1J=o!sU5YjZrjynzs~I9ad&nLuX0Jh#T7EyL2GeR?&)r_P+Dx8Xp;L&x{{&XbpD zsy;WUaY;xvyD(#lxj=2jVN*fNoV@{;j^9i5RB0X2{~pJBAd@8p!*3EL+nkM69~YLpBG+}Yg05jfN| zTq>s#iRX#61|NOle)=f9_VjC#GiMjMbFRx>b30sd4X&sLEjoN?@)Ap;U}(Y1`2XZW zHTmH^)>mqHUSPdP1xGCy=5&vsCB8Nm(2+QzAkx5Ux%c)6>+eDZ4q#kb3b!WyTN3wJ=toq5;&l+b4D_neg&dL+_;U^S&TCg{2p*DJi!FkN?76QrC$s=Tkrje}-rvy%x z1Qqb!QuHn5=7`YUCkzG%XYg9Gk!G}O4wHaU3a2#avCUC{G9K4cPmmrfJd?uEbqSaI zZC2eR@5}Z`haUP0X#^9EKG*gFvvor|hhc!O3#2xVD{A2)DG^Vm9nTKu7)s6I`em-~ z)%e``LmX=Eu-8vHG*uv+pe(4GLZb4Ki06B4BjwVi4CA7~QJyRE%+Vis0?^%0DUIRY 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

Qi4W95XJ#XJtC~d$Rr_- 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 E0VmnJMgRZ+ literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/pointcloudrenderer/expected_rgb_triangles/expected_rgb_triangles.png b/tests/testdata/control_images/pointcloudrenderer/expected_rgb_triangles/expected_rgb_triangles.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8e2b876ca7864010eb93dd33f388bcd7b11945 GIT binary patch literal 471523 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

$4Yz$ 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 Date: Wed, 22 Nov 2023 12:36:19 +0100 Subject: [PATCH 56/97] Nyall's review, spelling fixes, clang tidy fixes --- .../pointcloud/qgspointcloudrenderer.sip.in | 8 ++++---- src/core/pointcloud/qgspointcloudlayerrenderer.cpp | 14 +++++++------- src/core/pointcloud/qgspointcloudrenderer.cpp | 2 +- src/core/pointcloud/qgspointcloudrenderer.h | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in index 1fb9bb7c081..dd3d3184549 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in @@ -483,7 +483,7 @@ horizontally longer than the threshold in :py:func:`~QgsPointCloudRenderer.horiz .. versionadded:: 3.36 %End - float horizontalTriangleFilterThreshold() const; + double horizontalTriangleFilterThreshold() const; %Docstring Returns threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer @@ -499,7 +499,7 @@ given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`. .. versionadded:: 3.36 %End - void setHorizontalTriangleFilterThreshold( float threshold ); + void setHorizontalTriangleFilterThreshold( double threshold ); %Docstring Sets threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer @@ -517,7 +517,7 @@ given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`. Qgis::RenderUnit horizontalTriangleFilterUnit() const; %Docstring -Returns units of the treshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +Returns units of the threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. .. seealso:: :py:func:`horizontalTriangleFilter` @@ -531,7 +531,7 @@ Returns units of the treshold for filtering of triangles. This only applies when void setHorizontalTriangleFilterUnit( Qgis::RenderUnit unit ); %Docstring -Sets units of the treshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and +Sets units of the threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. .. seealso:: :py:func:`horizontalTriangleFilter` diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp index ff75da14f3c..61126c011de 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp @@ -627,7 +627,7 @@ static void renderTriangle( QImage &img, QPointF *pts, QRgb c0, QRgb c1, QRgb c2 for ( int j = topLim; j <= bottomLim; j++ ) { QRgb *scanLine = ( QRgb * ) img.scanLine( j ); - QRgb *elevScanLine = elevData ? elevData + ( outputSize.width() * j ) : nullptr; + QRgb *elevScanLine = elevData ? elevData + static_cast( outputSize.width() * j ) : nullptr; for ( int k = leftLim; k <= rightLim; k++ ) { QPointF pt( k, j ); @@ -636,15 +636,15 @@ static void renderTriangle( QImage &img, QPointF *pts, QRgb c0, QRgb c1, QRgb c2 continue; // interpolate color - int r = red0 * lam1 + red1 * lam2 + red2 * lam3; - int g = green0 * lam1 + green1 * lam2 + green2 * lam3; - int b = blue0 * lam1 + blue1 * lam2 + blue2 * lam3; + int r = static_cast( red0 * lam1 + red1 * lam2 + red2 * lam3 ); + int g = static_cast( green0 * lam1 + green1 * lam2 + green2 * lam3 ); + int b = static_cast( blue0 * lam1 + blue1 * lam2 + blue2 * lam3 ); scanLine[k] = qRgb( r, g, b ); // interpolate elevation - in case we are doing global map shading if ( elevScanLine ) { - float z = elev[0] * lam1 + elev[1] * lam2 + elev[2] * lam3; + float z = static_cast( elev[0] * lam1 + elev[1] * lam2 + elev[2] * lam3 ); elevScanLine[k] = QgsElevationMap::encodeElevation( z ); } } @@ -678,8 +678,8 @@ void QgsPointCloudLayerRenderer::renderTriangulatedSurface( QgsPointCloudRenderC float horizontalFilter = 0; if ( mRenderer->horizontalTriangleFilter() ) { - horizontalFilter = renderContext()->convertToPainterUnits( - mRenderer->horizontalTriangleFilterThreshold(), mRenderer->horizontalTriangleFilterUnit() ); + horizontalFilter = static_cast( renderContext()->convertToPainterUnits( + mRenderer->horizontalTriangleFilterThreshold(), mRenderer->horizontalTriangleFilterUnit() ) ); } QImage img( context.renderContext().deviceOutputSize(), QImage::Format_ARGB32_Premultiplied ); diff --git a/src/core/pointcloud/qgspointcloudrenderer.cpp b/src/core/pointcloud/qgspointcloudrenderer.cpp index 38549b1f306..0fb4843ac4a 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudrenderer.cpp @@ -215,7 +215,7 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, mRenderAsTriangles = element.attribute( QStringLiteral( "renderAsTriangles" ), QStringLiteral( "0" ) ).toInt(); mHorizontalTriangleFilter = element.attribute( QStringLiteral( "horizontalTriangleFilter" ), QStringLiteral( "0" ) ).toInt(); - mHorizontalTriangleFilterThreshold = element.attribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), QStringLiteral( "5" ) ).toFloat(); + mHorizontalTriangleFilterThreshold = element.attribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), QStringLiteral( "5" ) ).toDouble(); mHorizontalTriangleFilterUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QStringLiteral( "MM" ) ) ); } diff --git a/src/core/pointcloud/qgspointcloudrenderer.h b/src/core/pointcloud/qgspointcloudrenderer.h index 977c456d7b9..7a323375dc6 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.h +++ b/src/core/pointcloud/qgspointcloudrenderer.h @@ -634,7 +634,7 @@ class CORE_EXPORT QgsPointCloudRenderer * \see setHorizontalTriangleFilterThreshold() * \since QGIS 3.36 */ - float horizontalTriangleFilterThreshold() const { return mHorizontalTriangleFilterThreshold; } + double horizontalTriangleFilterThreshold() const { return mHorizontalTriangleFilterThreshold; } /** * Sets threshold for filtering of triangles. This only applies when renderAsTriangles() and @@ -647,10 +647,10 @@ class CORE_EXPORT QgsPointCloudRenderer * \see horizontalTriangleFilterThreshold() * \since QGIS 3.36 */ - void setHorizontalTriangleFilterThreshold( float threshold ) { mHorizontalTriangleFilterThreshold = threshold; } + void setHorizontalTriangleFilterThreshold( double threshold ) { mHorizontalTriangleFilterThreshold = threshold; } /** - * Returns units of the treshold for filtering of triangles. This only applies when renderAsTriangles() and + * Returns units of the threshold for filtering of triangles. This only applies when renderAsTriangles() and * horizontalTriangleFilter() are both enabled. * * \see horizontalTriangleFilter() @@ -661,7 +661,7 @@ class CORE_EXPORT QgsPointCloudRenderer Qgis::RenderUnit horizontalTriangleFilterUnit() const { return mHorizontalTriangleFilterUnit; } /** - * Sets units of the treshold for filtering of triangles. This only applies when renderAsTriangles() and + * Sets units of the threshold for filtering of triangles. This only applies when renderAsTriangles() and * horizontalTriangleFilter() are both enabled. * * \see horizontalTriangleFilter() @@ -754,7 +754,7 @@ class CORE_EXPORT QgsPointCloudRenderer triangulation.points.push_back( p.y() ); triangulation.colors.push_back( color.rgb() ); if ( context.renderContext().elevationMap() ) - triangulation.elevations.push_back( z ); + triangulation.elevations.push_back( static_cast( z ) ); } /** @@ -801,7 +801,7 @@ class CORE_EXPORT QgsPointCloudRenderer bool mRenderAsTriangles = false; bool mHorizontalTriangleFilter = false; - float mHorizontalTriangleFilterThreshold = 5.0; + double mHorizontalTriangleFilterThreshold = 5.0; Qgis::RenderUnit mHorizontalTriangleFilterUnit = Qgis::RenderUnit::Millimeters; }; From e1ba6f5272fef5a51c35a2586ef09216a30d97e7 Mon Sep 17 00:00:00 2001 From: dubravat Date: Mon, 20 Nov 2023 23:23:30 +0100 Subject: [PATCH 57/97] Mistype in QgsGeometryUtils::segmentSide description was fixed --- python/core/auto_generated/geometry/qgsgeometryutils.sip.in | 4 ++-- src/core/geometry/qgsgeometryutils.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/core/auto_generated/geometry/qgsgeometryutils.sip.in b/python/core/auto_generated/geometry/qgsgeometryutils.sip.in index 3fec3a7726d..d9faed58c1b 100644 --- a/python/core/auto_generated/geometry/qgsgeometryutils.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometryutils.sip.in @@ -424,8 +424,8 @@ angular deviation (in radians) allowed when testing for regular point spacing. static int segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 ) /HoldGIL/; %Docstring -For line defined by points pt1 and pt3, find out on which side of the line is point pt3. -Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line. +For line defined by points pt1 and pt3, find out on which side of the line is point pt2. +Returns -1 if pt2 on the left side, 1 if pt2 is on the right side or 0 if pt2 lies on the line. .. versionadded:: 3.0 %End diff --git a/src/core/geometry/qgsgeometryutils.h b/src/core/geometry/qgsgeometryutils.h index df39d87435b..c252233152e 100644 --- a/src/core/geometry/qgsgeometryutils.h +++ b/src/core/geometry/qgsgeometryutils.h @@ -433,8 +433,8 @@ class CORE_EXPORT QgsGeometryUtils double pointSpacingAngleTolerance ) SIP_HOLDGIL; /** - * For line defined by points pt1 and pt3, find out on which side of the line is point pt3. - * Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line. + * For line defined by points pt1 and pt3, find out on which side of the line is point pt2. + * Returns -1 if pt2 on the left side, 1 if pt2 is on the right side or 0 if pt2 lies on the line. * \since 3.0 */ static int segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 ) SIP_HOLDGIL; From 415d57eb6ad075574d44453f1764c9f9e0116561 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Wed, 22 Nov 2023 10:54:15 +0100 Subject: [PATCH 58/97] qgscameracontroller: Fix a clang-tidy warning warning: narrowing conversion from 'double' to 'float' [bugprone-narrowing-conversions] --- src/3d/qgscameracontroller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 5582053adb9..7ee13c8fea9 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -328,9 +328,9 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { // rotate/tilt using mouse (camera moves as it rotates around the clicked point) - double scale = std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ); - float pitchDiff = 180 * ( mouse->y() - mMiddleButtonClickPos.y() ) / scale; - float yawDiff = -180 * ( mouse->x() - mMiddleButtonClickPos.x() ) / scale; + float scale = static_cast( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) ); + float pitchDiff = 180.0f * static_cast( mouse->y() - mMiddleButtonClickPos.y() ) / scale; + float yawDiff = -180.0f * static_cast( mouse->x() - mMiddleButtonClickPos.x() ) / scale; if ( !mDepthBufferIsReady ) return; From fb57b60c33575f7371ac9ec892de956dbba3e0ee Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 20:44:21 +0100 Subject: [PATCH 59/97] qgscameracontroller: Remove unused attribute mPressedButton --- src/3d/qgscameracontroller.cpp | 3 --- src/3d/qgscameracontroller.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 7ee13c8fea9..71b37280ea4 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -627,7 +627,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mMousePos = QPoint( mouse->x(), mouse->y() ); mDragButtonClickPos = QPoint( mouse->x(), mouse->y() ); - mPressedButton = mouse->button(); mMousePressed = true; if ( mCaptureFpsMouseMovements ) @@ -651,7 +650,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mMousePos = QPoint( mouse->x(), mouse->y() ); mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() ); - mPressedButton = mouse->button(); mMousePressed = true; if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; @@ -676,7 +674,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) - mPressedButton = Qt3DInput::QMouseEvent::NoButton; mMousePressed = false; mDragPointCalculated = false; diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index a619c8c0a2d..b3d29978509 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -295,7 +295,6 @@ class _3D_EXPORT QgsCameraController : public QObject //! Last mouse position recorded QPoint mMousePos; bool mMousePressed = false; - Qt3DInput::QMouseEvent::Buttons mPressedButton = Qt3DInput::QMouseEvent::Buttons::NoButton; bool mDepthBufferIsReady = false; QImage mDepthBufferImage; From 8497a89a84ad0dba4a883ad85bd771de32f0b14a Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 20:46:53 +0100 Subject: [PATCH 60/97] qgscameracontroller: Remove unused attribute mMousePressed --- src/3d/qgscameracontroller.cpp | 3 --- src/3d/qgscameracontroller.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 71b37280ea4..ef79139e565 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -627,7 +627,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mMousePos = QPoint( mouse->x(), mouse->y() ); mDragButtonClickPos = QPoint( mouse->x(), mouse->y() ); - mMousePressed = true; if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; @@ -650,7 +649,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mMousePos = QPoint( mouse->x(), mouse->y() ); mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() ); - mMousePressed = true; if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; mDepthBufferIsReady = false; @@ -674,7 +672,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) - mMousePressed = false; mDragPointCalculated = false; mRotationCenterCalculated = false; diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index b3d29978509..fbfbe3d750b 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -294,7 +294,6 @@ class _3D_EXPORT QgsCameraController : public QObject //! Last mouse position recorded QPoint mMousePos; - bool mMousePressed = false; bool mDepthBufferIsReady = false; QImage mDepthBufferImage; From ce0d66ea156cbfb7e70c30ba7a0097979c78262a Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 21:27:15 +0100 Subject: [PATCH 61/97] qgscameracontroller: Do not set both translate and rotate on mousepressed It cannot happen a translation and a rotation at the same time. --- src/3d/qgscameracontroller.cpp | 51 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index ef79139e565..64e8296457d 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -623,7 +623,33 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel ) void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mKeyboardHandler->setFocus( true ); - if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton ) + + if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ) + { + mMousePos = QPoint( mouse->x(), mouse->y() ); + mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() ); + + if ( mCaptureFpsMouseMovements ) + mIgnoreNextMouseMove = true; + + mDepthBufferIsReady = false; + mRotationCenterCalculated = false; + + mRotationPitch = mCameraPose.pitchAngle(); + mRotationYaw = mCameraPose.headingAngle(); + + mCameraPose.updateCamera( mCameraBeforeRotation.get() ); + + mCameraBeforeRotation->setProjectionMatrix( mCamera->projectionMatrix() ); + mCameraBeforeRotation->setNearPlane( mCamera->nearPlane() ); + mCameraBeforeRotation->setFarPlane( mCamera->farPlane() ); + mCameraBeforeRotation->setAspectRatio( mCamera->aspectRatio() ); + mCameraBeforeRotation->setFieldOfView( mCamera->fieldOfView() ); + + emit requestDepthBufferCapture(); + } + + else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton ) { mMousePos = QPoint( mouse->x(), mouse->y() ); mDragButtonClickPos = QPoint( mouse->x(), mouse->y() ); @@ -644,29 +670,6 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) emit requestDepthBufferCapture(); } - - if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ) - { - mMousePos = QPoint( mouse->x(), mouse->y() ); - mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() ); - if ( mCaptureFpsMouseMovements ) - mIgnoreNextMouseMove = true; - mDepthBufferIsReady = false; - mRotationCenterCalculated = false; - - mRotationPitch = mCameraPose.pitchAngle(); - mRotationYaw = mCameraPose.headingAngle(); - - mCameraPose.updateCamera( mCameraBeforeRotation.get() ); - - mCameraBeforeRotation->setProjectionMatrix( mCamera->projectionMatrix() ); - mCameraBeforeRotation->setNearPlane( mCamera->nearPlane() ); - mCameraBeforeRotation->setFarPlane( mCamera->farPlane() ); - mCameraBeforeRotation->setAspectRatio( mCamera->aspectRatio() ); - mCameraBeforeRotation->setFieldOfView( mCamera->fieldOfView() ); - - emit requestDepthBufferCapture(); - } } void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) From 1b8c44b948a38669828ae6b2427e9856f0aee3b7 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 21:57:41 +0100 Subject: [PATCH 62/97] qgscameracontroller: Use updateCameraFromPose in setCameraPose --- src/3d/qgscameracontroller.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 64e8296457d..aa4bc82dcdf 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -182,11 +182,7 @@ void QgsCameraController::setCameraPose( const QgsCameraPose &camPose ) return; mCameraPose = camPose; - - if ( mCamera ) - mCameraPose.updateCamera( mCamera ); - - emit cameraChanged(); + updateCameraFromPose(); } QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const @@ -260,6 +256,7 @@ void QgsCameraController::updateCameraFromPose() { if ( mCamera ) mCameraPose.updateCamera( mCamera ); + emit cameraChanged(); } From e276f08f5fe1ff85e7a7923c3429a61bad0548a7 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 21:15:09 +0100 Subject: [PATCH 63/97] qgscameracontroller: Move mouse parameters logic in a unique function Different kind of operations can happen while clicking on the mouse: zoom, rotation or translation. A lot of parameters need to be updated to keep track of the current operation. This commit only moves all this logic in the `QgsCameraController::setMouseParameters` function. This makes it easier to understand the code base and this will make it easier to fix some existing issues in the next commits. --- src/3d/qgscameracontroller.cpp | 101 +++++++++++++++++++-------------- src/3d/qgscameracontroller.h | 13 ++++- 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index aa4bc82dcdf..2ede0aa984c 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -570,8 +570,8 @@ void QgsCameraController::handleTerrainNavigationWheelZoom() mCameraPose.setCenterPoint( newViewCenterWorld ); updateCameraFromPose(); } - mIsInZoomInState = false; mCumulatedWheelY = 0; + setMouseParameters( MouseOperation::None ); } void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel ) @@ -594,24 +594,14 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel ) // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta mCumulatedWheelY += scaling * wheel->angleDelta().y(); - if ( !mIsInZoomInState ) + if ( mCurrentOperation != MouseOperation::Zoom ) { - mCameraPose.updateCamera( mCameraBeforeZoom.get() ); - - mCameraBeforeZoom->setProjectionMatrix( mCamera->projectionMatrix() ); - mCameraBeforeZoom->setNearPlane( mCamera->nearPlane() ); - mCameraBeforeZoom->setFarPlane( mCamera->farPlane() ); - mCameraBeforeZoom->setAspectRatio( mCamera->aspectRatio() ); - mCameraBeforeZoom->setFieldOfView( mCamera->fieldOfView() ); - - mZoomPointCalculated = false; - mIsInZoomInState = true; - mDepthBufferIsReady = false; - emit requestDepthBufferCapture(); + setMouseParameters( MouseOperation::Zoom ); } else + { handleTerrainNavigationWheelZoom(); - + } break; } } @@ -629,21 +619,10 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - mDepthBufferIsReady = false; - mRotationCenterCalculated = false; - mRotationPitch = mCameraPose.pitchAngle(); mRotationYaw = mCameraPose.headingAngle(); - mCameraPose.updateCamera( mCameraBeforeRotation.get() ); - - mCameraBeforeRotation->setProjectionMatrix( mCamera->projectionMatrix() ); - mCameraBeforeRotation->setNearPlane( mCamera->nearPlane() ); - mCameraBeforeRotation->setFarPlane( mCamera->farPlane() ); - mCameraBeforeRotation->setAspectRatio( mCamera->aspectRatio() ); - mCameraBeforeRotation->setFieldOfView( mCamera->fieldOfView() ); - - emit requestDepthBufferCapture(); + setMouseParameters( MouseOperation::Rotation ); } else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton ) @@ -654,18 +633,7 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - mCameraPose.updateCamera( mCameraBeforeDrag.get() ); - - mCameraBeforeDrag->setProjectionMatrix( mCamera->projectionMatrix() ); - mCameraBeforeDrag->setNearPlane( mCamera->nearPlane() ); - mCameraBeforeDrag->setFarPlane( mCamera->farPlane() ); - mCameraBeforeDrag->setAspectRatio( mCamera->aspectRatio() ); - mCameraBeforeDrag->setFieldOfView( mCamera->fieldOfView() ); - - mDepthBufferIsReady = false; - mDragPointCalculated = false; - - emit requestDepthBufferCapture(); + setMouseParameters( MouseOperation::Translation ); } } @@ -673,8 +641,7 @@ void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) - mDragPointCalculated = false; - mRotationCenterCalculated = false; + setMouseParameters( MouseOperation::None ); } void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event ) @@ -1080,6 +1047,56 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage ) mDepthBufferImage = depthImage; mDepthBufferIsReady = true; - if ( mIsInZoomInState ) + if ( mCurrentOperation == MouseOperation::Zoom ) + { handleTerrainNavigationWheelZoom(); + } +} + +void QgsCameraController::setMouseParameters( const MouseOperation &newOperation ) +{ + if ( newOperation == mCurrentOperation ) + { + return; + } + + mCurrentOperation = newOperation; + mDepthBufferIsReady = false; + mRotationCenterCalculated = false; + mDragPointCalculated = false; + mZoomPointCalculated = false; + + if ( mCurrentOperation != MouseOperation::None ) + { + emit requestDepthBufferCapture(); + } + + Qt3DRender::QCamera *cameraBefore = nullptr; + switch ( mCurrentOperation ) + { + case MouseOperation::None: + break; + + case MouseOperation::Rotation: + cameraBefore = mCameraBeforeRotation.get(); + break; + + case MouseOperation::Translation: + cameraBefore = mCameraBeforeDrag.get(); + break; + + case MouseOperation::Zoom: + cameraBefore = mCameraBeforeZoom.get(); + break; + } + + if ( cameraBefore ) + { + cameraBefore->setProjectionMatrix( mCamera->projectionMatrix() ); + cameraBefore->setNearPlane( mCamera->nearPlane() ); + cameraBefore->setFarPlane( mCamera->farPlane() ); + cameraBefore->setAspectRatio( mCamera->aspectRatio() ); + cameraBefore->setFieldOfView( mCamera->fieldOfView() ); + mCameraPose.updateCamera( cameraBefore ); + } } diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index fbfbe3d750b..a43a07a8d6b 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -225,6 +225,16 @@ class _3D_EXPORT QgsCameraController : public QObject //! Returns a pointer to the scene's engine's window or nullptr if engine is QgsOffscreen3DEngine QWindow *window() const; + enum class MouseOperation + { + None = 0, + Translation, + Rotation, + Zoom + }; + + void setMouseParameters( const MouseOperation &newOperation ); + signals: //! Emitted when camera has been updated void cameraChanged(); @@ -312,7 +322,6 @@ class _3D_EXPORT QgsCameraController : public QObject QVector3D mDragPoint; double mDragDepth; - bool mIsInZoomInState = false; std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeZoom; bool mZoomPointCalculated = false; QVector3D mZoomPoint; @@ -330,6 +339,8 @@ class _3D_EXPORT QgsCameraController : public QObject double mCumulatedWheelY = 0; + MouseOperation mCurrentOperation = MouseOperation::None; + friend QgsCameraController4Test; }; From 7c28c604936abaae42aee364377c717196c2e62e Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 17 Nov 2023 20:43:14 +0100 Subject: [PATCH 64/97] qgscameracontroller: Fix combination of rotation and zoom with the mouse While pressing the left button of the mouse, some sequences do not work properly. For example, : rotate around a clicked point, zoom-in and then rotate again. The last rotation does not work because a new rotation center (mRotationCenter) has not been computed. This is because the `mRotationCenterCalculated` flag has not been updated. This issue is fixed by calling `QgsCameraController::setMouseParameters` accordingly in `QgsCameraController::onPositionChangedTerrainNavigation`. --- src/3d/qgscameracontroller.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 2ede0aa984c..49a8fa783cc 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -325,6 +325,8 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { // rotate/tilt using mouse (camera moves as it rotates around the clicked point) + setMouseParameters( MouseOperation::Rotation ); + float scale = static_cast( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) ); float pitchDiff = 180.0f * static_cast( mouse->y() - mMiddleButtonClickPos.y() ) / scale; float yawDiff = -180.0f * static_cast( mouse->x() - mMiddleButtonClickPos.x() ) / scale; @@ -386,6 +388,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE else if ( hasLeftButton && !hasShift && !hasCtrl ) { // translation works as if one grabbed a point on the 3D viewer and dragged it + setMouseParameters( MouseOperation::Translation ); if ( !mDepthBufferIsReady ) return; @@ -444,6 +447,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } else if ( hasRightButton && !hasShift && !hasCtrl ) { + setMouseParameters( MouseOperation::Zoom ); if ( !mDepthBufferIsReady ) return; From 87d6d518ee94773dd79061b210269b4d4624e280 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 20 Nov 2023 18:42:36 +0100 Subject: [PATCH 65/97] qgscameracontroller: Use only one camerabefore These different cameras are never used at the same time. --- src/3d/qgscameracontroller.cpp | 66 +++++++++-------------------- src/3d/qgscameracontroller.h | 5 +-- tests/src/3d/testqgs3drendering.cpp | 10 ++--- 3 files changed, 28 insertions(+), 53 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 49a8fa783cc..26114f328d1 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -31,9 +31,7 @@ QgsCameraController::QgsCameraController( Qgs3DMapScene *scene ) : Qt3DCore::QEntity( scene ) , mScene( scene ) , mCamera( scene->engine()->camera() ) - , mCameraBeforeRotation( new Qt3DRender::QCamera ) - , mCameraBeforeDrag( new Qt3DRender::QCamera ) - , mCameraBeforeZoom( new Qt3DRender::QCamera ) + , mCameraBefore( new Qt3DRender::QCamera ) , mMouseHandler( new Qt3DInput::QMouseHandler ) , mKeyboardHandler( new Qt3DInput::QKeyboardHandler ) { @@ -280,7 +278,7 @@ void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse ) } } -bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *cameraBefore, double &depth, QVector3D &worldPosition ) +bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition ) { depth = sampleDepthBuffer( mDepthBufferImage, position.x(), position.y() ); if ( !std::isfinite( depth ) ) @@ -290,7 +288,7 @@ bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QC } else { - worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), cameraBefore ); + worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore ); if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) ) { QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 ); @@ -338,10 +336,10 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mMiddleButtonClickPos, mCameraBeforeRotation.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mMiddleButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) { mRotationCenter = worldPosition; - mRotationDistanceFromCenter = ( mRotationCenter - mCameraBeforeRotation->position() ).length(); + mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length(); emit cameraRotationCenterChanged( mRotationCenter ); mRotationCenterCalculated = true; } @@ -397,7 +395,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBeforeDrag.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) { mDragDepth = depth; mDragPoint = worldPosition; @@ -406,11 +404,11 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } - QVector3D cameraBeforeDragPos = mCameraBeforeDrag->position(); + QVector3D cameraBeforeDragPos = mCameraBefore->position(); - QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBeforeDrag.get() ); - QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBeforeDrag->position() ).normalized(); - QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBeforeDrag->position() ).normalized(); + QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() ); + QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized(); + QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized(); // Make sure the rays are not horizontal (add small y shift if it is) if ( cameraBeforeToMoveToPos.y() == 0 ) @@ -433,7 +431,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE QVector3D shiftVector = to - from; - mCameraPose.setCenterPoint( mCameraBeforeDrag->viewCenter() + shiftVector ); + mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector ); updateCameraFromPose(); } else if ( hasLeftButton && hasShift && hasCtrl ) @@ -455,14 +453,14 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBeforeDrag.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) { mDragPoint = worldPosition; mDragPointCalculated = true; } } - float dist = ( mCameraBeforeDrag->position() - mDragPoint ).length(); + float dist = ( mCameraBefore->position() - mDragPoint ).length(); int yOffset = 0; int screenHeight = mScene->engine()->size().height(); @@ -537,7 +535,7 @@ void QgsCameraController::handleTerrainNavigationWheelZoom() { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mMousePos, mCameraBeforeZoom.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) ) { mZoomPoint = worldPosition; mZoomPointCalculated = true; @@ -546,7 +544,7 @@ void QgsCameraController::handleTerrainNavigationWheelZoom() float f = mCumulatedWheelY / ( 120.0 * 24.0 ); - double dist = ( mZoomPoint - mCameraBeforeZoom->position() ).length(); + double dist = ( mZoomPoint - mCameraBefore->position() ).length(); dist -= dist * f; // First transformation : Shift camera position and view center and rotate the camera @@ -1073,34 +1071,12 @@ void QgsCameraController::setMouseParameters( const MouseOperation &newOperation if ( mCurrentOperation != MouseOperation::None ) { emit requestDepthBufferCapture(); - } - Qt3DRender::QCamera *cameraBefore = nullptr; - switch ( mCurrentOperation ) - { - case MouseOperation::None: - break; - - case MouseOperation::Rotation: - cameraBefore = mCameraBeforeRotation.get(); - break; - - case MouseOperation::Translation: - cameraBefore = mCameraBeforeDrag.get(); - break; - - case MouseOperation::Zoom: - cameraBefore = mCameraBeforeZoom.get(); - break; - } - - if ( cameraBefore ) - { - cameraBefore->setProjectionMatrix( mCamera->projectionMatrix() ); - cameraBefore->setNearPlane( mCamera->nearPlane() ); - cameraBefore->setFarPlane( mCamera->farPlane() ); - cameraBefore->setAspectRatio( mCamera->aspectRatio() ); - cameraBefore->setFieldOfView( mCamera->fieldOfView() ); - mCameraPose.updateCamera( cameraBefore ); + mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() ); + mCameraBefore->setNearPlane( mCamera->nearPlane() ); + mCameraBefore->setFarPlane( mCamera->farPlane() ); + mCameraBefore->setAspectRatio( mCamera->aspectRatio() ); + mCameraBefore->setFieldOfView( mCamera->fieldOfView() ); + mCameraPose.updateCamera( mCameraBefore.get() ); } } diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index a43a07a8d6b..8ddcbce4f6b 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -308,21 +308,20 @@ class _3D_EXPORT QgsCameraController : public QObject bool mDepthBufferIsReady = false; QImage mDepthBufferImage; + std::unique_ptr< Qt3DRender::QCamera > mCameraBefore; + QPoint mMiddleButtonClickPos; bool mRotationCenterCalculated = false; QVector3D mRotationCenter; double mRotationDistanceFromCenter; double mRotationPitch = 0; double mRotationYaw = 0; - std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeRotation; QPoint mDragButtonClickPos; - std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeDrag; bool mDragPointCalculated = false; QVector3D mDragPoint; double mDragDepth; - std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeZoom; bool mZoomPointCalculated = false; QVector3D mZoomPoint; diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 409df27f6f7..0100ef13a8d 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -123,7 +123,7 @@ class QgsCameraController4Test : public QgsCameraController // wraps protected member vars QVector3D zoomPoint() { return mZoomPoint; } double cumulatedWheelY() { return mCumulatedWheelY; } - Qt3DRender::QCamera *cameraBeforeZoom() { return mCameraBeforeZoom.get(); } + Qt3DRender::QCamera *cameraBefore() { return mCameraBefore.get(); } QgsCameraPose *cameraPose() { return &mCameraPose; } }; @@ -1357,7 +1357,7 @@ void TestQgs3DRendering::testDepthBuffer() false, Qt::MouseEventSynthesizedByApplication ); testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent ) ); QCOMPARE( testCam->cumulatedWheelY(), wheelEvent.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBeforeZoom()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); grayImage = convertDepthImageToGray16Image( depthImage ); @@ -1376,7 +1376,7 @@ void TestQgs3DRendering::testDepthBuffer() false, Qt::MouseEventSynthesizedByApplication ); testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent2 ) ); QCOMPARE( testCam->cumulatedWheelY(), wheelEvent2.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBeforeZoom()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); grayImage = convertDepthImageToGray16Image( depthImage ); @@ -1395,7 +1395,7 @@ void TestQgs3DRendering::testDepthBuffer() false, Qt::MouseEventSynthesizedByApplication ); testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent3 ) ); QCOMPARE( testCam->cumulatedWheelY(), wheelEvent3.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBeforeZoom()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); grayImage = convertDepthImageToGray16Image( depthImage ); @@ -1414,7 +1414,7 @@ void TestQgs3DRendering::testDepthBuffer() false, Qt::MouseEventSynthesizedByApplication ); testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent4 ) ); QCOMPARE( testCam->cumulatedWheelY(), wheelEvent4.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBeforeZoom()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); grayImage = convertDepthImageToGray16Image( depthImage ); From 5148a87bbaebcb08f5148295b29c4147dd5235f1 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Tue, 21 Nov 2023 13:15:11 +0100 Subject: [PATCH 66/97] qgscameracontroller: Fix combination of translate and rotate with the mouse While pressing the left button of the mouse, some sequences do not work properly. For example, translate, and then rotate on the clicked point. The rotation does not work because the initial rotation angles and the rotation center are not computed when the rotation starts. This issue is fixed by adding a clickpoint parameter to `QgsCameraController::setMouseParameters`. This allows to set the rotation center to the mouse position when the rotation starts. However, the new `mClickPoint` must not be updated if the two consecutive operations are not a rotation or a translation because that would break a sequence if there is a zoom operation between a rotation or a translation. --- src/3d/qgscameracontroller.cpp | 53 ++++++++++++++++++++-------------- src/3d/qgscameracontroller.h | 7 +++-- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 26114f328d1..bb7792bad5c 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -322,12 +322,11 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) ) { // rotate/tilt using mouse (camera moves as it rotates around the clicked point) - - setMouseParameters( MouseOperation::Rotation ); + setMouseParameters( MouseOperation::Rotation, mMousePos ); float scale = static_cast( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) ); - float pitchDiff = 180.0f * static_cast( mouse->y() - mMiddleButtonClickPos.y() ) / scale; - float yawDiff = -180.0f * static_cast( mouse->x() - mMiddleButtonClickPos.x() ) / scale; + float pitchDiff = 180.0f * static_cast( mouse->y() - mClickPoint.y() ) / scale; + float yawDiff = -180.0f * static_cast( mouse->x() - mClickPoint.x() ) / scale; if ( !mDepthBufferIsReady ) return; @@ -336,7 +335,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mMiddleButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) ) { mRotationCenter = worldPosition; mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length(); @@ -362,7 +361,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE // Second transformation : Shift camera position back { - QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mMiddleButtonClickPos.x(), mMiddleButtonClickPos.y() ), mScene->engine()->size(), mCamera ); + QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( mClickPoint, mScene->engine()->size(), mCamera ); QVector3D clickedPositionWorld = ray.origin() + mRotationDistanceFromCenter * ray.direction(); @@ -386,7 +385,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE else if ( hasLeftButton && !hasShift && !hasCtrl ) { // translation works as if one grabbed a point on the 3D viewer and dragged it - setMouseParameters( MouseOperation::Translation ); + setMouseParameters( MouseOperation::Translation, mMousePos ); if ( !mDepthBufferIsReady ) return; @@ -395,7 +394,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) ) { mDragDepth = depth; mDragPoint = worldPosition; @@ -445,7 +444,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } else if ( hasRightButton && !hasShift && !hasCtrl ) { - setMouseParameters( MouseOperation::Zoom ); + setMouseParameters( MouseOperation::Zoom, mMousePos ); if ( !mDepthBufferIsReady ) return; @@ -453,7 +452,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE { double depth; QVector3D worldPosition; - if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBefore.get(), depth, worldPosition ) ) + if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) ) { mDragPoint = worldPosition; mDragPointCalculated = true; @@ -472,16 +471,16 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } // Applies smoothing - if ( mMousePos.y() > mDragButtonClickPos.y() ) // zoom in + if ( mMousePos.y() > mClickPoint.y() ) // zoom in { - double f = ( double )( mMousePos.y() - mDragButtonClickPos.y() ) / ( double )( screenHeight - mDragButtonClickPos.y() - yOffset ); + double f = ( double )( mMousePos.y() - mClickPoint.y() ) / ( double )( screenHeight - mClickPoint.y() - yOffset ); f = std::max( 0.0, std::min( 1.0, f ) ); f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) ); dist = dist * f; } else // zoom out { - double f = 1 - ( double )( mMousePos.y() + yOffset ) / ( double )( mDragButtonClickPos.y() + yOffset ); + double f = 1 - ( double )( mMousePos.y() + yOffset ) / ( double )( mClickPoint.y() + yOffset ); f = std::max( 0.0, std::min( 1.0, f ) ); f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) ); dist = dist + 2 * dist * f; @@ -500,7 +499,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE // Second transformation : Shift camera position back { - QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mDragButtonClickPos.x(), mDragButtonClickPos.y() ), mScene->engine()->size(), mCamera ); + QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( mClickPoint, mScene->engine()->size(), mCamera ); QVector3D clickedPositionWorld = ray.origin() + dist * ray.direction(); QVector3D shiftVector = clickedPositionWorld - mCamera->viewCenter(); @@ -616,26 +615,21 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ) { mMousePos = QPoint( mouse->x(), mouse->y() ); - mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() ); if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - mRotationPitch = mCameraPose.pitchAngle(); - mRotationYaw = mCameraPose.headingAngle(); - - setMouseParameters( MouseOperation::Rotation ); + setMouseParameters( MouseOperation::Rotation, mMousePos ); } else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton ) { mMousePos = QPoint( mouse->x(), mouse->y() ); - mDragButtonClickPos = QPoint( mouse->x(), mouse->y() ); if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - setMouseParameters( MouseOperation::Translation ); + setMouseParameters( MouseOperation::Translation, mMousePos ); } } @@ -643,6 +637,7 @@ void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) + mClickPoint = QPoint( 0, 0 ); setMouseParameters( MouseOperation::None ); } @@ -1055,13 +1050,27 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage ) } } -void QgsCameraController::setMouseParameters( const MouseOperation &newOperation ) +void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint ) { if ( newOperation == mCurrentOperation ) { return; } + // click point and rotation angles are updated if: + // - it has never been computed + // - the current and new operations are both rotation and translation + // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on + // the click point does not need to be updated because the relative mouse position is kept + // during a zoom operation + if ( mClickPoint == QPoint( 0, 0 ) || + ( ( newOperation == MouseOperation::Rotation || newOperation == MouseOperation::Translation ) && + ( mCurrentOperation == MouseOperation::Rotation || mCurrentOperation == MouseOperation::Translation ) ) ) + { + mClickPoint = clickPoint; + mRotationPitch = mCameraPose.pitchAngle(); + mRotationYaw = mCameraPose.headingAngle(); + } mCurrentOperation = newOperation; mDepthBufferIsReady = false; mRotationCenterCalculated = false; diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 8ddcbce4f6b..1710ff0ff38 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -233,7 +233,7 @@ class _3D_EXPORT QgsCameraController : public QObject Zoom }; - void setMouseParameters( const MouseOperation &newOperation ); + void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint( 0, 0 ) ); signals: //! Emitted when camera has been updated @@ -305,19 +305,20 @@ class _3D_EXPORT QgsCameraController : public QObject //! Last mouse position recorded QPoint mMousePos; + //! click point for a rotation or a translation + QPoint mClickPoint = QPoint( 0, 0 ); + bool mDepthBufferIsReady = false; QImage mDepthBufferImage; std::unique_ptr< Qt3DRender::QCamera > mCameraBefore; - QPoint mMiddleButtonClickPos; bool mRotationCenterCalculated = false; QVector3D mRotationCenter; double mRotationDistanceFromCenter; double mRotationPitch = 0; double mRotationYaw = 0; - QPoint mDragButtonClickPos; bool mDragPointCalculated = false; QVector3D mDragPoint; double mDragDepth; From 3def5d5c1b90c339e5101f89fe883b8e1a1040ab Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 23 Nov 2023 06:21:17 +1000 Subject: [PATCH 67/97] Apply suggestions from code review --- src/3d/qgscameracontroller.cpp | 4 ++-- src/3d/qgscameracontroller.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index bb7792bad5c..a73f5de69f3 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -637,7 +637,7 @@ void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) - mClickPoint = QPoint( 0, 0 ); + mClickPoint = QPoint(); setMouseParameters( MouseOperation::None ); } @@ -1063,7 +1063,7 @@ void QgsCameraController::setMouseParameters( const MouseOperation &newOperation // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on // the click point does not need to be updated because the relative mouse position is kept // during a zoom operation - if ( mClickPoint == QPoint( 0, 0 ) || + if ( mClickPoint.isNull() || ( ( newOperation == MouseOperation::Rotation || newOperation == MouseOperation::Translation ) && ( mCurrentOperation == MouseOperation::Rotation || mCurrentOperation == MouseOperation::Translation ) ) ) { diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 1710ff0ff38..a13b91943d7 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -233,7 +233,7 @@ class _3D_EXPORT QgsCameraController : public QObject Zoom }; - void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint( 0, 0 ) ); + void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint() ); signals: //! Emitted when camera has been updated @@ -306,7 +306,7 @@ class _3D_EXPORT QgsCameraController : public QObject QPoint mMousePos; //! click point for a rotation or a translation - QPoint mClickPoint = QPoint( 0, 0 ); + QPoint mClickPoint; bool mDepthBufferIsReady = false; QImage mDepthBufferImage; From 7e7a8a1f57a9093ac6082df29d8aff8e5bba79cf Mon Sep 17 00:00:00 2001 From: Germap Date: Wed, 22 Nov 2023 18:44:01 -0500 Subject: [PATCH 68/97] [Expressions] Add tags to layer_property function Fix #39828 --- resources/function_help/json/layer_property | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/function_help/json/layer_property b/resources/function_help/json/layer_property index 3173099da55..0070b84caa1 100644 --- a/resources/function_help/json/layer_property +++ b/resources/function_help/json/layer_property @@ -20,5 +20,5 @@ "expression": "layer_property('landsat','crs')", "returns": "'EPSG:4326'" }], - "tags": ["property", "matching", "metadata"] + "tags": ["property", "matching", "metadata", "layer name", "layer id", "title", "abstract", "keywords", "data url", "attribution", "attribution url", "layer source", "minimum scale", "min scale", "maximum scale", "max scale", "is editable", "crs", "crs definition", "crs description", "layer extent", "distance units", "layer type", "storage type", "geometry type", "feature count", "file path"] } From b96e48b80870734570b8b82b7b583759d66af19d Mon Sep 17 00:00:00 2001 From: Germap Date: Wed, 22 Nov 2023 18:56:41 -0500 Subject: [PATCH 69/97] [Expressions] Clean up tags in age function Previous tags were generated by a script, which produced an ugly tag value ('yearmonthweekdayhourminutesecond'). --- resources/function_help/json/age | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/function_help/json/age b/resources/function_help/json/age index 0e7f77b173e..3b530657d39 100644 --- a/resources/function_help/json/age +++ b/resources/function_help/json/age @@ -17,5 +17,5 @@ "expression": "hour(age('2012-05-12','2012-05-02'))", "returns": "240" }], - "tags": ["difference", "needs", "datetimes", "order", "extract", "information", "following", "interval", "dates", "functions", "yearmonthweekdayhourminutesecond"] + "tags": ["difference", "needs", "datetimes", "order", "extract", "information", "following", "interval", "dates", "functions", "year", "month", "week", "day", "hour", "minute", "second"] } From a153e900d0c760474df1ad0bc4231e83d3d280a5 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 23 Nov 2023 11:33:31 +0100 Subject: [PATCH 70/97] Do not represent invalid bools with false Fix #55250 --- src/core/fieldformatter/qgscheckboxfieldformatter.cpp | 7 +++++-- tests/src/python/test_qgsfieldformatters.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/core/fieldformatter/qgscheckboxfieldformatter.cpp b/src/core/fieldformatter/qgscheckboxfieldformatter.cpp index 36e48b69ce0..58f3d244e77 100644 --- a/src/core/fieldformatter/qgscheckboxfieldformatter.cpp +++ b/src/core/fieldformatter/qgscheckboxfieldformatter.cpp @@ -51,8 +51,11 @@ QString QgsCheckBoxFieldFormatter::representValue( QgsVectorLayer *layer, int fi const QVariant::Type fieldType = layer->fields().at( fieldIndex ).type(); if ( fieldType == QVariant::Bool ) { - boolValue = value.toBool(); - textValue = boolValue ? QObject::tr( "true" ) : QObject::tr( "false" ); + if ( ! isNull ) + { + boolValue = value.toBool(); + textValue = boolValue ? QObject::tr( "true" ) : QObject::tr( "false" ); + } } else { diff --git a/tests/src/python/test_qgsfieldformatters.py b/tests/src/python/test_qgsfieldformatters.py index 41ab0d23149..19ce9678634 100644 --- a/tests/src/python/test_qgsfieldformatters.py +++ b/tests/src/python/test_qgsfieldformatters.py @@ -422,8 +422,9 @@ class TestQgsCheckBoxFieldFormatter(QgisTestCase): def test_representValue(self): null_value = "NULL" - QgsSettings().setValue("qgis/nullValue", null_value) - layer = QgsVectorLayer("point?field=int:integer&field=str:string", "layer", "memory") + QgsApplication.setNullRepresentation(null_value) + + layer = QgsVectorLayer("point?field=int:integer&field=str:string&field=bool:bool", "layer", "memory") self.assertTrue(layer.isValid()) field_formatter = QgsCheckBoxFieldFormatter() @@ -465,6 +466,12 @@ class TestQgsCheckBoxFieldFormatter(QgisTestCase): self.assertEqual(field_formatter.representValue(layer, 0, config, None, 'nooh'), 'nooh') self.assertEqual(field_formatter.representValue(layer, 0, config, None, 'oops'), "(oops)") + # bool + config['TextDisplayMethod'] = QgsCheckBoxFieldFormatter.ShowTrueFalse + self.assertEqual(field_formatter.representValue(layer, 2, config, None, True), 'true') + self.assertEqual(field_formatter.representValue(layer, 2, config, None, False), 'false') + self.assertEqual(field_formatter.representValue(layer, 2, config, None, QVariant(QVariant.Type.Bool)), 'NULL') + class TestQgsFallbackFieldFormatter(QgisTestCase): From 41963cf1cad3881d8ed9096b2dd78caa8248adaf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 8 Nov 2023 10:22:06 +1000 Subject: [PATCH 71/97] Remove "__revision__" from python files This changes the commit hash in every release tarball without any changes to the file content cluttering the diff which needs to be reviewed for license & copyright changes. --- .ci/ctest2ci.py | 2 -- tests/src/python/test_qgsaggregatemappingwidget.py | 2 -- tests/src/python/test_qgsdatabaseschemamodel.py | 2 -- tests/src/python/test_qgsdatabasetablemodel.py | 2 -- tests/src/python/test_qgsfieldmappingwidget.py | 2 -- tests/src/python/test_qgsnewvectortabledialog.py | 2 -- tests/src/python/test_qgsproviderconnection_base.py | 2 -- tests/src/python/test_qgsproviderconnection_hana.py | 2 -- tests/src/python/test_qgsproviderconnection_mssql.py | 2 -- tests/src/python/test_qgsproviderconnection_ogr_gpkg.py | 2 -- tests/src/python/test_qgsproviderconnection_oracle.py | 2 -- tests/src/python/test_qgsproviderconnection_postgres.py | 2 -- tests/src/python/test_qgsproviderconnection_spatialite.py | 2 -- tests/src/python/test_qgsproviderconnectionmodel.py | 2 -- tests/src/python/test_qgsqueryresultmodel.py | 2 -- tests/src/python/test_qgsserver_api.py | 2 -- tests/src/python/test_qgsserver_apicontext.py | 2 -- tests/src/python/test_qgsserver_landingpage.py | 2 -- tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py | 2 -- tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py | 2 -- tests/src/python/test_qgsvectorlayer_namedstyle.py | 2 -- 21 files changed, 42 deletions(-) diff --git a/.ci/ctest2ci.py b/.ci/ctest2ci.py index 412cd50b415..f0722f7216c 100755 --- a/.ci/ctest2ci.py +++ b/.ci/ctest2ci.py @@ -21,8 +21,6 @@ __author__ = 'Matthias Kuhn' __date__ = 'March 2017' __copyright__ = '(C) 2017, Matthias Kuhn' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' # This script parses output from ctest and injects # diff --git a/tests/src/python/test_qgsaggregatemappingwidget.py b/tests/src/python/test_qgsaggregatemappingwidget.py index a655d3ec666..773a0073443 100644 --- a/tests/src/python/test_qgsaggregatemappingwidget.py +++ b/tests/src/python/test_qgsaggregatemappingwidget.py @@ -11,8 +11,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Nyall Dawson' __date__ = '03/06/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' from qgis.PyQt.Qt import Qt from qgis.PyQt.QtCore import ( diff --git a/tests/src/python/test_qgsdatabaseschemamodel.py b/tests/src/python/test_qgsdatabaseschemamodel.py index 597c6d1270f..84b80d58b7f 100644 --- a/tests/src/python/test_qgsdatabaseschemamodel.py +++ b/tests/src/python/test_qgsdatabaseschemamodel.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Nyall Dawson' __date__ = '07/03/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsdatabasetablemodel.py b/tests/src/python/test_qgsdatabasetablemodel.py index cdf9137b084..258f9364e56 100644 --- a/tests/src/python/test_qgsdatabasetablemodel.py +++ b/tests/src/python/test_qgsdatabasetablemodel.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Nyall Dawson' __date__ = '07/03/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsfieldmappingwidget.py b/tests/src/python/test_qgsfieldmappingwidget.py index 5275271b97e..ff9d77fa44f 100644 --- a/tests/src/python/test_qgsfieldmappingwidget.py +++ b/tests/src/python/test_qgsfieldmappingwidget.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '16/03/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' from qgis.PyQt.Qt import Qt from qgis.PyQt.QtCore import ( diff --git a/tests/src/python/test_qgsnewvectortabledialog.py b/tests/src/python/test_qgsnewvectortabledialog.py index 220bfb8da58..9a5c4f7d076 100644 --- a/tests/src/python/test_qgsnewvectortabledialog.py +++ b/tests/src/python/test_qgsnewvectortabledialog.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '12/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import shutil diff --git a/tests/src/python/test_qgsproviderconnection_base.py b/tests/src/python/test_qgsproviderconnection_base.py index a094fca2fce..1ce0e00584c 100644 --- a/tests/src/python/test_qgsproviderconnection_base.py +++ b/tests/src/python/test_qgsproviderconnection_base.py @@ -13,8 +13,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '05/08/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os import time diff --git a/tests/src/python/test_qgsproviderconnection_hana.py b/tests/src/python/test_qgsproviderconnection_hana.py index b36c2458a2d..ad04b6f22b3 100644 --- a/tests/src/python/test_qgsproviderconnection_hana.py +++ b/tests/src/python/test_qgsproviderconnection_hana.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Maxim Rylov' __date__ = '02/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsproviderconnection_mssql.py b/tests/src/python/test_qgsproviderconnection_mssql.py index c5b69c09bca..0fd8737b559 100644 --- a/tests/src/python/test_qgsproviderconnection_mssql.py +++ b/tests/src/python/test_qgsproviderconnection_mssql.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '12/03/2020' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsproviderconnection_ogr_gpkg.py b/tests/src/python/test_qgsproviderconnection_ogr_gpkg.py index 4d9d76bf86e..6ae6fcfc294 100644 --- a/tests/src/python/test_qgsproviderconnection_ogr_gpkg.py +++ b/tests/src/python/test_qgsproviderconnection_ogr_gpkg.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '10/08/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import shutil diff --git a/tests/src/python/test_qgsproviderconnection_oracle.py b/tests/src/python/test_qgsproviderconnection_oracle.py index 71cb9532c37..2726623fb7b 100644 --- a/tests/src/python/test_qgsproviderconnection_oracle.py +++ b/tests/src/python/test_qgsproviderconnection_oracle.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Julien Cabieces' __date__ = '28/12/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsproviderconnection_postgres.py b/tests/src/python/test_qgsproviderconnection_postgres.py index 82eb6aafac9..92e8a6fa395 100644 --- a/tests/src/python/test_qgsproviderconnection_postgres.py +++ b/tests/src/python/test_qgsproviderconnection_postgres.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '10/08/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsproviderconnection_spatialite.py b/tests/src/python/test_qgsproviderconnection_spatialite.py index 13f360c9959..62fbd1b3ba2 100644 --- a/tests/src/python/test_qgsproviderconnection_spatialite.py +++ b/tests/src/python/test_qgsproviderconnection_spatialite.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '28/10/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os import shutil diff --git a/tests/src/python/test_qgsproviderconnectionmodel.py b/tests/src/python/test_qgsproviderconnectionmodel.py index 8afe44d1290..338e81ed2fe 100644 --- a/tests/src/python/test_qgsproviderconnectionmodel.py +++ b/tests/src/python/test_qgsproviderconnectionmodel.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Nyall Dawson' __date__ = '07/08/2020' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os import shutil diff --git a/tests/src/python/test_qgsqueryresultmodel.py b/tests/src/python/test_qgsqueryresultmodel.py index d2ff68c7ac0..bceea5bcf8a 100644 --- a/tests/src/python/test_qgsqueryresultmodel.py +++ b/tests/src/python/test_qgsqueryresultmodel.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '24/12/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsserver_api.py b/tests/src/python/test_qgsserver_api.py index 76f2761485e..326ab03e2e4 100644 --- a/tests/src/python/test_qgsserver_api.py +++ b/tests/src/python/test_qgsserver_api.py @@ -11,8 +11,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '17/04/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import json import os diff --git a/tests/src/python/test_qgsserver_apicontext.py b/tests/src/python/test_qgsserver_apicontext.py index 7ab484b01ae..798fd264304 100644 --- a/tests/src/python/test_qgsserver_apicontext.py +++ b/tests/src/python/test_qgsserver_apicontext.py @@ -11,8 +11,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '11/07/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsserver_landingpage.py b/tests/src/python/test_qgsserver_landingpage.py index 7c103210371..3082c49a30a 100644 --- a/tests/src/python/test_qgsserver_landingpage.py +++ b/tests/src/python/test_qgsserver_landingpage.py @@ -11,8 +11,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '03/08/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import json import os diff --git a/tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py b/tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py index 12b80747c4b..ce4099ac899 100644 --- a/tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py +++ b/tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py @@ -12,8 +12,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '22/01/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py b/tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py index 037c43f65c3..ea867266aba 100644 --- a/tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py +++ b/tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py @@ -12,8 +12,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '13/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' import os diff --git a/tests/src/python/test_qgsvectorlayer_namedstyle.py b/tests/src/python/test_qgsvectorlayer_namedstyle.py index 66288b181c0..f7e81069533 100644 --- a/tests/src/python/test_qgsvectorlayer_namedstyle.py +++ b/tests/src/python/test_qgsvectorlayer_namedstyle.py @@ -9,8 +9,6 @@ the Free Software Foundation; either version 2 of the License, or __author__ = 'Alessandro Pasotti' __date__ = '22/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -# This will get replaced with a git SHA1 when you do a git archive -__revision__ = '$Format:%H$' from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsMapLayer, QgsReadWriteContext, QgsVectorLayer From 406e1d6b9a38fdbbe320de192177753dbef0d7e4 Mon Sep 17 00:00:00 2001 From: DelazJ Date: Thu, 23 Nov 2023 11:42:48 +0100 Subject: [PATCH 72/97] Harmonize user facing labels for GDAL resampling methods --- python/plugins/processing/algs/gdal/buildvrt.py | 8 ++++---- python/plugins/processing/algs/gdal/gdal2tiles.py | 10 +++++----- python/plugins/processing/algs/gdal/gdaladdo.py | 6 +++--- python/plugins/processing/algs/gdal/pansharp.py | 8 ++++---- python/plugins/processing/algs/gdal/retile.py | 8 ++++---- python/plugins/processing/algs/gdal/warp.py | 12 ++++++------ .../processing/qgsalgorithmalignsingleraster.cpp | 14 +++++++------- .../georeferencer/qgstransformsettingsdialog.cpp | 8 ++++---- src/app/options/qgsrasterrenderingoptions.cpp | 8 ++++---- src/core/providers/gdal/qgsgdalprovider.cpp | 8 ++++---- src/gui/raster/qgsresamplingutils.cpp | 8 ++++---- 11 files changed, 49 insertions(+), 49 deletions(-) diff --git a/python/plugins/processing/algs/gdal/buildvrt.py b/python/plugins/processing/algs/gdal/buildvrt.py index ffa6eac42e0..589cf66e541 100644 --- a/python/plugins/processing/algs/gdal/buildvrt.py +++ b/python/plugins/processing/algs/gdal/buildvrt.py @@ -89,10 +89,10 @@ class buildvrt(GdalAlgorithm): return True, '' self.RESAMPLING_OPTIONS = ((self.tr('Nearest Neighbour'), 'nearest'), - (self.tr('Bilinear'), 'bilinear'), - (self.tr('Cubic Convolution'), 'cubic'), - (self.tr('B-Spline Convolution'), 'cubicspline'), - (self.tr('Lanczos Windowed Sinc'), 'lanczos'), + (self.tr('Bilinear (2x2 Kernel)'), 'bilinear'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'), (self.tr('Average'), 'average'), (self.tr('Mode'), 'mode')) diff --git a/python/plugins/processing/algs/gdal/gdal2tiles.py b/python/plugins/processing/algs/gdal/gdal2tiles.py index 3f85f1bba1a..bdda66d3aa0 100644 --- a/python/plugins/processing/algs/gdal/gdal2tiles.py +++ b/python/plugins/processing/algs/gdal/gdal2tiles.py @@ -61,11 +61,11 @@ class gdal2tiles(GdalAlgorithm): (self.tr('Raster'), 'raster')) self.methods = ((self.tr('Average'), 'average'), - (self.tr('Nearest neighbour'), 'near'), - (self.tr('Bilinear'), 'bilinear'), - (self.tr('Cubic'), 'cubic'), - (self.tr('Cubic spline'), 'cubicspline'), - (self.tr('Lanczos windowed sinc'), 'lanczos'), + (self.tr('Nearest Neighbour'), 'near'), + (self.tr('Bilinear (2x2 Kernel)'), 'bilinear'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'), (self.tr('Antialias'), 'antialias')) self.viewers = ((self.tr('All'), 'all'), diff --git a/python/plugins/processing/algs/gdal/gdaladdo.py b/python/plugins/processing/algs/gdal/gdaladdo.py index bc39a3b42f5..9bf42c06c04 100644 --- a/python/plugins/processing/algs/gdal/gdaladdo.py +++ b/python/plugins/processing/algs/gdal/gdaladdo.py @@ -52,9 +52,9 @@ class gdaladdo(GdalAlgorithm): self.methods = ((self.tr('Nearest Neighbour (default)'), 'nearest'), (self.tr('Average'), 'average'), (self.tr('Gaussian'), 'gauss'), - (self.tr('Cubic Convolution'), 'cubic'), - (self.tr('B-Spline Convolution'), 'cubicspline'), - (self.tr('Lanczos Windowed Sinc'), 'lanczos'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'), (self.tr('Average MP'), 'average_mp'), (self.tr('Average in Mag/Phase Space'), 'average_magphase'), (self.tr('Mode'), 'mode')) diff --git a/python/plugins/processing/algs/gdal/pansharp.py b/python/plugins/processing/algs/gdal/pansharp.py index ef678d435eb..ffeed71941f 100644 --- a/python/plugins/processing/algs/gdal/pansharp.py +++ b/python/plugins/processing/algs/gdal/pansharp.py @@ -51,10 +51,10 @@ class pansharp(GdalAlgorithm): def initAlgorithm(self, config=None): self.methods = ((self.tr('Nearest Neighbour'), 'nearest'), - (self.tr('Bilinear'), 'bilinear'), - (self.tr('Cubic'), 'cubic'), - (self.tr('Cubic Spline'), 'cubicspline'), - (self.tr('Lanczos Windowed Sinc'), 'lanczos'), + (self.tr('Bilinear (2x2 Kernel)'), 'bilinear'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'), (self.tr('Average'), 'average')) self.addParameter(QgsProcessingParameterRasterLayer(self.SPECTRAL, diff --git a/python/plugins/processing/algs/gdal/retile.py b/python/plugins/processing/algs/gdal/retile.py index 8422d7544c6..31f9ec0481c 100644 --- a/python/plugins/processing/algs/gdal/retile.py +++ b/python/plugins/processing/algs/gdal/retile.py @@ -61,10 +61,10 @@ class retile(GdalAlgorithm): def initAlgorithm(self, config=None): self.methods = ((self.tr('Nearest Neighbour'), 'near'), - (self.tr('Bilinear'), 'bilinear'), - (self.tr('Cubic'), 'cubic'), - (self.tr('Cubic Spline'), 'cubicspline'), - (self.tr('Lanczos Windowed Sinc'), 'lanczos'),) + (self.tr('Bilinear (2x2 Kernel)'), 'bilinear'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),) self.addParameter(QgsProcessingParameterMultipleLayers(self.INPUT, self.tr('Input files'), diff --git a/python/plugins/processing/algs/gdal/warp.py b/python/plugins/processing/algs/gdal/warp.py index 71b66b01511..a29b7efe547 100644 --- a/python/plugins/processing/algs/gdal/warp.py +++ b/python/plugins/processing/algs/gdal/warp.py @@ -61,17 +61,17 @@ class warp(GdalAlgorithm): def initAlgorithm(self, config=None): self.methods = ((self.tr('Nearest Neighbour'), 'near'), - (self.tr('Bilinear'), 'bilinear'), - (self.tr('Cubic'), 'cubic'), - (self.tr('Cubic Spline'), 'cubicspline'), - (self.tr('Lanczos Windowed Sinc'), 'lanczos'), + (self.tr('Bilinear (2x2 Kernel)'), 'bilinear'), + (self.tr('Cubic (4x4 Kernel)'), 'cubic'), + (self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'), + (self.tr('Lanczos (6x6 Kernel)'), 'lanczos'), (self.tr('Average'), 'average'), (self.tr('Mode'), 'mode'), (self.tr('Maximum'), 'max'), (self.tr('Minimum'), 'min'), (self.tr('Median'), 'med'), - (self.tr('First Quartile'), 'q1'), - (self.tr('Third Quartile'), 'q3')) + (self.tr('First Quartile (Q1)'), 'q1'), + (self.tr('Third Quartile (Q3)'), 'q3')) self.TYPES = [self.tr('Use Input Layer Data Type'), 'Byte', 'Int16', 'UInt16', 'UInt32', 'Int32', 'Float32', 'Float64', 'CInt16', 'CInt32', 'CFloat32', 'CFloat64', 'Int8'] diff --git a/src/analysis/processing/qgsalgorithmalignsingleraster.cpp b/src/analysis/processing/qgsalgorithmalignsingleraster.cpp index 7203f5176c6..c012f8a5fff 100644 --- a/src/analysis/processing/qgsalgorithmalignsingleraster.cpp +++ b/src/analysis/processing/qgsalgorithmalignsingleraster.cpp @@ -68,18 +68,18 @@ void QgsAlignSingleRasterAlgorithm::initAlgorithm( const QVariantMap & ) addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); QStringList resamplingMethods; - resamplingMethods << QObject::tr( "Nearest neighbour" ) - << QObject::tr( "Bilinear" ) - << QObject::tr( "Cubic" ) - << QObject::tr( "Cubic spline" ) - << QObject::tr( "Lanczos" ) + resamplingMethods << QObject::tr( "Nearest Neighbour" ) + << QObject::tr( "Bilinear (2x2 Kernel)" ) + << QObject::tr( "Cubic (4x4 Kernel)" ) + << QObject::tr( "Cubic B-Spline (4x4 Kernel)" ) + << QObject::tr( "Lanczos (6x6 Kernel)" ) << QObject::tr( "Average" ) << QObject::tr( "Mode" ) << QObject::tr( "Maximum" ) << QObject::tr( "Minimum" ) << QObject::tr( "Median" ) - << QObject::tr( "First quartile" ) - << QObject::tr( "Third quartile" ); + << QObject::tr( "First Quartile (Q1)" ) + << QObject::tr( "Third Quartile (Q3)" ); addParameter( new QgsProcessingParameterEnum( QStringLiteral( "RESAMPLING_METHOD" ), QObject::tr( "Resampling method" ), resamplingMethods, false, 0, false ) ); addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "RESCALE" ), QObject::tr( "Rescale values according to the cell size" ), false ) ); addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "REFERENCE_LAYER" ), QObject::tr( "Reference layer" ) ) ); diff --git a/src/app/georeferencer/qgstransformsettingsdialog.cpp b/src/app/georeferencer/qgstransformsettingsdialog.cpp index d885119fe55..4d09d7a9ea4 100644 --- a/src/app/georeferencer/qgstransformsettingsdialog.cpp +++ b/src/app/georeferencer/qgstransformsettingsdialog.cpp @@ -117,10 +117,10 @@ QgsTransformSettingsDialog::QgsTransformSettingsDialog( Qgis::LayerType type, co cmbCompressionComboBox->addItem( tr( "DEFLATE" ), QStringLiteral( "DEFLATE" ) ); cmbResampling->addItem( tr( "Nearest Neighbour" ), static_cast< int >( QgsImageWarper::ResamplingMethod::NearestNeighbour ) ); - cmbResampling->addItem( tr( "Linear" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Bilinear ) ); - cmbResampling->addItem( tr( "Cubic" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Cubic ) ); - cmbResampling->addItem( tr( "Cubic Spline" ), static_cast< int >( QgsImageWarper::ResamplingMethod::CubicSpline ) ); - cmbResampling->addItem( tr( "Lanczos" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Lanczos ) ); + cmbResampling->addItem( tr( "Bilinear (2x2 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Bilinear ) ); + cmbResampling->addItem( tr( "Cubic (4x4 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Cubic ) ); + cmbResampling->addItem( tr( "Cubic B-Spline (4x4 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::CubicSpline ) ); + cmbResampling->addItem( tr( "Lanczos (6x6 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Lanczos ) ); connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsTransformSettingsDialog::showHelp ); } diff --git a/src/app/options/qgsrasterrenderingoptions.cpp b/src/app/options/qgsrasterrenderingoptions.cpp index 5361a964791..174d2909e54 100644 --- a/src/app/options/qgsrasterrenderingoptions.cpp +++ b/src/app/options/qgsrasterrenderingoptions.cpp @@ -39,12 +39,12 @@ QgsRasterRenderingOptionsWidget::QgsRasterRenderingOptionsWidget( QWidget *paren spnBlue->setClearValue( 3 ); mZoomedInResamplingComboBox->insertItem( 0, tr( "Nearest Neighbour" ), QStringLiteral( "nearest neighbour" ) ); - mZoomedInResamplingComboBox->insertItem( 1, tr( "Bilinear" ), QStringLiteral( "bilinear" ) ); - mZoomedInResamplingComboBox->insertItem( 2, tr( "Cubic" ), QStringLiteral( "cubic" ) ); + mZoomedInResamplingComboBox->insertItem( 1, tr( "Bilinear (2x2 Kernel)" ), QStringLiteral( "bilinear" ) ); + mZoomedInResamplingComboBox->insertItem( 2, tr( "Cubic (4x4 Kernel)" ), QStringLiteral( "cubic" ) ); mZoomedOutResamplingComboBox->insertItem( 0, tr( "Nearest Neighbour" ), QStringLiteral( "nearest neighbour" ) ); - mZoomedOutResamplingComboBox->insertItem( 1, tr( "Bilinear" ), QStringLiteral( "bilinear" ) ); - mZoomedOutResamplingComboBox->insertItem( 2, tr( "Cubic" ), QStringLiteral( "cubic" ) ); + mZoomedOutResamplingComboBox->insertItem( 1, tr( "Bilinear (2x2 Kernel)" ), QStringLiteral( "bilinear" ) ); + mZoomedOutResamplingComboBox->insertItem( 2, tr( "Cubic (4x4 Kernel)" ), QStringLiteral( "cubic" ) ); QString zoomedInResampling = settings.value( QStringLiteral( "/Raster/defaultZoomedInResampling" ), QStringLiteral( "nearest neighbour" ) ).toString(); mZoomedInResamplingComboBox->setCurrentIndex( mZoomedInResamplingComboBox->findData( zoomedInResampling ) ); diff --git a/src/core/providers/gdal/qgsgdalprovider.cpp b/src/core/providers/gdal/qgsgdalprovider.cpp index 452efab98ee..5844565b9f6 100644 --- a/src/core/providers/gdal/qgsgdalprovider.cpp +++ b/src/core/providers/gdal/qgsgdalprovider.cpp @@ -4265,10 +4265,10 @@ QList > QgsGdalProviderMetadata::pyramidResamplingMethod methods.append( QPair( QStringLiteral( "NEAREST" ), QObject::tr( "Nearest Neighbour" ) ) ); methods.append( QPair( QStringLiteral( "AVERAGE" ), QObject::tr( "Average" ) ) ); methods.append( QPair( QStringLiteral( "GAUSS" ), QObject::tr( "Gauss" ) ) ); - methods.append( QPair( QStringLiteral( "CUBIC" ), QObject::tr( "Cubic" ) ) ); - methods.append( QPair( QStringLiteral( "CUBICSPLINE" ), QObject::tr( "Cubic Spline" ) ) ); - methods.append( QPair( QStringLiteral( "LANCZOS" ), QObject::tr( "Lanczos" ) ) ); - methods.append( QPair( QStringLiteral( "BILINEAR" ), QObject::tr( "Bilinear" ) ) ); + methods.append( QPair( QStringLiteral( "CUBIC" ), QObject::tr( "Cubic (4x4 Kernel)" ) ) ); + methods.append( QPair( QStringLiteral( "CUBICSPLINE" ), QObject::tr( "Cubic B-Spline (4x4 Kernel)" ) ) ); + methods.append( QPair( QStringLiteral( "LANCZOS" ), QObject::tr( "Lanczos (6x6 Kernel)" ) ) ); + methods.append( QPair( QStringLiteral( "BILINEAR" ), QObject::tr( "Bilinear (2x2 Kernel)" ) ) ); methods.append( QPair( QStringLiteral( "MODE" ), QObject::tr( "Mode" ) ) ); methods.append( QPair( QStringLiteral( "NONE" ), QObject::tr( "None" ) ) ); } diff --git a/src/gui/raster/qgsresamplingutils.cpp b/src/gui/raster/qgsresamplingutils.cpp index a12c0b218e6..b8494b28985 100644 --- a/src/gui/raster/qgsresamplingutils.cpp +++ b/src/gui/raster/qgsresamplingutils.cpp @@ -47,8 +47,8 @@ void QgsResamplingUtils::initWidgets( QgsRasterLayer *rasterLayer, for ( QComboBox *combo : {mZoomedInResamplingComboBox, mZoomedOutResamplingComboBox } ) { combo->addItem( QObject::tr( "Nearest Neighbour" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Nearest ) ); - combo->addItem( QObject::tr( "Bilinear" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Bilinear ) ); - combo->addItem( QObject::tr( "Cubic" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Cubic ) ); + combo->addItem( QObject::tr( "Bilinear (2x2 Kernel)" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Bilinear ) ); + combo->addItem( QObject::tr( "Cubic (4x4 Kernel)" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Cubic ) ); } if ( mCbEarlyResampling->isChecked() ) @@ -222,8 +222,8 @@ void QgsResamplingUtils::addExtraEarlyResamplingMethodsToCombos() for ( QComboBox *combo : {mZoomedInResamplingComboBox, mZoomedOutResamplingComboBox } ) { - combo->addItem( QObject::tr( "Cubic Spline" ), static_cast( QgsRasterDataProvider::ResamplingMethod::CubicSpline ) ); - combo->addItem( QObject::tr( "Lanczos" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Lanczos ) ); + combo->addItem( QObject::tr( "Cubic B-Spline (4x4 Kernel)" ), static_cast( QgsRasterDataProvider::ResamplingMethod::CubicSpline ) ); + combo->addItem( QObject::tr( "Lanczos (6x6 Kernel)" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Lanczos ) ); combo->addItem( QObject::tr( "Average" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Average ) ); combo->addItem( QObject::tr( "Mode" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Mode ) ); combo->addItem( QObject::tr( "Gauss" ), static_cast( QgsRasterDataProvider::ResamplingMethod::Gauss ) ); From 2ade7fbadfe47c12e645d3dc948a4f283b422dbc Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 10:31:29 +0100 Subject: [PATCH 73/97] qgscameracontroller: Differentiate translation from zoom on mousepressed This fixes a regression introduced in commit . --- src/3d/qgscameracontroller.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index a73f5de69f3..00b6e0893b8 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -629,7 +629,8 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - setMouseParameters( MouseOperation::Translation, mMousePos ); + const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom; + setMouseParameters( operation, mMousePos ); } } From b991f231b08b1cd9b6727cc734f1a48333a1629a Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 10:37:27 +0100 Subject: [PATCH 74/97] qgscameracontroller: Distinguish zoom with a click from zoom on wheel The `depthBufferCaptured` logic needs to distinguish a zoom made while pressing the right button from a zoom with a wheel otherwise the `onPositionChangedTerrainNavigation` function is never called. This fixes a regression introduced by commit 7c28c604936abaae42aee364377c717196c2e62e --- src/3d/qgscameracontroller.cpp | 6 +++--- src/3d/qgscameracontroller.h | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 00b6e0893b8..dc0c8191127 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -595,9 +595,9 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel ) // see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta mCumulatedWheelY += scaling * wheel->angleDelta().y(); - if ( mCurrentOperation != MouseOperation::Zoom ) + if ( mCurrentOperation != MouseOperation::ZoomWheel ) { - setMouseParameters( MouseOperation::Zoom ); + setMouseParameters( MouseOperation::ZoomWheel ); } else { @@ -1045,7 +1045,7 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage ) mDepthBufferImage = depthImage; mDepthBufferIsReady = true; - if ( mCurrentOperation == MouseOperation::Zoom ) + if ( mCurrentOperation == MouseOperation::ZoomWheel ) { handleTerrainNavigationWheelZoom(); } diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index a13b91943d7..0c15b8c5035 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -230,7 +230,8 @@ class _3D_EXPORT QgsCameraController : public QObject None = 0, Translation, Rotation, - Zoom + Zoom, + ZoomWheel }; void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint() ); From 53b31c7fbd9dff005d964c599d92c617b1c45c25 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 12:35:16 +0100 Subject: [PATCH 75/97] qgscameracontroller: Allow to combine translation and wheel zoom While pressing the left button of the mouse, some sequences do not work properly. For example, translate, zoom on wheel and then translate again does not work because the clickpoint has not been reset once the zoom operation is finished. This issue is fixed by reseting `mClickPoint` once the zoom by wheel is finished. --- src/3d/qgscameracontroller.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index dc0c8191127..5793b6af55b 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -638,7 +638,6 @@ void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse ) { Q_UNUSED( mouse ) - mClickPoint = QPoint(); setMouseParameters( MouseOperation::None ); } @@ -1058,15 +1057,19 @@ void QgsCameraController::setMouseParameters( const MouseOperation &newOperation return; } + if ( newOperation == MouseOperation::None ) + { + mClickPoint = QPoint(); + } // click point and rotation angles are updated if: // - it has never been computed // - the current and new operations are both rotation and translation // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on // the click point does not need to be updated because the relative mouse position is kept // during a zoom operation - if ( mClickPoint.isNull() || - ( ( newOperation == MouseOperation::Rotation || newOperation == MouseOperation::Translation ) && - ( mCurrentOperation == MouseOperation::Rotation || mCurrentOperation == MouseOperation::Translation ) ) ) + else if ( mClickPoint.isNull() || + ( ( newOperation == MouseOperation::Rotation || newOperation == MouseOperation::Translation ) && + ( mCurrentOperation == MouseOperation::Rotation || mCurrentOperation == MouseOperation::Translation ) ) ) { mClickPoint = clickPoint; mRotationPitch = mCameraPose.pitchAngle(); From 8a31dc0a3d6a4d5d236756c0c66780c7882a28cf Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 11:26:59 +0100 Subject: [PATCH 76/97] qgscameracontroller: Allow to combine translate and rotate by camera While pressing the left button of the mouse, some sequences do not work properly. For example, translate, rotate around the camera and then translate again does not work because the rotation angles and the click point have not been updated after the rotation. This issue is fixed by adding a `setMouseParameters` to the rotation around the camera logic. --- src/3d/qgscameracontroller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 5793b6af55b..5de077583a4 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -377,6 +377,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } else if ( hasLeftButton && hasCtrl && !hasShift ) { + setMouseParameters( MouseOperation::Rotation ); // rotate/tilt using mouse (camera stays at one position as it rotates) const float diffPitch = 0.2f * dy; const float diffYaw = - 0.2f * dx; From 5b52301ec6f0532f573eb700ad4c835ac8c89b09 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 12:32:51 +0100 Subject: [PATCH 77/97] qgscameracontroller: Allow to combine two type of rotations While pressing the left button of the mouse, some sequences do not work properly. For example, rotate around the clicked point (shift), then rotate around the camera (ctrl) and rotate around the clicked point (shift) again does not work because there is no distinction between these two types of operation in `MouseOperation`. This issue is fixed by adding `RotationCamera` to `MouseOperation` and renaming `Rotation` to `RotationCamera`. --- src/3d/qgscameracontroller.cpp | 28 ++++++++++++++++++++-------- src/3d/qgscameracontroller.h | 16 +++++++++++++++- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 5de077583a4..cfb70930414 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -322,7 +322,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) ) { // rotate/tilt using mouse (camera moves as it rotates around the clicked point) - setMouseParameters( MouseOperation::Rotation, mMousePos ); + setMouseParameters( MouseOperation::RotationCenter, mMousePos ); float scale = static_cast( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) ); float pitchDiff = 180.0f * static_cast( mouse->y() - mClickPoint.y() ) / scale; @@ -377,7 +377,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE } else if ( hasLeftButton && hasCtrl && !hasShift ) { - setMouseParameters( MouseOperation::Rotation ); + setMouseParameters( MouseOperation::RotationCamera ); // rotate/tilt using mouse (camera stays at one position as it rotates) const float diffPitch = 0.2f * dy; const float diffYaw = - 0.2f * dx; @@ -613,14 +613,22 @@ void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse ) { mKeyboardHandler->setFocus( true ); - if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ) + if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || + ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) || + ( ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ) { mMousePos = QPoint( mouse->x(), mouse->y() ); if ( mCaptureFpsMouseMovements ) mIgnoreNextMouseMove = true; - setMouseParameters( MouseOperation::Rotation, mMousePos ); + const MouseOperation operation + { + ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ? + MouseOperation::RotationCamera : + MouseOperation::RotationCenter + }; + setMouseParameters( operation, mMousePos ); } else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton ) @@ -1051,6 +1059,12 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage ) } } +bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const +{ + return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) && + std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate ); +} + void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint ) { if ( newOperation == mCurrentOperation ) @@ -1068,9 +1082,7 @@ void QgsCameraController::setMouseParameters( const MouseOperation &newOperation // Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on // the click point does not need to be updated because the relative mouse position is kept // during a zoom operation - else if ( mClickPoint.isNull() || - ( ( newOperation == MouseOperation::Rotation || newOperation == MouseOperation::Translation ) && - ( mCurrentOperation == MouseOperation::Rotation || mCurrentOperation == MouseOperation::Translation ) ) ) + else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) ) { mClickPoint = clickPoint; mRotationPitch = mCameraPose.pitchAngle(); @@ -1082,7 +1094,7 @@ void QgsCameraController::setMouseParameters( const MouseOperation &newOperation mDragPointCalculated = false; mZoomPointCalculated = false; - if ( mCurrentOperation != MouseOperation::None ) + if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera ) { emit requestDepthBufferCapture(); diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 0c15b8c5035..58cc5aa275e 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -229,11 +229,25 @@ class _3D_EXPORT QgsCameraController : public QObject { None = 0, Translation, - Rotation, + RotationCamera, + RotationCenter, Zoom, ZoomWheel }; + // This list gathers all the rotation and translation operations. + // It is used to update the appropriate parameters when successive + // translation and rotation happen. + const QList mTranslateOrRotate = + { + MouseOperation::Translation, + MouseOperation::RotationCamera, + MouseOperation::RotationCenter + }; + + // check that current sequence (current operation and new operation) is a rotation or translation + bool isATranslationRotationSequence( MouseOperation newOperation ) const; + void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint() ); signals: From 556d74aea07baffd410a228854f5ba042ce6d688 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 23 Nov 2023 15:13:16 +0100 Subject: [PATCH 78/97] qgscameracontroller: Add comments for MouseOperation enum --- src/3d/qgscameracontroller.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 58cc5aa275e..1e6e8d9ee73 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -225,14 +225,15 @@ class _3D_EXPORT QgsCameraController : public QObject //! Returns a pointer to the scene's engine's window or nullptr if engine is QgsOffscreen3DEngine QWindow *window() const; + //! List of possible operations with the mouse in TerrainBased navigation enum class MouseOperation { - None = 0, - Translation, - RotationCamera, - RotationCenter, - Zoom, - ZoomWheel + None = 0, // no operation + Translation, // left button pressed, no modifier + RotationCamera, // left button pressed + ctrl modifier + RotationCenter, // left button pressed + shift modifier + Zoom, // right button pressed + ZoomWheel // mouse wheel scroll }; // This list gathers all the rotation and translation operations. From b6fffac7a129aa624d9a2d6b5549a78379c126e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Wed, 15 Nov 2023 11:12:02 +0000 Subject: [PATCH 79/97] QgsVectorLayerEditUtils: force hole order in addring --- src/core/vector/qgsvectorlayereditutils.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index f7b9391f4dd..7b9bc65c616 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -183,7 +183,14 @@ Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ //add ring takes ownership of ring, and deletes it if there's an error QgsGeometry g = f.geometry(); - addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) ); + if ( ring->orientation() == Qgis::AngularDirection::Clockwise ) + { + addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) ); + } + else + { + addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->reversed() ) ); + } if ( addRingReturnCode == Qgis::GeometryOperationResult::Success ) { layer->changeGeometry( f.id(), g ); From 0f27fd3b1e615e2da3c2fc8c178ef7e91816ca41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Tue, 21 Nov 2023 10:48:12 +0100 Subject: [PATCH 80/97] QgsGeometry: add methods to return orientation --- .../geometry/qgsgeometry.sip.in | 75 +++++++++++++++++++ src/core/geometry/qgsgeometry.cpp | 28 +++++++ src/core/geometry/qgsgeometry.h | 48 ++++++++++++ 3 files changed, 151 insertions(+) diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index fe868f7dc8c..d847d8d3bb7 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2549,6 +2549,69 @@ They require builds based on GEOS 3.10 or later. .. versionadded:: 3.0 %End + Qgis::AngularDirection polygonOrientation() const; +%Docstring +Returns the orientation of the polygon. + +.. warning:: + + Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + you have to perform in deep verification. + +.. warning:: + + returns :py:class:`Qgis`.AngularDirection.Clockwise if the geometry is not a polygon type + +.. versionadded:: 3.36 +%End + + bool isPolygonCounterClockwise() const; +%Docstring +Returns True if the Polygon is counter-clockwise. + +.. warning:: + + Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + you have to perform in deep verification. + +.. warning:: + + returns false if the geometry is not a polygon type + + +.. seealso:: :py:func:`isPolygonClockwise` + +.. seealso:: :py:func:`forcePolygonClockwise` + +.. seealso:: :py:func:`forcePolygonCounterClockwise` + +.. versionadded:: 3.36 +%End + + bool isPolygonClockwise() const; +%Docstring +Returns True if the Polygon is clockwise. + +.. warning:: + + Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + you have to perform in deep verification. + +.. warning:: + + returns true if the geometry is not a polygon type + + +.. seealso:: :py:func:`isPolygonCounterClockwise` + +.. seealso:: :py:func:`forcePolygonClockwise` + +.. seealso:: :py:func:`forcePolygonCounterClockwise` + +.. versionadded:: 3.36 +%End + + QgsGeometry forceRHR() const; %Docstring Forces geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon @@ -2560,6 +2623,10 @@ and the interior rings in a counter-clockwise direction. Due to the conflicting definitions of the right-hand-rule in general use, it is recommended to use the explicit :py:func:`~QgsGeometry.forcePolygonClockwise` or :py:func:`~QgsGeometry.forcePolygonCounterClockwise` methods instead. +.. seealso:: :py:func:`isPolygonClockwise` + +.. seealso:: :py:func:`isPolygonCounterClockwise` + .. seealso:: :py:func:`forcePolygonClockwise` .. seealso:: :py:func:`forcePolygonCounterClockwise` @@ -2573,6 +2640,10 @@ Forces geometries to respect the exterior ring is clockwise, interior rings are This convention is used primarily by ESRI software. +.. seealso:: :py:func:`isPolygonClockwise` + +.. seealso:: :py:func:`isPolygonCounterClockwise` + .. seealso:: :py:func:`forcePolygonCounterClockwise` .. versionadded:: 3.24 @@ -2584,6 +2655,10 @@ Forces geometries to respect the exterior ring is counter-clockwise, interior ri This convention matches the OGC Simple Features specification. +.. seealso:: :py:func:`isPolygonClockwise` + +.. seealso:: :py:func:`isPolygonCounterClockwise` + .. seealso:: :py:func:`forcePolygonClockwise` .. versionadded:: 3.24 diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index cff7d35f5a9..b9f88f07e68 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3174,6 +3174,34 @@ QgsGeometry QgsGeometry::forceRHR() const return forcePolygonClockwise(); } +Qgis::AngularDirection QgsGeometry::polygonOrientation() const +{ + if ( !d->geometry ) + { + return Qgis::AngularDirection::Clockwise; + } + + if ( isMultipart() ) + { + const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( d->geometry.get() ); + const QgsAbstractGeometry *g = collection->geometryN( 0 ); + if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) ) + { + return cp->exteriorRing()->orientation(); + } + } + else + { + if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( d->geometry.get() ) ) + { + return cp->exteriorRing()->orientation(); + } + } + + return Qgis::AngularDirection::Clockwise; + +} + QgsGeometry QgsGeometry::forcePolygonClockwise() const { if ( !d->geometry ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 4c8b8d0a5e6..4d2329f2b81 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2646,6 +2646,48 @@ class CORE_EXPORT QgsGeometry */ QgsGeometry makeValid( Qgis::MakeValidMethod method = Qgis::MakeValidMethod::Linework, bool keepCollapsed = false ) const SIP_THROW( QgsNotSupportedException ); + /** + * Returns the orientation of the polygon. + * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + * you have to perform in deep verification. + * + * \warning returns Qgis::AngularDirection::Clockwise if the geometry is not a polygon type + * + * \since QGIS 3.36 + */ + Qgis::AngularDirection polygonOrientation() const; + + /** + * Returns True if the Polygon is counter-clockwise. + * + * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + * you have to perform in deep verification. + * + * \warning returns false if the geometry is not a polygon type + * + * \see isPolygonClockwise() + * \see forcePolygonClockwise() + * \see forcePolygonCounterClockwise() + * \since QGIS 3.36 + */ + bool isPolygonCounterClockwise() const { return polygonOrientation() == Qgis::AngularDirection::CounterClockwise; } + + /** + * Returns True if the Polygon is clockwise. + * + * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, + * you have to perform in deep verification. + * + * \warning returns true if the geometry is not a polygon type + * + * \see isPolygonCounterClockwise() + * \see forcePolygonClockwise() + * \see forcePolygonCounterClockwise() + * \since QGIS 3.36 + */ + bool isPolygonClockwise() const { return polygonOrientation() == Qgis::AngularDirection::Clockwise; } + + /** * Forces geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon * is to the right of the boundary. In particular, the exterior ring is oriented in a clockwise direction @@ -2654,6 +2696,8 @@ class CORE_EXPORT QgsGeometry * \warning Due to the conflicting definitions of the right-hand-rule in general use, it is recommended * to use the explicit forcePolygonClockwise() or forcePolygonCounterClockwise() methods instead. * + * \see isPolygonClockwise() + * \see isPolygonCounterClockwise() * \see forcePolygonClockwise() * \see forcePolygonCounterClockwise() * \since QGIS 3.6 @@ -2665,6 +2709,8 @@ class CORE_EXPORT QgsGeometry * * This convention is used primarily by ESRI software. * + * \see isPolygonClockwise() + * \see isPolygonCounterClockwise() * \see forcePolygonCounterClockwise() * \since QGIS 3.24 */ @@ -2675,6 +2721,8 @@ class CORE_EXPORT QgsGeometry * * This convention matches the OGC Simple Features specification. * + * \see isPolygonClockwise() + * \see isPolygonCounterClockwise() * \see forcePolygonClockwise() * \since QGIS 3.24 */ From 36052d082a54c1ef476bc9a3fdc84f0d39863324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Tue, 21 Nov 2023 10:48:57 +0100 Subject: [PATCH 81/97] QgsVectorLayerEditUtils: use polygonOrientation to reorder rings --- src/core/vector/qgsvectorlayereditutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index 7b9bc65c616..e6a4c0f95fe 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -183,7 +183,7 @@ Qgis::GeometryOperationResult staticAddRing( QgsVectorLayer *layer, std::unique_ //add ring takes ownership of ring, and deletes it if there's an error QgsGeometry g = f.geometry(); - if ( ring->orientation() == Qgis::AngularDirection::Clockwise ) + if ( ring->orientation() != g.polygonOrientation() ) { addRingReturnCode = g.addRing( static_cast< QgsCurve * >( ring->clone() ) ); } From bad18ca1cdb48d24145870bfa83556d8b20a5ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Tue, 21 Nov 2023 16:39:26 +0100 Subject: [PATCH 82/97] QgsVectorLayerEditUtils: use polygonOrientation to reorder parts --- src/core/vector/qgsvectorlayereditutils.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index e6a4c0f95fe..5935eed17f8 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -294,6 +294,7 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addPart( const QgsPointSe Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addPart( QgsCurve *ring, QgsFeatureId featureId ) { + if ( !mLayer->isSpatial() ) return Qgis::GeometryOperationResult::AddPartSelectedGeometryNotFound; @@ -311,9 +312,13 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::addPart( QgsCurve *ring, else { geometry = f.geometry(); + if ( ring->orientation() != geometry.polygonOrientation() ) + { + ring = ring->reversed(); + } } - Qgis::GeometryOperationResult errorCode = geometry.addPart( ring, mLayer->geometryType() ); + if ( errorCode == Qgis::GeometryOperationResult::Success ) { if ( firstPart && QgsWkbTypes::isSingleType( mLayer->wkbType() ) @@ -349,6 +354,7 @@ int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QVector &splitLine, bool topologicalEditing ) { + QgsPointSequence l; for ( QVector::const_iterator it = splitLine.constBegin(); it != splitLine.constEnd(); ++it ) { From 4be1215a8ee18062bf30c71c287cf887765cf3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Wed, 22 Nov 2023 07:59:08 +0100 Subject: [PATCH 83/97] QgsMapToolAddPart: add a test, draw in oposite direction and get part right-ordered --- tests/src/app/testqgsmaptooladdpart.cpp | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/src/app/testqgsmaptooladdpart.cpp b/tests/src/app/testqgsmaptooladdpart.cpp index fd0111d58bb..a8bd798c944 100644 --- a/tests/src/app/testqgsmaptooladdpart.cpp +++ b/tests/src/app/testqgsmaptooladdpart.cpp @@ -41,6 +41,7 @@ class TestQgsMapToolAddPart: public QObject void cleanupTestCase();// will be called after the last testfunction was executed. void testAddPart(); + void testAddPartClockWise(); private: QPoint mapToPoint( double x, double y ); @@ -172,5 +173,53 @@ void TestQgsMapToolAddPart::testAddPart() QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); } +void TestQgsMapToolAddPart::testAddPartClockWise() +{ + mLayerMultiPolygon->select( 1 ); + + // Draw in clockwise + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 15, 15 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 15, 16 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 16, 16 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 16, 15 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 15, 15 ), + Qt::RightButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + const QString wkt = "MultiPolygon (((2 2, 4 2, 4 4, 2 4)),((5 5, 5 5, 6 5, 6 6, 5 6, 5 5)),((15 15, 16 15, 16 16, 15 16, 15 15)))"; + QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); +} QGSTEST_MAIN( TestQgsMapToolAddPart ) #include "testqgsmaptooladdpart.moc" From 8f1c37fb2da74235536cea1545e3c468daefa3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Wed, 22 Nov 2023 08:31:55 +0100 Subject: [PATCH 84/97] QgsMapToolAddRing: add test --- tests/src/app/CMakeLists.txt | 1 + tests/src/app/testqgsmaptooladdring.cpp | 221 ++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 tests/src/app/testqgsmaptooladdring.cpp diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index bbd68f592fc..dbda4d94840 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -22,6 +22,7 @@ set(TESTS testqgslayerpropertiesdialogs.cpp testqgsmapcanvasdockwidget.cpp testqgsmaptooladdpart.cpp + testqgsmaptooladdring.cpp testqgsmaptooleditannotation.cpp testqgsmaptoolidentifyaction.cpp testqgsmaptoollabel.cpp diff --git a/tests/src/app/testqgsmaptooladdring.cpp b/tests/src/app/testqgsmaptooladdring.cpp new file mode 100644 index 00000000000..0579cade9cc --- /dev/null +++ b/tests/src/app/testqgsmaptooladdring.cpp @@ -0,0 +1,221 @@ +/*************************************************************************** + testqgsmaptooladdring.cpp + -------------------------------- + Date : 2023-11-22 + Copyright : (C) 2023 by Loïc Bartoletti + Email : loic dot bartoletti at oslandia 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 "qgstest.h" + +#include "qgisapp.h" +#include "qgsgeometry.h" +#include "qgsmapcanvas.h" +#include "qgsmaptooladdring.h" +#include "qgsproject.h" +#include "qgssettingsregistrycore.h" +#include "qgsvectorlayer.h" +#include "qgsmapmouseevent.h" +#include "testqgsmaptoolutils.h" + + +/** + * \ingroup UnitTests + * This is a unit test for the add ring map tool + */ +class TestQgsMapToolAddRing: public QObject +{ + Q_OBJECT + public: + TestQgsMapToolAddRing(); + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + + void testAddRing(); + void testAddRingClockWise(); + + private: + QPoint mapToPoint( double x, double y ); + + QgisApp *mQgisApp = nullptr; + QgsMapCanvas *mCanvas = nullptr; + QgsMapToolAddRing *mCaptureTool = nullptr; + QgsVectorLayer *mLayerMultiPolygon = nullptr; +}; + +TestQgsMapToolAddRing::TestQgsMapToolAddRing() = default; + + +//runs before all tests +void TestQgsMapToolAddRing::initTestCase() +{ + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + + // Set up the QSettings environment + QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) ); + QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) ); + QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) ); + + mQgisApp = new QgisApp(); + + mCanvas = new QgsMapCanvas(); + + mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) ); + + mCanvas->setFrameStyle( QFrame::NoFrame ); + mCanvas->resize( 512, 512 ); + mCanvas->setExtent( QgsRectangle( 0, 0, 8, 8 ) ); + mCanvas->show(); // to make the canvas resize + mCanvas->hide(); + + // make testing layers + mLayerMultiPolygon = new QgsVectorLayer( QStringLiteral( "MultiPolygon?crs=EPSG:3946" ), QStringLiteral( "multipolygon" ), QStringLiteral( "memory" ) ); + QVERIFY( mLayerMultiPolygon->isValid() ); + QgsProject::instance()->addMapLayers( QList() << mLayerMultiPolygon ); + + mLayerMultiPolygon->startEditing(); + QgsFeature f; + const QString wkt( "MultiPolygon (((0 0, 5 0, 5 5, 0 5, 0 0)))" ); + f.setGeometry( QgsGeometry::fromWkt( wkt ) ); + mLayerMultiPolygon->dataProvider()->addFeatures( QgsFeatureList() << f ); + QCOMPARE( mLayerMultiPolygon->featureCount(), ( long )1 ); + QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); + + mCanvas->setCurrentLayer( mLayerMultiPolygon ); + + // create the tool + mCaptureTool = new QgsMapToolAddRing( mCanvas ); + mCanvas->setMapTool( mCaptureTool ); + + QCOMPARE( mCanvas->mapSettings().outputSize(), QSize( 512, 512 ) ); + QCOMPARE( mCanvas->mapSettings().visibleExtent(), QgsRectangle( 0, 0, 8, 8 ) ); +} + +//runs after all tests +void TestQgsMapToolAddRing::cleanupTestCase() +{ + delete mCaptureTool; + delete mCanvas; + QgsApplication::exitQgis(); +} + +QPoint TestQgsMapToolAddRing::mapToPoint( double x, double y ) +{ + const QgsPointXY mapPoint = mCanvas->mapSettings().mapToPixel().transform( x, y ); + + return QPoint( static_cast( std::round( mapPoint.x() ) ), static_cast( std::round( mapPoint.y() ) ) ); +} + +void TestQgsMapToolAddRing::testAddRing() +{ + mLayerMultiPolygon->select( 1 ); + + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 1, 1 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 1, 2 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 2, 2 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 2, 1 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 1, 1 ), + Qt::RightButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + // TODO: fix https://github.com/qgis/QGIS/issues/55361 + // const QString wkt = "MultiPolygon (((0 0, 5 0, 5 5, 0 5, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)))"; + const QString wkt = "MultiPolygon (((0 0, 5 0, 5 5, 0 5, 0 0),CompoundCurve ((1 1, 1 2, 2 2, 2 1, 1 1))))"; + QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); +} + +void TestQgsMapToolAddRing::testAddRingClockWise() +{ + mLayerMultiPolygon->select( 1 ); + + // Draw in clockwise + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 3, 3 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 4, 3 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 4, 4 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 3, 4 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 3, 3 ), + Qt::RightButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + // TODO: fix https://github.com/qgis/QGIS/issues/55361 + // const QString wkt = "MultiPolygon (((0 0, 5 0, 5 5, 0 5, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (3 3, 3 4, 4 4, 4 3, 3 3)))"; + const QString wkt = "MultiPolygon (((0 0, 5 0, 5 5, 0 5, 0 0),CompoundCurve ((1 1, 1 2, 2 2, 2 1, 1 1)),CompoundCurve ((3 3, 3 4, 4 4, 4 3, 3 3))))"; + QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); +} +QGSTEST_MAIN( TestQgsMapToolAddRing ) +#include "testqgsmaptooladdring.moc" From 5452be576dc1716394577723dea81eb43d14259e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Fri, 24 Nov 2023 09:17:13 +0000 Subject: [PATCH 85/97] Qgis::AngularDirection: Add a new enum flag: NoOrientation used as sentinel or for empty (multi)polygon --- src/core/qgis.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/qgis.h b/src/core/qgis.h index 58ffeef9a78..5821eb200ca 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -2565,6 +2565,7 @@ class CORE_EXPORT Qgis { Clockwise, //!< Clockwise direction CounterClockwise, //!< Counter-clockwise direction + NoOrientation, //!< Unknown orientation or sentinel value }; Q_ENUM( AngularDirection ) From 361fbded94980a9f5f5e092a65a38652d0ce1de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Fri, 24 Nov 2023 09:18:02 +0000 Subject: [PATCH 86/97] QgsGeometry: use NoOrientation enum and fix some possible nullptr uses --- python/core/auto_additions/qgis.py | 5 ++++- python/core/auto_generated/geometry/qgsgeometry.sip.in | 6 +++--- python/core/auto_generated/qgis.sip.in | 1 + src/core/geometry/qgsgeometry.cpp | 8 ++++---- src/core/geometry/qgsgeometry.h | 6 +++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 16ba5285126..6fbc491c01c 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -2542,7 +2542,10 @@ QgsCurve.Clockwise.__doc__ = "Clockwise direction" QgsCurve.CounterClockwise = Qgis.AngularDirection.CounterClockwise QgsCurve.CounterClockwise.is_monkey_patched = True QgsCurve.CounterClockwise.__doc__ = "Counter-clockwise direction" -Qgis.AngularDirection.__doc__ = "Angular directions.\n\n.. versionadded:: 3.24\n\n" + '* ``Clockwise``: ' + Qgis.AngularDirection.Clockwise.__doc__ + '\n' + '* ``CounterClockwise``: ' + Qgis.AngularDirection.CounterClockwise.__doc__ +QgsCurve.NoOrientation = Qgis.AngularDirection.NoOrientation +QgsCurve.NoOrientation.is_monkey_patched = True +QgsCurve.NoOrientation.__doc__ = "Unknown orientation or sentinel value" +Qgis.AngularDirection.__doc__ = "Angular directions.\n\n.. versionadded:: 3.24\n\n" + '* ``Clockwise``: ' + Qgis.AngularDirection.Clockwise.__doc__ + '\n' + '* ``CounterClockwise``: ' + Qgis.AngularDirection.CounterClockwise.__doc__ + '\n' + '* ``NoOrientation``: ' + Qgis.AngularDirection.NoOrientation.__doc__ # -- Qgis.AngularDirection.baseClass = Qgis # monkey patching scoped based enum diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index d847d8d3bb7..2cf888a67b0 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2560,7 +2560,7 @@ Returns the orientation of the polygon. .. warning:: - returns :py:class:`Qgis`.AngularDirection.Clockwise if the geometry is not a polygon type + returns :py:class:`Qgis`.AngularDirection.NoOrientation if the geometry is not a polygon type or empty .. versionadded:: 3.36 %End @@ -2576,7 +2576,7 @@ Returns True if the Polygon is counter-clockwise. .. warning:: - returns false if the geometry is not a polygon type + returns false if the geometry is not a polygon type or empty .. seealso:: :py:func:`isPolygonClockwise` @@ -2599,7 +2599,7 @@ Returns True if the Polygon is clockwise. .. warning:: - returns true if the geometry is not a polygon type + returns true if the geometry is not a polygon type or empty .. seealso:: :py:func:`isPolygonCounterClockwise` diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 7eff6a5bb3d..7f66e63a442 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -1508,6 +1508,7 @@ The development version { Clockwise, CounterClockwise, + NoOrientation, }; enum class RendererUsage diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index b9f88f07e68..a60c263fa08 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3178,7 +3178,7 @@ Qgis::AngularDirection QgsGeometry::polygonOrientation() const { if ( !d->geometry ) { - return Qgis::AngularDirection::Clockwise; + return Qgis::AngularDirection::NoOrientation; } if ( isMultipart() ) @@ -3187,18 +3187,18 @@ Qgis::AngularDirection QgsGeometry::polygonOrientation() const const QgsAbstractGeometry *g = collection->geometryN( 0 ); if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) ) { - return cp->exteriorRing()->orientation(); + return cp->exteriorRing() ? cp->exteriorRing()->orientation() : Qgis::AngularDirection::NoOrientation; } } else { if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( d->geometry.get() ) ) { - return cp->exteriorRing()->orientation(); + return cp->exteriorRing() ? cp->exteriorRing()->orientation() : Qgis::AngularDirection::NoOrientation; } } - return Qgis::AngularDirection::Clockwise; + return Qgis::AngularDirection::NoOrientation; } diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 4d2329f2b81..70da25b2133 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2651,7 +2651,7 @@ class CORE_EXPORT QgsGeometry * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, * you have to perform in deep verification. * - * \warning returns Qgis::AngularDirection::Clockwise if the geometry is not a polygon type + * \warning returns Qgis::AngularDirection::NoOrientation if the geometry is not a polygon type or empty * * \since QGIS 3.36 */ @@ -2663,7 +2663,7 @@ class CORE_EXPORT QgsGeometry * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, * you have to perform in deep verification. * - * \warning returns false if the geometry is not a polygon type + * \warning returns false if the geometry is not a polygon type or empty * * \see isPolygonClockwise() * \see forcePolygonClockwise() @@ -2678,7 +2678,7 @@ class CORE_EXPORT QgsGeometry * \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders, * you have to perform in deep verification. * - * \warning returns true if the geometry is not a polygon type + * \warning returns true if the geometry is not a polygon type or empty * * \see isPolygonCounterClockwise() * \see forcePolygonClockwise() From 6b6ed33a73fe2c5ab117a35ecffb92104a0b364c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Fri, 24 Nov 2023 09:18:34 +0000 Subject: [PATCH 87/97] QgsGeometry.polygonOrientation, is(Counter)Clockwise: add test --- tests/src/python/test_qgsgeometry.py | 115 +++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 2c82338775b..193e5bea0c2 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -7438,6 +7438,121 @@ class TestQgsGeometry(QgisTestCase): res = g1.simplifyCoverageVW(10, True) self.assertEqual(res.asWkt(0), 'GeometryCollection (Polygon ((10 0, 10 10, 0 10, 0 0, 10 0)),Polygon ((10 0, 20 0, 20 10, 10 10, 10 0)))') + def testPolygonOrientation(self): + """ + Test QgsGeometry.polygonOrientation, QgsGeometry.isPolygonClockwise and QgsGeometry.isPolygonCounterClockwise + """ + + # Empty geometry + geometry = QgsGeometry() + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.NoOrientation) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, False) + + # Not a polygon + geometry = QgsGeometry.fromWkt('Point(1 2)') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.NoOrientation) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, False) + + # Closed curve but not a polygon + geometry = QgsGeometry.fromWkt('LineString(0 0, 0 1, 1 1, 1 0, 0 0)') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.NoOrientation) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, False) + + # Polygon Empty + geometry = QgsGeometry.fromWkt('Polygon EMPTY') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.NoOrientation) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, False) + + # Polygon Clockwise + geometry = QgsGeometry.fromWkt('Polygon((0 0, 0 1, 1 1, 1 0, 0 0))') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.Clockwise) + self.assertEqual(res_isClockwise, True) + self.assertEqual(res_isCounterClockwise, False) + + # Polygon CounterClockwise + geometry = QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 1, 0 0))') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.CounterClockwise) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, True) + + # MultiPolygon Empty + geometry = QgsGeometry.fromWkt('MultiPolygon EMPTY') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.NoOrientation) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, False) + + # MultiPolygon Clockwise + geometry = QgsGeometry.fromWkt('MultiPolygon( ((0 0, 0 1, 1 1, 1 0, 0 0)) )') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.Clockwise) + self.assertEqual(res_isClockwise, True) + self.assertEqual(res_isCounterClockwise, False) + + # MultiPolygon Clockwise with a CounterClockwise part + geometry = QgsGeometry.fromWkt('MultiPolygon( ((0 0, 0 1, 1 1, 1 0, 0 0)), ((4 4, 5 4, 5 5, 4 5, 4 4)) )') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.Clockwise) + self.assertEqual(res_isClockwise, True) + self.assertEqual(res_isCounterClockwise, False) + + # MultiPolygon CounterClockwise + geometry = QgsGeometry.fromWkt('MultiPolygon( ((0 0, 1 0, 1 1, 0 1, 0 0)) )') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.CounterClockwise) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, True) + + # MultiPolygon CounterClockwise with a Clockwise part + geometry = QgsGeometry.fromWkt('MultiPolygon( ((0 0, 1 0, 1 1, 0 1, 0 0)), ((4 4, 4 5, 5 5, 5 4, 4 4)) ) ') + res_orientation = geometry.polygonOrientation() + res_isClockwise = geometry.isPolygonClockwise() + res_isCounterClockwise = geometry.isPolygonCounterClockwise() + + self.assertEqual(res_orientation, Qgis.AngularDirection.CounterClockwise) + self.assertEqual(res_isClockwise, False) + self.assertEqual(res_isCounterClockwise, True) + if __name__ == '__main__': unittest.main() From d7d3cdcbdcd3bc3e0dff53919e677d4eca98f15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Fri, 24 Nov 2023 11:00:45 +0000 Subject: [PATCH 88/97] Fix enumeration value 'NoOrientation' not handled in switch --- .../providers/arcgis/qgsarcgisrestutils.cpp | 34 ++++++++++++------- src/core/qgscolorrampimpl.cpp | 32 ++++++++++------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/core/providers/arcgis/qgsarcgisrestutils.cpp b/src/core/providers/arcgis/qgsarcgisrestutils.cpp index 2ffaed7296e..6acf106b78b 100644 --- a/src/core/providers/arcgis/qgsarcgisrestutils.cpp +++ b/src/core/providers/arcgis/qgsarcgisrestutils.cpp @@ -1,17 +1,17 @@ /*************************************************************************** - qgsarcgisrestutils.cpp - ---------------------- - begin : Nov 25, 2015 - copyright : (C) 2015 by Sandro Mani - email : manisandro@gmail.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. * - * * - ***************************************************************************/ + qgsarcgisrestutils.cpp + ---------------------- + begin : Nov 25, 2015 + copyright : (C) 2015 by Sandro Mani + email : manisandro@gmail.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 "qgsarcgisrestutils.h" #include "qgsfields.h" @@ -1412,6 +1412,8 @@ QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon ) rings.push_back( lineStringToJsonPath( reversed.get() ) ); break; } + case Qgis::AngularDirection::NoOrientation: + break; } } @@ -1431,6 +1433,8 @@ QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon ) rings.push_back( lineStringToJsonPath( reversed.get() ) ); break; } + case Qgis::AngularDirection::NoOrientation: + break; } } return rings; @@ -1457,6 +1461,8 @@ QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon rings.push_back( curveToJsonCurve( reversed.get(), true ) ); break; } + case Qgis::AngularDirection::NoOrientation: + break; } } @@ -1476,6 +1482,8 @@ QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon rings.push_back( curveToJsonCurve( reversed.get(), true ) ); break; } + case Qgis::AngularDirection::NoOrientation: + break; } } return rings; diff --git a/src/core/qgscolorrampimpl.cpp b/src/core/qgscolorrampimpl.cpp index 31ec562956e..afa6b4ac305 100644 --- a/src/core/qgscolorrampimpl.cpp +++ b/src/core/qgscolorrampimpl.cpp @@ -1,17 +1,17 @@ /*************************************************************************** - qgscolorrampimpl.cpp - --------------------- - begin : November 2009 - copyright : (C) 2009 by Martin Dobias - email : wonder dot sk at gmail 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. * - * * - ***************************************************************************/ + qgscolorrampimpl.cpp + --------------------- + begin : November 2009 + copyright : (C) 2009 by Martin Dobias + email : wonder dot sk at gmail 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 "qgscolorrampimpl.h" #include "qgscolorbrewerpalette.h" @@ -94,6 +94,8 @@ static QColor _interpolateHsv( const QColor &c1, const QColor &c2, const double hue -= 1; break; } + case Qgis::AngularDirection::NoOrientation: + break; } } @@ -151,6 +153,8 @@ static QColor _interpolateHsl( const QColor &c1, const QColor &c2, const double hue -= 1; break; } + case Qgis::AngularDirection::NoOrientation: + break; } } @@ -473,6 +477,8 @@ QVariantMap QgsGradientColorRamp::properties() const case Qgis::AngularDirection::CounterClockwise: map[QStringLiteral( "direction" ) ] = QStringLiteral( "ccw" ); break; + case Qgis::AngularDirection::NoOrientation: + break; } map[QStringLiteral( "rampType" )] = type(); From 13bc09e706af42ce0e6b383150aa9aa00bd873e3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 23 Nov 2023 09:34:16 +1000 Subject: [PATCH 89/97] Remove redundant "import qgis" lines from python files These were only needed way back in the early days of qgis 3.0 transition, now they have no effect --- python/plugins/db_manager/db_plugins/postgis/connector_test.py | 1 - python/plugins/db_manager/db_plugins/postgis/plugin_test.py | 1 - python/plugins/grassprovider/tests/AlgorithmsTestBase.py | 1 - python/plugins/otbprovider/tests/AlgorithmsTestBase.py | 1 - python/plugins/processing/tests/AlgorithmsTestBase.py | 1 - python/pyplugin_installer/installer.py | 1 - python/pyplugin_installer/qgsplugininstallerinstallingdialog.py | 1 - tests/src/python/qgis_interface.py | 1 - tests/src/python/test_console.py | 1 - tests/src/python/test_core_additions.py | 1 - tests/src/python/test_db_manager_gpkg.py | 1 - tests/src/python/test_db_manager_spatialite.py | 1 - tests/src/python/test_layer_dependencies.py | 1 - tests/src/python/test_processing_alg_decorator.py | 1 - tests/src/python/test_project_storage_base.py | 1 - tests/src/python/test_project_storage_oracle.py | 1 - tests/src/python/test_project_storage_postgres.py | 1 - tests/src/python/test_provider_mssql.py | 1 - tests/src/python/test_provider_ogr_gpkg.py | 1 - tests/src/python/test_provider_ogr_sqlite.py | 1 - tests/src/python/test_provider_oracle.py | 1 - tests/src/python/test_provider_postgres.py | 1 - tests/src/python/test_provider_postgres_latency.py | 1 - tests/src/python/test_provider_postgresraster.py | 1 - tests/src/python/test_provider_spatialite.py | 1 - tests/src/python/test_provider_virtual.py | 1 - tests/src/python/test_python_repr.py | 1 - tests/src/python/test_qgsaction.py | 1 - tests/src/python/test_qgsactionmanager.py | 1 - tests/src/python/test_qgsactionwidgetwrapper.py | 1 - tests/src/python/test_qgsaggregatecalculator.py | 1 - tests/src/python/test_qgsalignmentcombobox.py | 1 - tests/src/python/test_qgsanimatedmarkersymbollayer.py | 1 - tests/src/python/test_qgsannotation.py | 1 - tests/src/python/test_qgsannotationitemeditoperation.py | 1 - tests/src/python/test_qgsannotationitemnode.py | 1 - tests/src/python/test_qgsannotationlayer.py | 1 - tests/src/python/test_qgsannotationlineitem.py | 1 - tests/src/python/test_qgsannotationlinetextitem.py | 1 - tests/src/python/test_qgsannotationmarkeritem.py | 1 - tests/src/python/test_qgsannotationpointtextitem.py | 1 - tests/src/python/test_qgsannotationpolygonitem.py | 1 - tests/src/python/test_qgsapplication.py | 1 - tests/src/python/test_qgsarcgisrestutils.py | 1 - tests/src/python/test_qgsarrowsymbollayer.py | 1 - tests/src/python/test_qgsattributeeditoraction.py | 1 - tests/src/python/test_qgsattributeformeditorwidget.py | 1 - tests/src/python/test_qgsattributetableconfig.py | 1 - tests/src/python/test_qgsauxiliarystorage.py | 1 - tests/src/python/test_qgsbabelgpsformat.py | 1 - tests/src/python/test_qgsbearingutils.py | 1 - tests/src/python/test_qgsbinarywidget.py | 1 - tests/src/python/test_qgsblendmodes.py | 1 - tests/src/python/test_qgsblockingnetworkrequest.py | 1 - tests/src/python/test_qgsblockingprocess.py | 1 - tests/src/python/test_qgsbookmarkmanager.py | 1 - tests/src/python/test_qgsbookmarkmodel.py | 1 - tests/src/python/test_qgsbox3d.py | 1 - tests/src/python/test_qgscategorizedsymbolrenderer.py | 1 - tests/src/python/test_qgscesium3dtileslayer.py | 1 - tests/src/python/test_qgscesiumutils.py | 1 - tests/src/python/test_qgscheckablecombobox.py | 1 - tests/src/python/test_qgsclassificationmethod.py | 1 - tests/src/python/test_qgscodeeditor.py | 1 - tests/src/python/test_qgscodeeditorcolorscheme.py | 1 - tests/src/python/test_qgscodeeditorpython.py | 1 - tests/src/python/test_qgscolorbutton.py | 1 - tests/src/python/test_qgscolorramp.py | 1 - tests/src/python/test_qgscolorramplegendnode.py | 1 - tests/src/python/test_qgscolorscheme.py | 1 - tests/src/python/test_qgscolorschemeregistry.py | 1 - tests/src/python/test_qgscolorutils.py | 1 - tests/src/python/test_qgscombinedstylemodel.py | 1 - tests/src/python/test_qgsconnectionregistry.py | 1 - tests/src/python/test_qgscoordinateoperationwidget.py | 1 - tests/src/python/test_qgscoordinatereferencesystem.py | 1 - tests/src/python/test_qgscoordinatereferencesystemmodel.py | 1 - tests/src/python/test_qgscoordinatereferencesystemutils.py | 1 - tests/src/python/test_qgscoordinatetransform.py | 1 - tests/src/python/test_qgscoordinatetransformcontext.py | 1 - tests/src/python/test_qgscore.py | 1 - tests/src/python/test_qgscrsdefinitionwidget.py | 1 - tests/src/python/test_qgscrsselectionwidget.py | 1 - tests/src/python/test_qgsdatabaseschemacombobox.py | 1 - tests/src/python/test_qgsdatabasetablecombobox.py | 1 - tests/src/python/test_qgsdataitemguiproviderregistry.py | 1 - tests/src/python/test_qgsdataitemproviderregistry.py | 1 - tests/src/python/test_qgsdatetimeedit.py | 1 - tests/src/python/test_qgsdatetimestatisticalsummary.py | 1 - tests/src/python/test_qgsdefaultvalue.py | 1 - tests/src/python/test_qgsdelimitedtextprovider.py | 1 - tests/src/python/test_qgsdistancearea.py | 1 - tests/src/python/test_qgseditformconfig.py | 1 - tests/src/python/test_qgseditwidgets.py | 1 - tests/src/python/test_qgselevationprofilecanvas.py | 1 - tests/src/python/test_qgsellipsoidutils.py | 1 - tests/src/python/test_qgsembeddedsymbolrenderer.py | 1 - tests/src/python/test_qgsencodingselectiondialog.py | 1 - tests/src/python/test_qgsexiftools.py | 1 - tests/src/python/test_qgsexpression.py | 1 - tests/src/python/test_qgsexpressionbuilderwidget.py | 1 - tests/src/python/test_qgsexpressionlineedit.py | 1 - tests/src/python/test_qgsextentgroupbox.py | 1 - tests/src/python/test_qgsextentwidget.py | 1 - tests/src/python/test_qgsfeature.py | 1 - tests/src/python/test_qgsfeatureiterator.py | 1 - tests/src/python/test_qgsfeaturepicker.py | 1 - tests/src/python/test_qgsfeaturerequest.py | 1 - tests/src/python/test_qgsfeaturesink.py | 1 - tests/src/python/test_qgsfeaturesource.py | 1 - tests/src/python/test_qgsfeedback.py | 1 - tests/src/python/test_qgsfield.py | 1 - tests/src/python/test_qgsfieldcombobox.py | 1 - tests/src/python/test_qgsfieldformatters.py | 1 - tests/src/python/test_qgsfieldmodel.py | 1 - tests/src/python/test_qgsfields.py | 1 - tests/src/python/test_qgsfieldvalidator.py | 1 - tests/src/python/test_qgsfileutils.py | 1 - tests/src/python/test_qgsfilledlinesymbollayer.py | 1 - tests/src/python/test_qgsfillsymbollayers.py | 1 - tests/src/python/test_qgsfilterlineedit.py | 1 - tests/src/python/test_qgsfloatingwidget.py | 1 - tests/src/python/test_qgsfontbutton.py | 1 - tests/src/python/test_qgsfontmanager.py | 1 - tests/src/python/test_qgsgeocoderalgorithm.py | 1 - tests/src/python/test_qgsgeocoderlocatorfilter.py | 1 - tests/src/python/test_qgsgeometry_avoid_intersections.py | 1 - tests/src/python/test_qgsgeometrygeneratorsymbollayer.py | 1 - tests/src/python/test_qgsgeometrywidget.py | 1 - tests/src/python/test_qgsgooglemapsgeocoder.py | 1 - tests/src/python/test_qgsgpslogger.py | 1 - tests/src/python/test_qgsgraduatedsymbolrenderer.py | 1 - tests/src/python/test_qgsgraph.py | 1 - tests/src/python/test_qgsgrouplayer.py | 1 - tests/src/python/test_qgshashlinesymbollayer.py | 1 - tests/src/python/test_qgshighlight.py | 1 - tests/src/python/test_qgshistoryproviderregistry.py | 1 - tests/src/python/test_qgsimagecache.py | 1 - tests/src/python/test_qgsimagesourcelineedit.py | 1 - tests/src/python/test_qgsinputcontroller.py | 1 - tests/src/python/test_qgsinterpolatedlinesymbollayers.py | 1 - tests/src/python/test_qgsinterval.py | 1 - tests/src/python/test_qgsissue7244.py | 1 - tests/src/python/test_qgsjsonutils.py | 1 - tests/src/python/test_qgslabellinesettings.py | 1 - tests/src/python/test_qgslabelobstaclesettings.py | 1 - tests/src/python/test_qgslabelsettingswidget.py | 1 - tests/src/python/test_qgslabelthinningsettings.py | 1 - tests/src/python/test_qgslayerdefinition.py | 1 - tests/src/python/test_qgslayermetadata.py | 1 - tests/src/python/test_qgslayertree.py | 1 - tests/src/python/test_qgslayertreemapcanvasbridge.py | 1 - tests/src/python/test_qgslayertreeview.py | 1 - tests/src/python/test_qgslayout.py | 1 - tests/src/python/test_qgslayoutaligner.py | 1 - tests/src/python/test_qgslayoutatlas.py | 1 - tests/src/python/test_qgslayoutatlasclippingsettings.py | 1 - tests/src/python/test_qgslayoutcombobox.py | 1 - tests/src/python/test_qgslayoutexporter.py | 1 - tests/src/python/test_qgslayoutframe.py | 1 - tests/src/python/test_qgslayoutgridsettings.py | 1 - tests/src/python/test_qgslayoutguides.py | 1 - tests/src/python/test_qgslayouthtml.py | 1 - tests/src/python/test_qgslayoutitem.py | 1 - tests/src/python/test_qgslayoutitemcombobox.py | 1 - tests/src/python/test_qgslayoutitempropertiesdialog.py | 1 - tests/src/python/test_qgslayoutlabel.py | 1 - tests/src/python/test_qgslayoutmanager.py | 1 - tests/src/python/test_qgslayoutmanagermodel.py | 1 - tests/src/python/test_qgslayoutmap.py | 1 - tests/src/python/test_qgslayoutmapgrid.py | 1 - tests/src/python/test_qgslayoutmapitemclippingsettings.py | 1 - tests/src/python/test_qgslayoutmapoverview.py | 1 - tests/src/python/test_qgslayoutmarker.py | 1 - tests/src/python/test_qgslayoutnortharrowhandler.py | 1 - tests/src/python/test_qgslayoutpage.py | 1 - tests/src/python/test_qgslayoutpagecollection.py | 1 - tests/src/python/test_qgslayoutpicture.py | 1 - tests/src/python/test_qgslayoutpolygon.py | 1 - tests/src/python/test_qgslayoutpolyline.py | 1 - tests/src/python/test_qgslayoutscalebar.py | 1 - tests/src/python/test_qgslayoutshape.py | 1 - tests/src/python/test_qgslayoutsnapper.py | 1 - tests/src/python/test_qgslayoutunitscombobox.py | 1 - tests/src/python/test_qgslayoutview.py | 1 - tests/src/python/test_qgslegendpatchshape.py | 1 - tests/src/python/test_qgslegendpatchshapebutton.py | 1 - tests/src/python/test_qgslegendpatchshapewidget.py | 1 - tests/src/python/test_qgslineburstsymbollayer.py | 1 - tests/src/python/test_qgslinesegment.py | 1 - tests/src/python/test_qgslinesymbollayers.py | 1 - tests/src/python/test_qgslocaldefaultsettings.py | 1 - tests/src/python/test_qgslocalizeddatapathregistry.py | 1 - tests/src/python/test_qgslocator.py | 1 - tests/src/python/test_qgslogger.py | 1 - tests/src/python/test_qgsmapboxglconverter.py | 1 - tests/src/python/test_qgsmapcanvas.py | 1 - tests/src/python/test_qgsmapcanvasannotationitem.py | 1 - tests/src/python/test_qgsmapclippingregion.py | 1 - tests/src/python/test_qgsmapclippingutils.py | 1 - tests/src/python/test_qgsmaplayer.py | 1 - tests/src/python/test_qgsmaplayeraction.py | 1 - tests/src/python/test_qgsmaplayercombobox.py | 1 - tests/src/python/test_qgsmaplayerfactory.py | 1 - tests/src/python/test_qgsmaplayermodel.py | 1 - tests/src/python/test_qgsmaplayerproxymodel.py | 1 - tests/src/python/test_qgsmaplayerutils.py | 1 - tests/src/python/test_qgsmaprenderer.py | 1 - tests/src/python/test_qgsmaprenderercache.py | 1 - tests/src/python/test_qgsmapthemecollection.py | 1 - tests/src/python/test_qgsmapunitscale.py | 1 - tests/src/python/test_qgsmargins.py | 2 -- tests/src/python/test_qgsmarkerlinesymbollayer.py | 1 - tests/src/python/test_qgsmatrix4x4.py | 1 - tests/src/python/test_qgsmediawidget.py | 1 - tests/src/python/test_qgsmergedfeaturerenderer.py | 1 - tests/src/python/test_qgsmeshlayerelevationproperties.py | 1 - tests/src/python/test_qgsmeshlayerprofilegenerator.py | 1 - tests/src/python/test_qgsmessagelog.py | 1 - tests/src/python/test_qgsmetadatabase.py | 1 - tests/src/python/test_qgsmetadatawidget.py | 1 - tests/src/python/test_qgsmultiedittoolbutton.py | 1 - tests/src/python/test_qgsnetworkaccessmanager.py | 1 - tests/src/python/test_qgsnetworkcontentfetcher.py | 1 - tests/src/python/test_qgsnetworkcontentfetcherregistry.py | 1 - tests/src/python/test_qgsnetworkcontentfetchertask.py | 1 - tests/src/python/test_qgsnetworkreply.py | 1 - tests/src/python/test_qgsnoapplication.py | 1 - tests/src/python/test_qgsnominatimgeocoder.py | 1 - tests/src/python/test_qgsnullsymbolrenderer.py | 1 - tests/src/python/test_qgsnumericformat.py | 1 - tests/src/python/test_qgsnumericformatgui.py | 1 - tests/src/python/test_qgsobjectcustomproperties.py | 1 - tests/src/python/test_qgsogcutils.py | 1 - tests/src/python/test_qgsopacitywidget.py | 1 - tests/src/python/test_qgsoptional.py | 1 - tests/src/python/test_qgsorientedbox3d.py | 1 - tests/src/python/test_qgsowsconnection.py | 1 - tests/src/python/test_qgspallabeling_base.py | 1 - tests/src/python/test_qgspallabeling_canvas.py | 1 - tests/src/python/test_qgspallabeling_layout.py | 1 - tests/src/python/test_qgspallabeling_placement.py | 1 - tests/src/python/test_qgspallabeling_server.py | 1 - tests/src/python/test_qgspallabeling_tests.py | 1 - tests/src/python/test_qgspanelwidget.py | 1 - tests/src/python/test_qgspanelwidgetstack.py | 1 - tests/src/python/test_qgspathresolver.py | 1 - tests/src/python/test_qgsplot.py | 1 - tests/src/python/test_qgspoint.py | 1 - tests/src/python/test_qgspointcloudattributebyramprenderer.py | 1 - tests/src/python/test_qgspointcloudattributecombobox.py | 1 - tests/src/python/test_qgspointcloudattributemodel.py | 1 - tests/src/python/test_qgspointcloudclassifiedrenderer.py | 1 - tests/src/python/test_qgspointcloudelevationproperties.py | 1 - tests/src/python/test_qgspointcloudextentrenderer.py | 1 - tests/src/python/test_qgspointcloudlayerprofilegenerator.py | 1 - tests/src/python/test_qgspointcloudprovider.py | 1 - tests/src/python/test_qgspointcloudrgbrenderer.py | 1 - tests/src/python/test_qgspointdisplacementrenderer.py | 1 - tests/src/python/test_qgspolymorphicrelation.py | 1 - tests/src/python/test_qgspostgresdomain.py | 1 - tests/src/python/test_qgspostgrestransaction.py | 1 - tests/src/python/test_qgsprocessingbatch.py | 1 - tests/src/python/test_qgsprofileexporter.py | 1 - tests/src/python/test_qgsprofilepoint.py | 1 - tests/src/python/test_qgsprofilerequest.py | 1 - tests/src/python/test_qgsproject.py | 1 - tests/src/python/test_qgsprojectbadlayers.py | 1 - tests/src/python/test_qgsprojectdisplaysettings.py | 1 - tests/src/python/test_qgsprojectelevationproperties.py | 1 - tests/src/python/test_qgsprojectgpssettings.py | 1 - tests/src/python/test_qgsprojectionselectionwidgets.py | 1 - tests/src/python/test_qgsprojectmetadata.py | 1 - tests/src/python/test_qgsprojectrelationmanager.py | 1 - tests/src/python/test_qgsprojectstylesettings.py | 1 - tests/src/python/test_qgsprojecttimesettings.py | 1 - tests/src/python/test_qgsprojectutils.py | 1 - tests/src/python/test_qgsprojectviewsettings.py | 1 - tests/src/python/test_qgspropertyoverridebutton.py | 1 - tests/src/python/test_qgsproviderconnectioncombobox.py | 1 - tests/src/python/test_qgsproviderguiregistry.py | 1 - tests/src/python/test_qgsproviderregistry.py | 1 - tests/src/python/test_qgsprovidersublayerdetails.py | 1 - tests/src/python/test_qgsprovidersublayermodel.py | 1 - tests/src/python/test_qgsrandommarkersymbollayer.py | 1 - tests/src/python/test_qgsrange.py | 1 - tests/src/python/test_qgsrangeslider.py | 1 - tests/src/python/test_qgsrangewidgets.py | 1 - tests/src/python/test_qgsrasterattributetable.py | 1 - tests/src/python/test_qgsrasterattributetablemodel.py | 1 - tests/src/python/test_qgsrasterattributetablewidget.py | 1 - tests/src/python/test_qgsrasterbandcombobox.py | 1 - tests/src/python/test_qgsrastercolorrampshader.py | 1 - tests/src/python/test_qgsrasterfilewriter.py | 1 - tests/src/python/test_qgsrasterfilewritertask.py | 1 - tests/src/python/test_qgsrasterlayer.py | 1 - tests/src/python/test_qgsrasterlayerelevationproperties.py | 1 - tests/src/python/test_qgsrasterlayerprofilegenerator.py | 1 - tests/src/python/test_qgsrasterlayerproperties.py | 1 - tests/src/python/test_qgsrasterlayerrenderer.py | 1 - tests/src/python/test_qgsrasterlinesymbollayer.py | 1 - tests/src/python/test_qgsrasterpipe.py | 1 - tests/src/python/test_qgsrasterrange.py | 1 - tests/src/python/test_qgsrasterrendererutils.py | 1 - tests/src/python/test_qgsrasterrerderer_createsld.py | 1 - tests/src/python/test_qgsrasterresampler.py | 1 - tests/src/python/test_qgsrastertransparencywidget.py | 1 - tests/src/python/test_qgsratiolockbutton.py | 1 - tests/src/python/test_qgsreadwritecontext.py | 1 - tests/src/python/test_qgsrectangle.py | 1 - tests/src/python/test_qgsreferencedgeometry.py | 1 - tests/src/python/test_qgsrelation.py | 1 - tests/src/python/test_qgsrelationeditorwidgetregistry.py | 1 - tests/src/python/test_qgsrelationeditwidget.py | 1 - tests/src/python/test_qgsrelationmanager.py | 1 - tests/src/python/test_qgsrelationpostgres.py | 1 - tests/src/python/test_qgsrendercontext.py | 1 - tests/src/python/test_qgsrendereditemresults.py | 1 - tests/src/python/test_qgsrenderer.py | 1 - tests/src/python/test_qgsreport.py | 1 - tests/src/python/test_qgsrubberband.py | 1 - tests/src/python/test_qgsscalebarrendererregistry.py | 1 - tests/src/python/test_qgsscalecalculator.py | 1 - tests/src/python/test_qgsscalewidget.py | 1 - tests/src/python/test_qgsscreenproperties.py | 1 - tests/src/python/test_qgssearchwidgettoolbutton.py | 1 - tests/src/python/test_qgssearchwidgetwrapper.py | 1 - tests/src/python/test_qgsselectioncontext.py | 1 - tests/src/python/test_qgssensormanager.py | 1 - tests/src/python/test_qgssensormodel.py | 1 - tests/src/python/test_qgssensorregistry.py | 1 - tests/src/python/test_qgsserver_accesscontrol.py | 1 - .../python/test_qgsserver_accesscontrol_wms_getlegendgraphic.py | 1 - tests/src/python/test_qgsserver_cachemanager.py | 1 - tests/src/python/test_qgsserver_configcache.py | 1 - tests/src/python/test_qgsserver_wms_dimension.py | 1 - tests/src/python/test_qgsshortcutsmanager.py | 1 - tests/src/python/test_qgssimplefillsymbollayer.py | 1 - tests/src/python/test_qgssimplelinesymbollayer.py | 1 - tests/src/python/test_qgssingleitemmodel.py | 1 - tests/src/python/test_qgssinglesymbolrenderer.py | 1 - tests/src/python/test_qgssourcewidgetproviderregistry.py | 1 - tests/src/python/test_qgsspatialindex.py | 1 - tests/src/python/test_qgssphere.py | 1 - tests/src/python/test_qgsstringstatisticalsummary.py | 1 - tests/src/python/test_qgsstringutils.py | 1 - tests/src/python/test_qgsstylemodel.py | 1 - tests/src/python/test_qgssubsetstringeditorproviderregistry.py | 1 - tests/src/python/test_qgssvgcache.py | 1 - tests/src/python/test_qgssvgsourcelineedit.py | 1 - tests/src/python/test_qgssymbol.py | 1 - tests/src/python/test_qgssymbolbutton.py | 1 - tests/src/python/test_qgssymbolexpressionvariables.py | 1 - tests/src/python/test_qgssymbollayer.py | 1 - tests/src/python/test_qgssymbollayer_createsld.py | 1 - tests/src/python/test_qgssymbollayer_readsld.py | 1 - tests/src/python/test_qgssymbollayerregistry.py | 1 - tests/src/python/test_qgssymbollayerutils.py | 1 - tests/src/python/test_qgstablecell.py | 1 - tests/src/python/test_qgstabwidget.py | 1 - tests/src/python/test_qgstaskmanager.py | 1 - tests/src/python/test_qgstemporalutils.py | 1 - tests/src/python/test_qgsterrainprovider.py | 1 - tests/src/python/test_qgstextblock.py | 1 - tests/src/python/test_qgstextcharacterformat.py | 1 - tests/src/python/test_qgstextdocument.py | 1 - tests/src/python/test_qgstextformatwidget.py | 1 - tests/src/python/test_qgstextfragment.py | 1 - tests/src/python/test_qgstextrenderer.py | 1 - tests/src/python/test_qgstiledsceneboundingvolume.py | 1 - tests/src/python/test_qgstiledsceneelevationproperties.py | 1 - tests/src/python/test_qgstiledscenelayer.py | 1 - tests/src/python/test_qgstiledscenerender.py | 1 - tests/src/python/test_qgstiledscenerequest.py | 1 - tests/src/python/test_qgstiledscenetile.py | 1 - tests/src/python/test_qgstiles.py | 1 - tests/src/python/test_qgstreewidgetitem.py | 1 - tests/src/python/test_qgsunittypes.py | 1 - tests/src/python/test_qgsunsetattributevalue.py | 1 - tests/src/python/test_qgsvaliditychecks.py | 1 - tests/src/python/test_qgsvalidityresultswidget.py | 1 - tests/src/python/test_qgsvectorfieldmarkersymbollayer.py | 1 - tests/src/python/test_qgsvectorfilewriter.py | 1 - tests/src/python/test_qgsvectorfilewriter_postgres.py | 1 - tests/src/python/test_qgsvectorfilewritertask.py | 1 - tests/src/python/test_qgsvectorlayer.py | 1 - tests/src/python/test_qgsvectorlayercache.py | 1 - tests/src/python/test_qgsvectorlayereditbuffer.py | 1 - tests/src/python/test_qgsvectorlayereditbuffergroup.py | 1 - tests/src/python/test_qgsvectorlayereditutils.py | 1 - tests/src/python/test_qgsvectorlayerelevationproperties.py | 1 - tests/src/python/test_qgsvectorlayerfeaturecounter.py | 1 - tests/src/python/test_qgsvectorlayerprofilegenerator.py | 1 - tests/src/python/test_qgsvectorlayerrenderer.py | 1 - tests/src/python/test_qgsvectorlayershapefile.py | 1 - tests/src/python/test_qgsvectorlayertemporalproperties.py | 1 - tests/src/python/test_qgsvectorlayerutils.py | 1 - tests/src/python/test_qgsvectorlayerutils_postgres.py | 1 - tests/src/python/test_qgsvectortile.py | 1 - tests/src/python/test_qgsvectorwarper.py | 1 - tests/src/python/test_qgsvirtuallayerdefinition.py | 1 - tests/src/python/test_qgsvirtuallayertask.py | 1 - tests/src/python/test_qgsvtpk.py | 1 - tests/src/python/test_qgsxmlutils.py | 1 - tests/src/python/test_qgszonalstatistics.py | 1 - tests/src/python/test_selective_masking.py | 1 - tests/src/python/test_syntactic_sugar.py | 1 - tests/src/python/test_testrunner.py | 1 - tests/src/python/test_versioncompare.py | 1 - tests/src/python/utilities.py | 1 - 410 files changed, 411 deletions(-) diff --git a/python/plugins/db_manager/db_plugins/postgis/connector_test.py b/python/plugins/db_manager/db_plugins/postgis/connector_test.py index 71d9f8c986f..b8be72f5a3f 100644 --- a/python/plugins/db_manager/db_plugins/postgis/connector_test.py +++ b/python/plugins/db_manager/db_plugins/postgis/connector_test.py @@ -20,7 +20,6 @@ __date__ = 'May 2017' __copyright__ = '(C) 2017, Sandro Santilli' import os -import qgis import unittest from qgis.testing import start_app, QgisTestCase from qgis.core import QgsDataSourceUri diff --git a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py index 754248fe878..9f57ff04708 100644 --- a/python/plugins/db_manager/db_plugins/postgis/plugin_test.py +++ b/python/plugins/db_manager/db_plugins/postgis/plugin_test.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2017, Sandro Santilli' import os import re -import qgis import unittest from qgis.testing import start_app, QgisTestCase from qgis.core import QgsDataSourceUri diff --git a/python/plugins/grassprovider/tests/AlgorithmsTestBase.py b/python/plugins/grassprovider/tests/AlgorithmsTestBase.py index cf1a48f915e..06ced6c469f 100644 --- a/python/plugins/grassprovider/tests/AlgorithmsTestBase.py +++ b/python/plugins/grassprovider/tests/AlgorithmsTestBase.py @@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn' __date__ = 'January 2016' __copyright__ = '(C) 2016, Matthias Kuhn' -import qgis # NOQA switch sip api import os import yaml diff --git a/python/plugins/otbprovider/tests/AlgorithmsTestBase.py b/python/plugins/otbprovider/tests/AlgorithmsTestBase.py index ece593d663b..d069f2900ef 100644 --- a/python/plugins/otbprovider/tests/AlgorithmsTestBase.py +++ b/python/plugins/otbprovider/tests/AlgorithmsTestBase.py @@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn' __date__ = 'January 2016' __copyright__ = '(C) 2016, Matthias Kuhn' -import qgis # NOQA switch sip api import os import yaml diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index d192abe6889..e2281fe0714 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn' __date__ = 'January 2016' __copyright__ = '(C) 2016, Matthias Kuhn' -import qgis # NOQA switch sip api import os import yaml diff --git a/python/pyplugin_installer/installer.py b/python/pyplugin_installer/installer.py index af546b5eaae..fe41fc2341b 100644 --- a/python/pyplugin_installer/installer.py +++ b/python/pyplugin_installer/installer.py @@ -42,7 +42,6 @@ from qgis.PyQt.QtWidgets import ( ) from qgis.PyQt.QtNetwork import QNetworkRequest -import qgis from qgis.core import Qgis, QgsApplication, QgsMessageLog, QgsNetworkAccessManager, QgsSettings, QgsSettingsTree, QgsNetworkRequestParameters from qgis.gui import QgsMessageBar, QgsPasswordLineEdit, QgsHelp from qgis.utils import (iface, startPlugin, unloadPlugin, loadPlugin, OverrideCursor, diff --git a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py index 1b4dc7552b4..c15da88ec51 100644 --- a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py +++ b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py @@ -29,7 +29,6 @@ from qgis.PyQt.QtCore import QDir, QUrl, QFile, QCoreApplication from qgis.PyQt.QtWidgets import QDialog from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply -import qgis from qgis.core import QgsNetworkAccessManager, QgsApplication, QgsNetworkRequestParameters from .ui_qgsplugininstallerinstallingbase import Ui_QgsPluginInstallerInstallingDialogBase diff --git a/tests/src/python/qgis_interface.py b/tests/src/python/qgis_interface.py index d40e0e37411..7faf63c873c 100644 --- a/tests/src/python/qgis_interface.py +++ b/tests/src/python/qgis_interface.py @@ -23,7 +23,6 @@ __copyright__ = ('Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and ' 'Copyright (c) 2011 German Carrillo, ' 'geotux_tuxman@linuxmail.org') -import qgis # NOQA from qgis.PyQt.QtCore import QObject from qgis.core import QgsProject diff --git a/tests/src/python/test_console.py b/tests/src/python/test_console.py index 8f7b305600b..83bb64028a0 100644 --- a/tests/src/python/test_console.py +++ b/tests/src/python/test_console.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os -import qgis # NOQA from console import console from qgis.PyQt.QtCore import QCoreApplication from qgis.core import QgsSettings diff --git a/tests/src/python/test_core_additions.py b/tests/src/python/test_core_additions.py index dc3c78b9c83..108b517c7de 100644 --- a/tests/src/python/test_core_additions.py +++ b/tests/src/python/test_core_additions.py @@ -11,7 +11,6 @@ __author__ = 'Denis Rouzaud' __date__ = '15.5.2018' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, diff --git a/tests/src/python/test_db_manager_gpkg.py b/tests/src/python/test_db_manager_gpkg.py index 4fcf932f161..2c12326ccd4 100644 --- a/tests/src/python/test_db_manager_gpkg.py +++ b/tests/src/python/test_db_manager_gpkg.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from osgeo import gdal, ogr, osr from plugins.db_manager.db_plugins import createDbPlugin, supportedDbTypes from plugins.db_manager.db_plugins.plugin import TableField diff --git a/tests/src/python/test_db_manager_spatialite.py b/tests/src/python/test_db_manager_spatialite.py index b61aa4650fc..213e14273a8 100644 --- a/tests/src/python/test_db_manager_spatialite.py +++ b/tests/src/python/test_db_manager_spatialite.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from osgeo import ogr from plugins.db_manager.db_plugins import createDbPlugin, supportedDbTypes from plugins.db_manager.db_plugins.plugin import TableField diff --git a/tests/src/python/test_layer_dependencies.py b/tests/src/python/test_layer_dependencies.py index eaa58f1147b..3523848abc5 100644 --- a/tests/src/python/test_layer_dependencies.py +++ b/tests/src/python/test_layer_dependencies.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QPoint, QSize from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_processing_alg_decorator.py b/tests/src/python/test_processing_alg_decorator.py index 19265769c43..29cea78f94c 100644 --- a/tests/src/python/test_processing_alg_decorator.py +++ b/tests/src/python/test_processing_alg_decorator.py @@ -9,7 +9,6 @@ __author__ = 'Nathan Woodrow' __date__ = '10.12.2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.processing import alg import unittest diff --git a/tests/src/python/test_project_storage_base.py b/tests/src/python/test_project_storage_base.py index f2774a7fe76..dfe7e0543a3 100644 --- a/tests/src/python/test_project_storage_base.py +++ b/tests/src/python/test_project_storage_base.py @@ -11,7 +11,6 @@ __author__ = 'Julien Cabieces' __date__ = '2022-04-19' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from PyQt5.QtCore import QDateTime from qgis.core import ( diff --git a/tests/src/python/test_project_storage_oracle.py b/tests/src/python/test_project_storage_oracle.py index a942259aafa..f3a4a104282 100644 --- a/tests/src/python/test_project_storage_oracle.py +++ b/tests/src/python/test_project_storage_oracle.py @@ -13,7 +13,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from PyQt5.QtCore import QUrl, QUrlQuery from qgis.PyQt.QtSql import QSqlDatabase, QSqlQuery from qgis.core import ( diff --git a/tests/src/python/test_project_storage_postgres.py b/tests/src/python/test_project_storage_postgres.py index 8ce8f599977..b4cf39a4db7 100644 --- a/tests/src/python/test_project_storage_postgres.py +++ b/tests/src/python/test_project_storage_postgres.py @@ -17,7 +17,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os import psycopg2 -import qgis # NOQA from PyQt5.QtCore import QUrl, QUrlQuery from qgis.core import ( QgsDataSourceUri, diff --git a/tests/src/python/test_provider_mssql.py b/tests/src/python/test_provider_mssql.py index b5774364bca..08802f4fc04 100644 --- a/tests/src/python/test_provider_mssql.py +++ b/tests/src/python/test_provider_mssql.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QDir, QTime, QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index a6effd61652..9cd291f80c7 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -20,7 +20,6 @@ import tempfile import time from sqlite3 import OperationalError -import qgis # NOQA from osgeo import gdal, ogr from qgis.PyQt.QtCore import ( QCoreApplication, diff --git a/tests/src/python/test_provider_ogr_sqlite.py b/tests/src/python/test_provider_ogr_sqlite.py index 603151e1262..784376fba8e 100644 --- a/tests/src/python/test_provider_ogr_sqlite.py +++ b/tests/src/python/test_provider_ogr_sqlite.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from osgeo import ogr from qgis.PyQt.QtCore import QByteArray, QDate, QDateTime, QTime, QVariant from qgis.core import ( diff --git a/tests/src/python/test_provider_oracle.py b/tests/src/python/test_provider_oracle.py index bd096604c5a..9cfbba5c0ad 100644 --- a/tests/src/python/test_provider_oracle.py +++ b/tests/src/python/test_provider_oracle.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime, QVariant from qgis.PyQt.QtSql import QSqlDatabase, QSqlQuery from qgis.core import ( diff --git a/tests/src/python/test_provider_postgres.py b/tests/src/python/test_provider_postgres.py index ddc0721417b..a0da28b8794 100644 --- a/tests/src/python/test_provider_postgres.py +++ b/tests/src/python/test_provider_postgres.py @@ -23,7 +23,6 @@ import time from datetime import datetime import psycopg2 -import qgis # NOQA from qgis.PyQt.QtCore import ( QByteArray, QDate, diff --git a/tests/src/python/test_provider_postgres_latency.py b/tests/src/python/test_provider_postgres_latency.py index de542eaad3d..ce138c82601 100644 --- a/tests/src/python/test_provider_postgres_latency.py +++ b/tests/src/python/test_provider_postgres_latency.py @@ -20,7 +20,6 @@ import os import time import psycopg2 -import qgis # NOQA from qgis.PyQt.QtCore import ( QDir, QTemporaryFile, diff --git a/tests/src/python/test_provider_postgresraster.py b/tests/src/python/test_provider_postgresraster.py index 70707521297..440fd19ae24 100644 --- a/tests/src/python/test_provider_postgresraster.py +++ b/tests/src/python/test_provider_postgresraster.py @@ -21,7 +21,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os import time -import qgis # NOQA from qgis.core import ( QgsDataSourceUri, QgsPointXY, diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index 156d3e357cd..d37bce157c9 100644 --- a/tests/src/python/test_provider_spatialite.py +++ b/tests/src/python/test_provider_spatialite.py @@ -16,7 +16,6 @@ import sys import tempfile from datetime import datetime -import qgis # NOQA from osgeo import ogr from qgis.PyQt.QtCore import QByteArray, QVariant from qgis.core import ( diff --git a/tests/src/python/test_provider_virtual.py b/tests/src/python/test_provider_virtual.py index da283568e8a..0405458d58c 100644 --- a/tests/src/python/test_provider_virtual.py +++ b/tests/src/python/test_provider_virtual.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir, QUrl, QVariant from qgis.core import ( Qgis, diff --git a/tests/src/python/test_python_repr.py b/tests/src/python/test_python_repr.py index 43cc0a46ebd..8b835bad168 100644 --- a/tests/src/python/test_python_repr.py +++ b/tests/src/python/test_python_repr.py @@ -11,7 +11,6 @@ __author__ = 'Denis Rouzaud' __date__ = '05.06.2018' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from PyQt5.QtCore import QVariant from qgis.core import ( diff --git a/tests/src/python/test_qgsaction.py b/tests/src/python/test_qgsaction.py index 316452ce4f8..57de79b2ac5 100644 --- a/tests/src/python/test_qgsaction.py +++ b/tests/src/python/test_qgsaction.py @@ -14,7 +14,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import os from functools import partial -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QTemporaryDir from qgis.core import ( QgsAction, diff --git a/tests/src/python/test_qgsactionmanager.py b/tests/src/python/test_qgsactionmanager.py index 91779ce3d17..d505319e217 100644 --- a/tests/src/python/test_qgsactionmanager.py +++ b/tests/src/python/test_qgsactionmanager.py @@ -15,7 +15,6 @@ import os import platform import time -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QDir, QTemporaryFile, QUuid from qgis.core import ( QgsAction, diff --git a/tests/src/python/test_qgsactionwidgetwrapper.py b/tests/src/python/test_qgsactionwidgetwrapper.py index 4219095e93b..9dae2af93b7 100644 --- a/tests/src/python/test_qgsactionwidgetwrapper.py +++ b/tests/src/python/test_qgsactionwidgetwrapper.py @@ -11,7 +11,6 @@ __author__ = 'Alessandro Pasotti' __date__ = '16/08/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QUuid from qgis.PyQt.QtWidgets import QPushButton, QWidget from qgis.core import QgsAction, QgsVectorLayer diff --git a/tests/src/python/test_qgsaggregatecalculator.py b/tests/src/python/test_qgsaggregatecalculator.py index 764a249aa49..adaa81e8265 100644 --- a/tests/src/python/test_qgsaggregatecalculator.py +++ b/tests/src/python/test_qgsaggregatecalculator.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsalignmentcombobox.py b/tests/src/python/test_qgsalignmentcombobox.py index 1ca075277bf..cb5ee44803e 100644 --- a/tests/src/python/test_qgsalignmentcombobox.py +++ b/tests/src/python/test_qgsalignmentcombobox.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '26/06/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtTest import QSignalSpy from qgis.gui import QgsAlignmentComboBox diff --git a/tests/src/python/test_qgsanimatedmarkersymbollayer.py b/tests/src/python/test_qgsanimatedmarkersymbollayer.py index 2e6a011c781..16cd19db9df 100644 --- a/tests/src/python/test_qgsanimatedmarkersymbollayer.py +++ b/tests/src/python/test_qgsanimatedmarkersymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2022, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.core import ( QgsAnimatedMarkerSymbolLayer, diff --git a/tests/src/python/test_qgsannotation.py b/tests/src/python/test_qgsannotation.py index 071d164c380..8833d131ba8 100644 --- a/tests/src/python/test_qgsannotation.py +++ b/tests/src/python/test_qgsannotation.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '24/1/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QPointF, QRectF, QSize, QSizeF from qgis.PyQt.QtGui import QColor, QImage, QPainter, QTextDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsannotationitemeditoperation.py b/tests/src/python/test_qgsannotationitemeditoperation.py index b63fcd35877..7351b7a91ec 100644 --- a/tests/src/python/test_qgsannotationitemeditoperation.py +++ b/tests/src/python/test_qgsannotationitemeditoperation.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '09/09/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsAnnotationItemEditOperationAddNode, QgsAnnotationItemEditOperationDeleteNode, diff --git a/tests/src/python/test_qgsannotationitemnode.py b/tests/src/python/test_qgsannotationitemnode.py index 86f164a666e..12de1ba236c 100644 --- a/tests/src/python/test_qgsannotationitemnode.py +++ b/tests/src/python/test_qgsannotationitemnode.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import Qgis, QgsAnnotationItemNode, QgsPointXY, QgsVertexId import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsannotationlayer.py b/tests/src/python/test_qgsannotationlayer.py index f1e1ee8f496..3cb7578e985 100644 --- a/tests/src/python/test_qgsannotationlayer.py +++ b/tests/src/python/test_qgsannotationlayer.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize, QTemporaryDir from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsannotationlineitem.py b/tests/src/python/test_qgsannotationlineitem.py index e9077fede5a..3e22ca21c1c 100644 --- a/tests/src/python/test_qgsannotationlineitem.py +++ b/tests/src/python/test_qgsannotationlineitem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsannotationlinetextitem.py b/tests/src/python/test_qgsannotationlinetextitem.py index 0b1fcddbe56..fc65c9fa419 100644 --- a/tests/src/python/test_qgsannotationlinetextitem.py +++ b/tests/src/python/test_qgsannotationlinetextitem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '10/08/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsannotationmarkeritem.py b/tests/src/python/test_qgsannotationmarkeritem.py index e94f9ef3e6a..e92745dd4ff 100644 --- a/tests/src/python/test_qgsannotationmarkeritem.py +++ b/tests/src/python/test_qgsannotationmarkeritem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsannotationpointtextitem.py b/tests/src/python/test_qgsannotationpointtextitem.py index 9e95ef0a925..8cbb84b31be 100644 --- a/tests/src/python/test_qgsannotationpointtextitem.py +++ b/tests/src/python/test_qgsannotationpointtextitem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '10/08/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsannotationpolygonitem.py b/tests/src/python/test_qgsannotationpolygonitem.py index ab38366cf58..61fb485c124 100644 --- a/tests/src/python/test_qgsannotationpolygonitem.py +++ b/tests/src/python/test_qgsannotationpolygonitem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsapplication.py b/tests/src/python/test_qgsapplication.py index 76566a1b1c7..2c4cf005341 100644 --- a/tests/src/python/test_qgsapplication.py +++ b/tests/src/python/test_qgsapplication.py @@ -9,7 +9,6 @@ __author__ = 'Tim Sutton (tim@linfiniti.com)' __date__ = '20/01/2011' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsarcgisrestutils.py b/tests/src/python/test_qgsarcgisrestutils.py index d1ff385c7b0..d5a3360e10c 100644 --- a/tests/src/python/test_qgsarcgisrestutils.py +++ b/tests/src/python/test_qgsarcgisrestutils.py @@ -11,7 +11,6 @@ __author__ = '(C) 2022 by Nyall Dawson' __date__ = '14/07/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, Qt, QTime, QTimeZone, QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsarrowsymbollayer.py b/tests/src/python/test_qgsarrowsymbollayer.py index 0a9720135e9..97f445435c0 100644 --- a/tests/src/python/test_qgsarrowsymbollayer.py +++ b/tests/src/python/test_qgsarrowsymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2016, Hugo Mercier' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsattributeeditoraction.py b/tests/src/python/test_qgsattributeeditoraction.py index 172dd0c0f52..66256a360a8 100644 --- a/tests/src/python/test_qgsattributeeditoraction.py +++ b/tests/src/python/test_qgsattributeeditoraction.py @@ -11,7 +11,6 @@ __author__ = 'Alessandro Pasotti' __date__ = '16/08/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QUuid from qgis.core import ( QgsAction, diff --git a/tests/src/python/test_qgsattributeformeditorwidget.py b/tests/src/python/test_qgsattributeformeditorwidget.py index 6bbe9335200..6ffae60db2d 100644 --- a/tests/src/python/test_qgsattributeformeditorwidget.py +++ b/tests/src/python/test_qgsattributeformeditorwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2016-05' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtWidgets import QDateTimeEdit, QWidget from qgis.core import QgsVectorLayer diff --git a/tests/src/python/test_qgsattributetableconfig.py b/tests/src/python/test_qgsattributetableconfig.py index 2d481f9cde1..68879534421 100644 --- a/tests/src/python/test_qgsattributetableconfig.py +++ b/tests/src/python/test_qgsattributetableconfig.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '07/06/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.core import QgsAttributeTableConfig, QgsVectorLayer import unittest diff --git a/tests/src/python/test_qgsauxiliarystorage.py b/tests/src/python/test_qgsauxiliarystorage.py index 076c080f846..d1dab1bd6c0 100644 --- a/tests/src/python/test_qgsauxiliarystorage.py +++ b/tests/src/python/test_qgsauxiliarystorage.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryFile, QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsbabelgpsformat.py b/tests/src/python/test_qgsbabelgpsformat.py index 57bf25806af..25c1832828a 100644 --- a/tests/src/python/test_qgsbabelgpsformat.py +++ b/tests/src/python/test_qgsbabelgpsformat.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2021-07' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsbearingutils.py b/tests/src/python/test_qgsbearingutils.py index d06e2546b27..bf59c07798e 100644 --- a/tests/src/python/test_qgsbearingutils.py +++ b/tests/src/python/test_qgsbearingutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/10/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.core import ( QgsBearingUtils, diff --git a/tests/src/python/test_qgsbinarywidget.py b/tests/src/python/test_qgsbinarywidget.py index 393a86d833d..afc087ce915 100644 --- a/tests/src/python/test_qgsbinarywidget.py +++ b/tests/src/python/test_qgsbinarywidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '11/11/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QByteArray from qgis.core import NULL, QgsFeature, QgsGeometry, QgsPointXY, QgsVectorLayer from qgis.gui import QgsGui diff --git a/tests/src/python/test_qgsblendmodes.py b/tests/src/python/test_qgsblendmodes.py index 8f37dbbe15b..173028005a6 100644 --- a/tests/src/python/test_qgsblendmodes.py +++ b/tests/src/python/test_qgsblendmodes.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2013, Nyall Dawson, Massimo Endrighi' import os -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QColor, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsblockingnetworkrequest.py b/tests/src/python/test_qgsblockingnetworkrequest.py index efee29f0562..b4c5e6f3a6e 100644 --- a/tests/src/python/test_qgsblockingnetworkrequest.py +++ b/tests/src/python/test_qgsblockingnetworkrequest.py @@ -10,7 +10,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/11/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsblockingprocess.py b/tests/src/python/test_qgsblockingprocess.py index 5be13b5b8ee..5270bf7d5d2 100644 --- a/tests/src/python/test_qgsblockingprocess.py +++ b/tests/src/python/test_qgsblockingprocess.py @@ -22,7 +22,6 @@ __copyright__ = '(C) 2021, Nyall Dawson' import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QProcess from qgis.core import QgsBlockingProcess, QgsFeedback import unittest diff --git a/tests/src/python/test_qgsbookmarkmanager.py b/tests/src/python/test_qgsbookmarkmanager.py index e2425d1c971..7753651758f 100644 --- a/tests/src/python/test_qgsbookmarkmanager.py +++ b/tests/src/python/test_qgsbookmarkmanager.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, QLocale, QTemporaryDir from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsbookmarkmodel.py b/tests/src/python/test_qgsbookmarkmodel.py index 1fb4d2282aa..3dcd3a58e41 100644 --- a/tests/src/python/test_qgsbookmarkmodel.py +++ b/tests/src/python/test_qgsbookmarkmodel.py @@ -9,7 +9,6 @@ __author__ = '(C) 2019 by Nyall Dawson' __date__ = '02/09/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QLocale, Qt from qgis.core import ( QgsBookmark, diff --git a/tests/src/python/test_qgsbox3d.py b/tests/src/python/test_qgsbox3d.py index 4fdbc2fe262..bc003b75971 100644 --- a/tests/src/python/test_qgsbox3d.py +++ b/tests/src/python/test_qgsbox3d.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import sys -import qgis # NOQA from qgis.core import QgsBox3d, QgsPoint, QgsRectangle, QgsVector3D from qgis.testing import unittest diff --git a/tests/src/python/test_qgscategorizedsymbolrenderer.py b/tests/src/python/test_qgscategorizedsymbolrenderer.py index faf1776275d..fa65347dae3 100644 --- a/tests/src/python/test_qgscategorizedsymbolrenderer.py +++ b/tests/src/python/test_qgscategorizedsymbolrenderer.py @@ -13,7 +13,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QLocale, QSize, Qt, QTemporaryDir, QVariant from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgscesium3dtileslayer.py b/tests/src/python/test_qgscesium3dtileslayer.py index 6e47858c762..51332b1c4eb 100644 --- a/tests/src/python/test_qgscesium3dtileslayer.py +++ b/tests/src/python/test_qgscesium3dtileslayer.py @@ -12,7 +12,6 @@ __copyright__ = "Copyright 2023, The QGIS Project" import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgscesiumutils.py b/tests/src/python/test_qgscesiumutils.py index a1395025c6c..21f7d45abfd 100644 --- a/tests/src/python/test_qgscesiumutils.py +++ b/tests/src/python/test_qgscesiumutils.py @@ -12,7 +12,6 @@ __date__ = '10/07/2023' __copyright__ = 'Copyright 2023, The QGIS Project' import math -import qgis # NOQA from qgis.core import ( QgsCesiumUtils ) diff --git a/tests/src/python/test_qgscheckablecombobox.py b/tests/src/python/test_qgscheckablecombobox.py index f72b2cd8fd2..90de3c37d88 100644 --- a/tests/src/python/test_qgscheckablecombobox.py +++ b/tests/src/python/test_qgscheckablecombobox.py @@ -9,7 +9,6 @@ __author__ = 'Alexander Bruy' __date__ = '22/03/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsclassificationmethod.py b/tests/src/python/test_qgsclassificationmethod.py index 754ce9128f3..115bc8fe10e 100644 --- a/tests/src/python/test_qgsclassificationmethod.py +++ b/tests/src/python/test_qgsclassificationmethod.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import random -import qgis # NOQA from qgis.PyQt.QtCore import QLocale from qgis.core import ( QgsClassificationFixedInterval, diff --git a/tests/src/python/test_qgscodeeditor.py b/tests/src/python/test_qgscodeeditor.py index 752555d80ea..07fc7fac47a 100644 --- a/tests/src/python/test_qgscodeeditor.py +++ b/tests/src/python/test_qgscodeeditor.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import sys -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtGui import QColor, QFont from qgis.core import QgsApplication, QgsSettings diff --git a/tests/src/python/test_qgscodeeditorcolorscheme.py b/tests/src/python/test_qgscodeeditorcolorscheme.py index c4a892d3d16..d38816005d7 100644 --- a/tests/src/python/test_qgscodeeditorcolorscheme.py +++ b/tests/src/python/test_qgscodeeditorcolorscheme.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '03/10/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtGui import QColor from qgis.core import QgsSettings diff --git a/tests/src/python/test_qgscodeeditorpython.py b/tests/src/python/test_qgscodeeditorpython.py index 949115eed8b..ecf67314900 100644 --- a/tests/src/python/test_qgscodeeditorpython.py +++ b/tests/src/python/test_qgscodeeditorpython.py @@ -10,7 +10,6 @@ __date__ = '31/03/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, Qt from qgis.PyQt.QtTest import QTest from qgis.core import QgsSettings diff --git a/tests/src/python/test_qgscolorbutton.py b/tests/src/python/test_qgscolorbutton.py index f3a3071ced8..ba8ffd4f3fa 100644 --- a/tests/src/python/test_qgscolorbutton.py +++ b/tests/src/python/test_qgscolorbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '25/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsApplication, QgsProjectColorScheme diff --git a/tests/src/python/test_qgscolorramp.py b/tests/src/python/test_qgscolorramp.py index 4b1c058c2ea..3d1dd817d13 100644 --- a/tests/src/python/test_qgscolorramp.py +++ b/tests/src/python/test_qgscolorramp.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2015-08' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor, QGradient from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgscolorramplegendnode.py b/tests/src/python/test_qgscolorramplegendnode.py index b90a16ec0e0..333f1cd70b5 100644 --- a/tests/src/python/test_qgscolorramplegendnode.py +++ b/tests/src/python/test_qgscolorramplegendnode.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2015-08' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize, QSizeF, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgscolorscheme.py b/tests/src/python/test_qgscolorscheme.py index 97bc45f0c47..d1d72ea3a9f 100644 --- a/tests/src/python/test_qgscolorscheme.py +++ b/tests/src/python/test_qgscolorscheme.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '25/07/2014' __copyright__ = 'Copyright 2014, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtGui import QColor from qgis.core import ( diff --git a/tests/src/python/test_qgscolorschemeregistry.py b/tests/src/python/test_qgscolorschemeregistry.py index 0635515d6a2..b0a4476671c 100644 --- a/tests/src/python/test_qgscolorschemeregistry.py +++ b/tests/src/python/test_qgscolorschemeregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '25/07/2014' __copyright__ = 'Copyright 2014, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgscolorutils.py b/tests/src/python/test_qgscolorutils.py index 64f28f2ff87..e761721c2a6 100644 --- a/tests/src/python/test_qgscolorutils.py +++ b/tests/src/python/test_qgscolorutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '06/07/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsColorUtils, QgsReadWriteContext, QgsSymbolLayerUtils diff --git a/tests/src/python/test_qgscombinedstylemodel.py b/tests/src/python/test_qgscombinedstylemodel.py index ab117c13abd..45a3bf54145 100644 --- a/tests/src/python/test_qgscombinedstylemodel.py +++ b/tests/src/python/test_qgscombinedstylemodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, Qt from qgis.core import QgsStyle, QgsStyleModel, QgsTextFormat import unittest diff --git a/tests/src/python/test_qgsconnectionregistry.py b/tests/src/python/test_qgsconnectionregistry.py index 619c0902909..ce4c0c57a14 100644 --- a/tests/src/python/test_qgsconnectionregistry.py +++ b/tests/src/python/test_qgsconnectionregistry.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgscoordinateoperationwidget.py b/tests/src/python/test_qgscoordinateoperationwidget.py index 738bb99e52e..a2f37cbb5a9 100644 --- a/tests/src/python/test_qgscoordinateoperationwidget.py +++ b/tests/src/python/test_qgscoordinateoperationwidget.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgscoordinatereferencesystem.py b/tests/src/python/test_qgscoordinatereferencesystem.py index 8a8ad011e72..9b952959735 100644 --- a/tests/src/python/test_qgscoordinatereferencesystem.py +++ b/tests/src/python/test_qgscoordinatereferencesystem.py @@ -11,7 +11,6 @@ __author__ = '(C) 2022 by Nyall Dawson' __date__ = '06/04/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.core import Qgis, QgsCoordinateReferenceSystem import unittest diff --git a/tests/src/python/test_qgscoordinatereferencesystemmodel.py b/tests/src/python/test_qgscoordinatereferencesystemmodel.py index cca3ea70d0c..36f7333de1c 100644 --- a/tests/src/python/test_qgscoordinatereferencesystemmodel.py +++ b/tests/src/python/test_qgscoordinatereferencesystemmodel.py @@ -9,7 +9,6 @@ __author__ = '(C) 2022 by Nyall Dawson' __date__ = '12/07/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import ( Qt, diff --git a/tests/src/python/test_qgscoordinatereferencesystemutils.py b/tests/src/python/test_qgscoordinatereferencesystemutils.py index dbf1d03d86d..a7062d2f63e 100644 --- a/tests/src/python/test_qgscoordinatereferencesystemutils.py +++ b/tests/src/python/test_qgscoordinatereferencesystemutils.py @@ -9,7 +9,6 @@ __author__ = '(C) 2022 by Nyall Dawson' __date__ = '06/04/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgscoordinatetransform.py b/tests/src/python/test_qgscoordinatetransform.py index 24514f3f6f4..7866bce07e8 100644 --- a/tests/src/python/test_qgscoordinatetransform.py +++ b/tests/src/python/test_qgscoordinatetransform.py @@ -9,7 +9,6 @@ __author__ = '(C) 2012 by Tim Sutton' __date__ = '20/08/2012' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgscoordinatetransformcontext.py b/tests/src/python/test_qgscoordinatetransformcontext.py index 8e06983feb4..34eed46ffec 100644 --- a/tests/src/python/test_qgscoordinatetransformcontext.py +++ b/tests/src/python/test_qgscoordinatetransformcontext.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '11/5/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgscore.py b/tests/src/python/test_qgscore.py index ab78743de74..723260cc12d 100644 --- a/tests/src/python/test_qgscore.py +++ b/tests/src/python/test_qgscore.py @@ -9,7 +9,6 @@ __author__ = 'Loïc Bartoletti' __date__ = '28.6.2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.core import qgsDoubleNear, qgsRound import unittest diff --git a/tests/src/python/test_qgscrsdefinitionwidget.py b/tests/src/python/test_qgscrsdefinitionwidget.py index e3aae280087..040dd2ef659 100644 --- a/tests/src/python/test_qgscrsdefinitionwidget.py +++ b/tests/src/python/test_qgscrsdefinitionwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/12/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsCoordinateReferenceSystem from qgis.gui import QgsCrsDefinitionWidget diff --git a/tests/src/python/test_qgscrsselectionwidget.py b/tests/src/python/test_qgscrsselectionwidget.py index 48f7069bbfd..ab3a7708c45 100644 --- a/tests/src/python/test_qgscrsselectionwidget.py +++ b/tests/src/python/test_qgscrsselectionwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/12/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsCoordinateReferenceSystem from qgis.gui import QgsCrsSelectionWidget diff --git a/tests/src/python/test_qgsdatabaseschemacombobox.py b/tests/src/python/test_qgsdatabaseschemacombobox.py index b3c5d50e379..645cc7792ca 100644 --- a/tests/src/python/test_qgsdatabaseschemacombobox.py +++ b/tests/src/python/test_qgsdatabaseschemacombobox.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsProviderRegistry diff --git a/tests/src/python/test_qgsdatabasetablecombobox.py b/tests/src/python/test_qgsdatabasetablecombobox.py index df1befd56ed..17daa6563f8 100644 --- a/tests/src/python/test_qgsdatabasetablecombobox.py +++ b/tests/src/python/test_qgsdatabasetablecombobox.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QVariant from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsdataitemguiproviderregistry.py b/tests/src/python/test_qgsdataitemguiproviderregistry.py index 6912ea75648..3789baeae42 100644 --- a/tests/src/python/test_qgsdataitemguiproviderregistry.py +++ b/tests/src/python/test_qgsdataitemguiproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '27/10/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.gui import ( QgsDataItemGuiContext, diff --git a/tests/src/python/test_qgsdataitemproviderregistry.py b/tests/src/python/test_qgsdataitemproviderregistry.py index d499129fb8a..1106b2bccdd 100644 --- a/tests/src/python/test_qgsdataitemproviderregistry.py +++ b/tests/src/python/test_qgsdataitemproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '27/10/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsdatetimeedit.py b/tests/src/python/test_qgsdatetimeedit.py index 1d02ca0ef36..ce5735006d3 100644 --- a/tests/src/python/test_qgsdatetimeedit.py +++ b/tests/src/python/test_qgsdatetimeedit.py @@ -9,7 +9,6 @@ __author__ = 'Denis Rouzaud' __date__ = '2018-01-04' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, Qt, QTime from qgis.gui import QgsDateEdit, QgsDateTimeEdit, QgsTimeEdit import unittest diff --git a/tests/src/python/test_qgsdatetimestatisticalsummary.py b/tests/src/python/test_qgsdatetimestatisticalsummary.py index 656eb620150..4af765aaa22 100644 --- a/tests/src/python/test_qgsdatetimestatisticalsummary.py +++ b/tests/src/python/test_qgsdatetimestatisticalsummary.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '07/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.core import NULL, QgsDateTimeStatisticalSummary, QgsInterval from qgis.testing import unittest diff --git a/tests/src/python/test_qgsdefaultvalue.py b/tests/src/python/test_qgsdefaultvalue.py index cec8315cb03..90eef05358d 100644 --- a/tests/src/python/test_qgsdefaultvalue.py +++ b/tests/src/python/test_qgsdefaultvalue.py @@ -10,7 +10,6 @@ __author__ = 'Matthias Kuhn' __date__ = '26.9.2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import QgsDefaultValue from qgis.testing import unittest diff --git a/tests/src/python/test_qgsdelimitedtextprovider.py b/tests/src/python/test_qgsdelimitedtextprovider.py index edb956d1f0a..cb0d5b96aa8 100644 --- a/tests/src/python/test_qgsdelimitedtextprovider.py +++ b/tests/src/python/test_qgsdelimitedtextprovider.py @@ -28,7 +28,6 @@ import time from collections.abc import Callable from pathlib import Path -import qgis # NOQA import test_qgsdelimitedtextprovider_wanted as want # NOQA diff --git a/tests/src/python/test_qgsdistancearea.py b/tests/src/python/test_qgsdistancearea.py index a6f5fb2af7f..706747a4e97 100644 --- a/tests/src/python/test_qgsdistancearea.py +++ b/tests/src/python/test_qgsdistancearea.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2014, The QGIS Project' import math from pprint import pprint -import qgis # NOQA from qgis.PyQt.QtCore import QLocale from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgseditformconfig.py b/tests/src/python/test_qgseditformconfig.py index bfd74094726..57714c84608 100644 --- a/tests/src/python/test_qgseditformconfig.py +++ b/tests/src/python/test_qgseditformconfig.py @@ -14,7 +14,6 @@ import os import socketserver import threading -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgseditwidgets.py b/tests/src/python/test_qgseditwidgets.py index 263257b2aea..a7b835fb986 100644 --- a/tests/src/python/test_qgseditwidgets.py +++ b/tests/src/python/test_qgseditwidgets.py @@ -9,7 +9,6 @@ __author__ = 'Matthias Kuhn' __date__ = '20/05/2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtWidgets import QTextEdit from qgis.core import ( diff --git a/tests/src/python/test_qgselevationprofilecanvas.py b/tests/src/python/test_qgselevationprofilecanvas.py index e3f9ec911f4..f960b6ce8c7 100644 --- a/tests/src/python/test_qgselevationprofilecanvas.py +++ b/tests/src/python/test_qgselevationprofilecanvas.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '28/3/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QEvent, QPoint, QPointF, Qt from qgis.PyQt.QtGui import QKeyEvent, QMouseEvent, QWheelEvent from qgis.core import ( diff --git a/tests/src/python/test_qgsellipsoidutils.py b/tests/src/python/test_qgsellipsoidutils.py index 6d1fc6c053e..a8ef1f54ba4 100644 --- a/tests/src/python/test_qgsellipsoidutils.py +++ b/tests/src/python/test_qgsellipsoidutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/4/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import QgsEllipsoidUtils, QgsProjUtils import unittest diff --git a/tests/src/python/test_qgsembeddedsymbolrenderer.py b/tests/src/python/test_qgsembeddedsymbolrenderer.py index f4f599ed31f..fa94fa0e37f 100644 --- a/tests/src/python/test_qgsembeddedsymbolrenderer.py +++ b/tests/src/python/test_qgsembeddedsymbolrenderer.py @@ -24,7 +24,6 @@ __copyright__ = "(C) 2015, Matthias Kuhn" import unittest -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsEmbeddedSymbolRenderer, diff --git a/tests/src/python/test_qgsencodingselectiondialog.py b/tests/src/python/test_qgsencodingselectiondialog.py index 31599120bed..1c26e39346b 100644 --- a/tests/src/python/test_qgsencodingselectiondialog.py +++ b/tests/src/python/test_qgsencodingselectiondialog.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '21/11/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsexiftools.py b/tests/src/python/test_qgsexiftools.py index 0beb8ebb4a4..c298e10142a 100644 --- a/tests/src/python/test_qgsexiftools.py +++ b/tests/src/python/test_qgsexiftools.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os import shutil -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QDateTime, Qt, QTemporaryFile from qgis.core import QgsExifTools, QgsPointXY import unittest diff --git a/tests/src/python/test_qgsexpression.py b/tests/src/python/test_qgsexpression.py index 6a3c30ebb47..143ac234bc3 100644 --- a/tests/src/python/test_qgsexpression.py +++ b/tests/src/python/test_qgsexpression.py @@ -9,7 +9,6 @@ __author__ = 'Nathan Woodrow' __date__ = '4/11/2012' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsexpressionbuilderwidget.py b/tests/src/python/test_qgsexpressionbuilderwidget.py index 57966686bdf..bfdc922ba47 100644 --- a/tests/src/python/test_qgsexpressionbuilderwidget.py +++ b/tests/src/python/test_qgsexpressionbuilderwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '30/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import QListView from qgis.core import ( diff --git a/tests/src/python/test_qgsexpressionlineedit.py b/tests/src/python/test_qgsexpressionlineedit.py index 8ef5376c579..d47845dec93 100644 --- a/tests/src/python/test_qgsexpressionlineedit.py +++ b/tests/src/python/test_qgsexpressionlineedit.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/08/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA try: from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsextentgroupbox.py b/tests/src/python/test_qgsextentgroupbox.py index 093dc4a5023..7f43af603b8 100644 --- a/tests/src/python/test_qgsextentgroupbox.py +++ b/tests/src/python/test_qgsextentgroupbox.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsextentwidget.py b/tests/src/python/test_qgsextentwidget.py index 83a371f1da7..a591741f111 100644 --- a/tests/src/python/test_qgsextentwidget.py +++ b/tests/src/python/test_qgsextentwidget.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsfeature.py b/tests/src/python/test_qgsfeature.py index 24f99934ea8..e432626fcbc 100644 --- a/tests/src/python/test_qgsfeature.py +++ b/tests/src/python/test_qgsfeature.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2012, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( NULL, QgsFeature, diff --git a/tests/src/python/test_qgsfeatureiterator.py b/tests/src/python/test_qgsfeatureiterator.py index 93b58e859cc..eacebf1478d 100644 --- a/tests/src/python/test_qgsfeatureiterator.py +++ b/tests/src/python/test_qgsfeatureiterator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsfeaturepicker.py b/tests/src/python/test_qgsfeaturepicker.py index 65753bfaa10..b61c0b0ef7c 100644 --- a/tests/src/python/test_qgsfeaturepicker.py +++ b/tests/src/python/test_qgsfeaturepicker.py @@ -9,7 +9,6 @@ __author__ = 'Denis Rouzaud' __date__ = '24/04/2020' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy, QTest from qgis.PyQt.QtWidgets import QComboBox from qgis.core import ( diff --git a/tests/src/python/test_qgsfeaturerequest.py b/tests/src/python/test_qgsfeaturerequest.py index 4fbd218c527..3ec03e2fccf 100644 --- a/tests/src/python/test_qgsfeaturerequest.py +++ b/tests/src/python/test_qgsfeaturerequest.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/06/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsfeaturesink.py b/tests/src/python/test_qgsfeaturesink.py index 54e7e23cd80..e672f953669 100644 --- a/tests/src/python/test_qgsfeaturesink.py +++ b/tests/src/python/test_qgsfeaturesink.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '26/04/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsfeaturesource.py b/tests/src/python/test_qgsfeaturesource.py index e0744f2c283..26ffa49a9c9 100644 --- a/tests/src/python/test_qgsfeaturesource.py +++ b/tests/src/python/test_qgsfeaturesource.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '26/04/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsfeedback.py b/tests/src/python/test_qgsfeedback.py index a1103985d25..5ab83be1dbf 100644 --- a/tests/src/python/test_qgsfeedback.py +++ b/tests/src/python/test_qgsfeedback.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/02/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsFeedback from qgis.testing import unittest diff --git a/tests/src/python/test_qgsfield.py b/tests/src/python/test_qgsfield.py index 7d5fe9800f7..19f8608e977 100644 --- a/tests/src/python/test_qgsfield.py +++ b/tests/src/python/test_qgsfield.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/08/2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsfieldcombobox.py b/tests/src/python/test_qgsfieldcombobox.py index f9bf21b2cff..a98a5719ce3 100644 --- a/tests/src/python/test_qgsfieldcombobox.py +++ b/tests/src/python/test_qgsfieldcombobox.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsfieldformatters.py b/tests/src/python/test_qgsfieldformatters.py index 19ce9678634..2c85adda6f4 100644 --- a/tests/src/python/test_qgsfieldformatters.py +++ b/tests/src/python/test_qgsfieldformatters.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import ( QCoreApplication, QDate, diff --git a/tests/src/python/test_qgsfieldmodel.py b/tests/src/python/test_qgsfieldmodel.py index 8365944eaf3..76ad9e67c4e 100644 --- a/tests/src/python/test_qgsfieldmodel.py +++ b/tests/src/python/test_qgsfieldmodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '14/11/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QModelIndex, Qt, QVariant from qgis.core import ( QgsEditorWidgetSetup, diff --git a/tests/src/python/test_qgsfields.py b/tests/src/python/test_qgsfields.py index 3dd98783bc3..41bb3f36cf4 100644 --- a/tests/src/python/test_qgsfields.py +++ b/tests/src/python/test_qgsfields.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/08/2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QVariant from qgis.core import NULL, QgsVectorLayer import unittest diff --git a/tests/src/python/test_qgsfieldvalidator.py b/tests/src/python/test_qgsfieldvalidator.py index 1e6f21f0150..d10c13738f0 100644 --- a/tests/src/python/test_qgsfieldvalidator.py +++ b/tests/src/python/test_qgsfieldvalidator.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QLocale, QVariant from qgis.PyQt.QtGui import QValidator from qgis.core import QgsVectorLayer diff --git a/tests/src/python/test_qgsfileutils.py b/tests/src/python/test_qgsfileutils.py index d157ca88e98..cd79eb7dc24 100644 --- a/tests/src/python/test_qgsfileutils.py +++ b/tests/src/python/test_qgsfileutils.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.core import Qgis, QgsFileUtils from qgis.testing import unittest diff --git a/tests/src/python/test_qgsfilledlinesymbollayer.py b/tests/src/python/test_qgsfilledlinesymbollayer.py index da22da80ae6..e7efce4a120 100644 --- a/tests/src/python/test_qgsfilledlinesymbollayer.py +++ b/tests/src/python/test_qgsfilledlinesymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2023, Nyall Dawson' from typing import Optional -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsfillsymbollayers.py b/tests/src/python/test_qgsfillsymbollayers.py index 156e792348b..758987deb98 100644 --- a/tests/src/python/test_qgsfillsymbollayers.py +++ b/tests/src/python/test_qgsfillsymbollayers.py @@ -10,7 +10,6 @@ __date__ = '2017-01' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsfilterlineedit.py b/tests/src/python/test_qgsfilterlineedit.py index b7474365f3b..3fc014ab194 100644 --- a/tests/src/python/test_qgsfilterlineedit.py +++ b/tests/src/python/test_qgsfilterlineedit.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/08/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.gui import QgsFilterLineEdit diff --git a/tests/src/python/test_qgsfloatingwidget.py b/tests/src/python/test_qgsfloatingwidget.py index b2917bce2d9..36be92ec2ad 100644 --- a/tests/src/python/test_qgsfloatingwidget.py +++ b/tests/src/python/test_qgsfloatingwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '26/04/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtWidgets import QGridLayout, QWidget from qgis.gui import QgsFloatingWidget import unittest diff --git a/tests/src/python/test_qgsfontbutton.py b/tests/src/python/test_qgsfontbutton.py index 87b2d7defef..5464f63477e 100644 --- a/tests/src/python/test_qgsfontbutton.py +++ b/tests/src/python/test_qgsfontbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '04/06/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsTextFormat diff --git a/tests/src/python/test_qgsfontmanager.py b/tests/src/python/test_qgsfontmanager.py index a543f07a0e4..a0ebc39fe67 100644 --- a/tests/src/python/test_qgsfontmanager.py +++ b/tests/src/python/test_qgsfontmanager.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os.path import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QUrl from qgis.PyQt.QtGui import QFont from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsgeocoderalgorithm.py b/tests/src/python/test_qgsgeocoderalgorithm.py index 333f94a9cea..d8f4f09e679 100644 --- a/tests/src/python/test_qgsgeocoderalgorithm.py +++ b/tests/src/python/test_qgsgeocoderalgorithm.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '02/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.analysis import QgsBatchGeocodeAlgorithm from qgis.core import ( diff --git a/tests/src/python/test_qgsgeocoderlocatorfilter.py b/tests/src/python/test_qgsgeocoderlocatorfilter.py index 44186ed97af..11d6bd20de4 100644 --- a/tests/src/python/test_qgsgeocoderlocatorfilter.py +++ b/tests/src/python/test_qgsgeocoderlocatorfilter.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '02/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsgeometry_avoid_intersections.py b/tests/src/python/test_qgsgeometry_avoid_intersections.py index 67703d53efe..3921018fb38 100644 --- a/tests/src/python/test_qgsgeometry_avoid_intersections.py +++ b/tests/src/python/test_qgsgeometry_avoid_intersections.py @@ -9,7 +9,6 @@ __author__ = 'Martin Dobias' __date__ = '20/08/2012' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.core import QgsFeature, QgsGeometry, QgsVectorLayer import unittest diff --git a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py index e26c1804067..4f1f0f44c87 100644 --- a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py +++ b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2015, Matthias Kuhn' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QPointF, QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter, QPolygonF from qgis.core import ( diff --git a/tests/src/python/test_qgsgeometrywidget.py b/tests/src/python/test_qgsgeometrywidget.py index 33bbd13444b..225d31602cb 100644 --- a/tests/src/python/test_qgsgeometrywidget.py +++ b/tests/src/python/test_qgsgeometrywidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '15/02/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsgooglemapsgeocoder.py b/tests/src/python/test_qgsgooglemapsgeocoder.py index e015afbbecb..c62fa37f53c 100644 --- a/tests/src/python/test_qgsgooglemapsgeocoder.py +++ b/tests/src/python/test_qgsgooglemapsgeocoder.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QUrl from qgis.core import ( QgsCoordinateTransformContext, diff --git a/tests/src/python/test_qgsgpslogger.py b/tests/src/python/test_qgsgpslogger.py index 7587549bdb3..537a2cdc496 100644 --- a/tests/src/python/test_qgsgpslogger.py +++ b/tests/src/python/test_qgsgpslogger.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '10/11/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QBuffer, QCoreApplication, QDateTime from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsgraduatedsymbolrenderer.py b/tests/src/python/test_qgsgraduatedsymbolrenderer.py index 8e1cd85f0a4..3c8450adead 100644 --- a/tests/src/python/test_qgsgraduatedsymbolrenderer.py +++ b/tests/src/python/test_qgsgraduatedsymbolrenderer.py @@ -9,7 +9,6 @@ __author__ = 'Chris Crook' __date__ = '3/10/2014' __copyright__ = 'Copyright 2014, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsgraph.py b/tests/src/python/test_qgsgraph.py index 2cf2b438b61..5e6b5d916f1 100644 --- a/tests/src/python/test_qgsgraph.py +++ b/tests/src/python/test_qgsgraph.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '08/11/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.analysis import QgsGraph from qgis.core import QgsPointXY diff --git a/tests/src/python/test_qgsgrouplayer.py b/tests/src/python/test_qgsgrouplayer.py index aa4f7f313bf..f3ea9ede164 100644 --- a/tests/src/python/test_qgsgrouplayer.py +++ b/tests/src/python/test_qgsgrouplayer.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import os from tempfile import TemporaryDirectory -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir, QEvent, QSize from qgis.PyQt.QtGui import QColor, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgshashlinesymbollayer.py b/tests/src/python/test_qgshashlinesymbollayer.py index 6379ff73be2..5aabc96a9e0 100644 --- a/tests/src/python/test_qgshashlinesymbollayer.py +++ b/tests/src/python/test_qgshashlinesymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2019, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgshighlight.py b/tests/src/python/test_qgshighlight.py index b68ed20d6ef..c3eea17e617 100644 --- a/tests/src/python/test_qgshighlight.py +++ b/tests/src/python/test_qgshighlight.py @@ -13,7 +13,6 @@ import os import unittest from typing import Optional -import qgis # NOQA from qgis.PyQt.QtCore import QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgshistoryproviderregistry.py b/tests/src/python/test_qgshistoryproviderregistry.py index 1658a4eac5f..96fd965b75d 100644 --- a/tests/src/python/test_qgshistoryproviderregistry.py +++ b/tests/src/python/test_qgshistoryproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/10/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.PyQt import sip from qgis.PyQt.QtCore import ( QDate, diff --git a/tests/src/python/test_qgsimagecache.py b/tests/src/python/test_qgsimagecache.py index c040663006a..567e533d409 100644 --- a/tests/src/python/test_qgsimagecache.py +++ b/tests/src/python/test_qgsimagecache.py @@ -15,7 +15,6 @@ import socketserver import threading import time -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir, QSize from qgis.PyQt.QtGui import QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsimagesourcelineedit.py b/tests/src/python/test_qgsimagesourcelineedit.py index 4b3205f3971..8373cfc1fe4 100644 --- a/tests/src/python/test_qgsimagesourcelineedit.py +++ b/tests/src/python/test_qgsimagesourcelineedit.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.gui import QgsImageSourceLineEdit import unittest diff --git a/tests/src/python/test_qgsinputcontroller.py b/tests/src/python/test_qgsinputcontroller.py index 1e45a4a0a7a..312e6cbf355 100644 --- a/tests/src/python/test_qgsinputcontroller.py +++ b/tests/src/python/test_qgsinputcontroller.py @@ -11,7 +11,6 @@ __author__ = '(C) 2022 by Nyall Dawson' __date__ = '14/07/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, Qt, QTime, QTimeZone, QVariant from qgis.gui import ( QgsInputControllerManager, diff --git a/tests/src/python/test_qgsinterpolatedlinesymbollayers.py b/tests/src/python/test_qgsinterpolatedlinesymbollayers.py index 1aa9870d814..c09ea2c0e05 100644 --- a/tests/src/python/test_qgsinterpolatedlinesymbollayers.py +++ b/tests/src/python/test_qgsinterpolatedlinesymbollayers.py @@ -10,7 +10,6 @@ __date__ = '2021-04' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QPointF from qgis.PyQt.QtGui import QColor, QImage, QPainter, QPolygonF from qgis.core import ( diff --git a/tests/src/python/test_qgsinterval.py b/tests/src/python/test_qgsinterval.py index fe1ed98344c..fa1ab843f61 100644 --- a/tests/src/python/test_qgsinterval.py +++ b/tests/src/python/test_qgsinterval.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '10/05/2016' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.core import QgsInterval, QgsUnitTypes from qgis.testing import unittest diff --git a/tests/src/python/test_qgsissue7244.py b/tests/src/python/test_qgsissue7244.py index 70e7f354697..79580e70d02 100644 --- a/tests/src/python/test_qgsissue7244.py +++ b/tests/src/python/test_qgsissue7244.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os -import qgis # NOQA from qgis.core import QgsPointXY, QgsVectorLayer import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsjsonutils.py b/tests/src/python/test_qgsjsonutils.py index b19bbe1d97e..9d56dbc4045 100644 --- a/tests/src/python/test_qgsjsonutils.py +++ b/tests/src/python/test_qgsjsonutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '3/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QLocale, Qt, QTextCodec, QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgslabellinesettings.py b/tests/src/python/test_qgslabellinesettings.py index f8cdd17a87b..74f022030dc 100644 --- a/tests/src/python/test_qgslabellinesettings.py +++ b/tests/src/python/test_qgslabellinesettings.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( QgsExpressionContext, QgsExpressionContextScope, diff --git a/tests/src/python/test_qgslabelobstaclesettings.py b/tests/src/python/test_qgslabelobstaclesettings.py index 266ce84c25b..81b66a47913 100644 --- a/tests/src/python/test_qgslabelobstaclesettings.py +++ b/tests/src/python/test_qgslabelobstaclesettings.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2019-12-07' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsExpressionContext, diff --git a/tests/src/python/test_qgslabelsettingswidget.py b/tests/src/python/test_qgslabelsettingswidget.py index d87fd5511fc..fb7cfe0c2e5 100644 --- a/tests/src/python/test_qgslabelsettingswidget.py +++ b/tests/src/python/test_qgslabelsettingswidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '04/02/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsLabelObstacleSettings, diff --git a/tests/src/python/test_qgslabelthinningsettings.py b/tests/src/python/test_qgslabelthinningsettings.py index 9da745eefab..071bb09243a 100644 --- a/tests/src/python/test_qgslabelthinningsettings.py +++ b/tests/src/python/test_qgslabelthinningsettings.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2019-12-07' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.core import QgsLabelThinningSettings, QgsPalLayerSettings import unittest diff --git a/tests/src/python/test_qgslayerdefinition.py b/tests/src/python/test_qgslayerdefinition.py index 932d82156db..2eb75c33046 100644 --- a/tests/src/python/test_qgslayerdefinition.py +++ b/tests/src/python/test_qgslayerdefinition.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os import shutil -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtXml import QDomDocument from qgis.core import Qgis, QgsLayerDefinition, QgsProject, QgsVectorLayer diff --git a/tests/src/python/test_qgslayermetadata.py b/tests/src/python/test_qgslayermetadata.py index af45869d7d1..d9802e8afd6 100644 --- a/tests/src/python/test_qgslayermetadata.py +++ b/tests/src/python/test_qgslayermetadata.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '11/04/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QRegularExpression, QTime from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayertree.py b/tests/src/python/test_qgslayertree.py index ee135718b3c..37548506817 100644 --- a/tests/src/python/test_qgslayertree.py +++ b/tests/src/python/test_qgslayertree.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os from tempfile import TemporaryDirectory -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir, QEvent from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgslayertreemapcanvasbridge.py b/tests/src/python/test_qgslayertreemapcanvasbridge.py index 605116d4b6b..e86f4eec47d 100644 --- a/tests/src/python/test_qgslayertreemapcanvasbridge.py +++ b/tests/src/python/test_qgslayertreemapcanvasbridge.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '8/03/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsProject, QgsVectorLayer, diff --git a/tests/src/python/test_qgslayertreeview.py b/tests/src/python/test_qgslayertreeview.py index 9a243e55d60..9d317c8b0b9 100644 --- a/tests/src/python/test_qgslayertreeview.py +++ b/tests/src/python/test_qgslayertreeview.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '02.04.2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QStringListModel, QItemSelectionModel from qgis.PyQt.QtTest import QAbstractItemModelTester, QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgslayout.py b/tests/src/python/test_qgslayout.py index 52b4fc6dce0..d5dcb7f5dbc 100644 --- a/tests/src/python/test_qgslayout.py +++ b/tests/src/python/test_qgslayout.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt import sip from qgis.PyQt.QtCore import QPointF, Qt from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgslayoutaligner.py b/tests/src/python/test_qgslayoutaligner.py index d05d4c80c5f..b6ea20ce98b 100644 --- a/tests/src/python/test_qgslayoutaligner.py +++ b/tests/src/python/test_qgslayoutaligner.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '3/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsLayout, diff --git a/tests/src/python/test_qgslayoutatlas.py b/tests/src/python/test_qgslayoutatlas.py index 8bd239ca4ad..6ca6a0f732a 100644 --- a/tests/src/python/test_qgslayoutatlas.py +++ b/tests/src/python/test_qgslayoutatlas.py @@ -14,7 +14,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QFileInfo, QRectF from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutatlasclippingsettings.py b/tests/src/python/test_qgslayoutatlasclippingsettings.py index 77a1de199d6..e6191b22f10 100644 --- a/tests/src/python/test_qgslayoutatlasclippingsettings.py +++ b/tests/src/python/test_qgslayoutatlasclippingsettings.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 Nyall Dawson' __date__ = '03/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutcombobox.py b/tests/src/python/test_qgslayoutcombobox.py index 13e50f82f88..28b93b81522 100644 --- a/tests/src/python/test_qgslayoutcombobox.py +++ b/tests/src/python/test_qgslayoutcombobox.py @@ -9,7 +9,6 @@ __author__ = '(C) 2019 by Nyall Dawson' __date__ = '11/03/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsLayoutManager, diff --git a/tests/src/python/test_qgslayoutexporter.py b/tests/src/python/test_qgslayoutexporter.py index 5720e319c59..6ee97648ac7 100644 --- a/tests/src/python/test_qgslayoutexporter.py +++ b/tests/src/python/test_qgslayoutexporter.py @@ -13,7 +13,6 @@ import os import subprocess import tempfile -import qgis # NOQA from osgeo import gdal from qgis.PyQt.QtCore import ( QDate, diff --git a/tests/src/python/test_qgslayoutframe.py b/tests/src/python/test_qgslayoutframe.py index 6ae1d997d31..003b745963c 100644 --- a/tests/src/python/test_qgslayoutframe.py +++ b/tests/src/python/test_qgslayoutframe.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '23/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import QgsLayoutFrame, QgsLayoutItemHtml import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgslayoutgridsettings.py b/tests/src/python/test_qgslayoutgridsettings.py index 0307e7c4666..e0a267e9d7b 100644 --- a/tests/src/python/test_qgslayoutgridsettings.py +++ b/tests/src/python/test_qgslayoutgridsettings.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor, QPen from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutguides.py b/tests/src/python/test_qgslayoutguides.py index c8f55b4424c..3b379f39b90 100644 --- a/tests/src/python/test_qgslayoutguides.py +++ b/tests/src/python/test_qgslayoutguides.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt import sip from qgis.PyQt.QtCore import QModelIndex, Qt from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgslayouthtml.py b/tests/src/python/test_qgslayouthtml.py index 45a23c39ff6..d688b927c9e 100644 --- a/tests/src/python/test_qgslayouthtml.py +++ b/tests/src/python/test_qgslayouthtml.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QRectF, QUrl, qDebug from qgis.core import ( QgsLayout, diff --git a/tests/src/python/test_qgslayoutitem.py b/tests/src/python/test_qgslayoutitem.py index ff3fa2a44b7..5446b3756fb 100644 --- a/tests/src/python/test_qgslayoutitem.py +++ b/tests/src/python/test_qgslayoutitem.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import ( Qt, QRectF diff --git a/tests/src/python/test_qgslayoutitemcombobox.py b/tests/src/python/test_qgslayoutitemcombobox.py index 8c17e57dee6..d6d282980c2 100644 --- a/tests/src/python/test_qgslayoutitemcombobox.py +++ b/tests/src/python/test_qgslayoutitemcombobox.py @@ -9,7 +9,6 @@ __author__ = '(C) 2019 by Nyall Dawson' __date__ = '11/03/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsLayoutItem, diff --git a/tests/src/python/test_qgslayoutitempropertiesdialog.py b/tests/src/python/test_qgslayoutitempropertiesdialog.py index f7fa2d4204c..1b14baff0db 100644 --- a/tests/src/python/test_qgslayoutitempropertiesdialog.py +++ b/tests/src/python/test_qgslayoutitempropertiesdialog.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsLayout, diff --git a/tests/src/python/test_qgslayoutlabel.py b/tests/src/python/test_qgslayoutlabel.py index 910bea48778..8763444faab 100644 --- a/tests/src/python/test_qgslayoutlabel.py +++ b/tests/src/python/test_qgslayoutlabel.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '23/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QFileInfo from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutmanager.py b/tests/src/python/test_qgslayoutmanager.py index 45f519f5236..0e97fe51846 100644 --- a/tests/src/python/test_qgslayoutmanager.py +++ b/tests/src/python/test_qgslayoutmanager.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '15/03/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutmanagermodel.py b/tests/src/python/test_qgslayoutmanagermodel.py index c8298cfa6f6..b9efff1c0d7 100644 --- a/tests/src/python/test_qgslayoutmanagermodel.py +++ b/tests/src/python/test_qgslayoutmanagermodel.py @@ -9,7 +9,6 @@ __author__ = '(C) 2019 by Nyall Dawson' __date__ = '11/03/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QModelIndex, Qt from qgis.core import ( QgsLayoutManager, diff --git a/tests/src/python/test_qgslayoutmap.py b/tests/src/python/test_qgslayoutmap.py index 6b2c8e08fc2..11786e89f78 100644 --- a/tests/src/python/test_qgslayoutmap.py +++ b/tests/src/python/test_qgslayoutmap.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import ( Qt, QFileInfo, diff --git a/tests/src/python/test_qgslayoutmapgrid.py b/tests/src/python/test_qgslayoutmapgrid.py index 372e7fe4f4a..1f035785e42 100644 --- a/tests/src/python/test_qgslayoutmapgrid.py +++ b/tests/src/python/test_qgslayoutmapgrid.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '20/10/2017' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QRectF from qgis.PyQt.QtGui import QColor, QPainter from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgslayoutmapitemclippingsettings.py b/tests/src/python/test_qgslayoutmapitemclippingsettings.py index 403f3c1fb2a..c49c7de9d27 100644 --- a/tests/src/python/test_qgslayoutmapitemclippingsettings.py +++ b/tests/src/python/test_qgslayoutmapitemclippingsettings.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 Nyall Dawson' __date__ = '03/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, QRectF from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutmapoverview.py b/tests/src/python/test_qgslayoutmapoverview.py index 81d864218cc..9e7cb69ffa7 100644 --- a/tests/src/python/test_qgslayoutmapoverview.py +++ b/tests/src/python/test_qgslayoutmapoverview.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QFileInfo, QRectF from qgis.PyQt.QtGui import QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutmarker.py b/tests/src/python/test_qgslayoutmarker.py index 7512923846a..b687bd210b8 100644 --- a/tests/src/python/test_qgslayoutmarker.py +++ b/tests/src/python/test_qgslayoutmarker.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '05/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QRectF, Qt from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutnortharrowhandler.py b/tests/src/python/test_qgslayoutnortharrowhandler.py index 73677192175..dc099c62a88 100644 --- a/tests/src/python/test_qgslayoutnortharrowhandler.py +++ b/tests/src/python/test_qgslayoutnortharrowhandler.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '05/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QRectF from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutpage.py b/tests/src/python/test_qgslayoutpage.py index c1b3b539c0e..761293b82b4 100644 --- a/tests/src/python/test_qgslayoutpage.py +++ b/tests/src/python/test_qgslayoutpage.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '23/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgslayoutpagecollection.py b/tests/src/python/test_qgslayoutpagecollection.py index cd5be1b62a5..7cf73fdb251 100644 --- a/tests/src/python/test_qgslayoutpagecollection.py +++ b/tests/src/python/test_qgslayoutpagecollection.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt import sip from qgis.PyQt.QtCore import QCoreApplication, QEvent, QPointF, QRectF, Qt from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgslayoutpicture.py b/tests/src/python/test_qgslayoutpicture.py index 35fce380a74..80a81014d42 100644 --- a/tests/src/python/test_qgslayoutpicture.py +++ b/tests/src/python/test_qgslayoutpicture.py @@ -14,7 +14,6 @@ import os import socketserver import threading -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QRectF from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutpolygon.py b/tests/src/python/test_qgslayoutpolygon.py index 2ede7787074..0c7c3bf696b 100644 --- a/tests/src/python/test_qgslayoutpolygon.py +++ b/tests/src/python/test_qgslayoutpolygon.py @@ -9,7 +9,6 @@ __author__ = '(C) 2016 by Paul Blottiere' __date__ = '14/03/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF, QRectF from qgis.PyQt.QtGui import QImage, QPainter, QPolygonF from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgslayoutpolyline.py b/tests/src/python/test_qgslayoutpolyline.py index e24b500fc34..30c643243b5 100644 --- a/tests/src/python/test_qgslayoutpolyline.py +++ b/tests/src/python/test_qgslayoutpolyline.py @@ -9,7 +9,6 @@ __author__ = '(C) 2016 by Paul Blottiere' __date__ = '14/03/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF from qgis.PyQt.QtGui import QPolygonF from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutscalebar.py b/tests/src/python/test_qgslayoutscalebar.py index c6cbf11ad23..0af5e9149ab 100644 --- a/tests/src/python/test_qgslayoutscalebar.py +++ b/tests/src/python/test_qgslayoutscalebar.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '23/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import QgsLayoutItemScaleBar import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgslayoutshape.py b/tests/src/python/test_qgslayoutshape.py index 6f285416625..5e0bbd558a3 100644 --- a/tests/src/python/test_qgslayoutshape.py +++ b/tests/src/python/test_qgslayoutshape.py @@ -9,7 +9,6 @@ __author__ = '(C) 2017 by Nyall Dawson' __date__ = '23/10/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QRectF from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutsnapper.py b/tests/src/python/test_qgslayoutsnapper.py index 84ee3de11bf..d7159ec813a 100644 --- a/tests/src/python/test_qgslayoutsnapper.py +++ b/tests/src/python/test_qgslayoutsnapper.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF, QRectF, Qt from qgis.PyQt.QtWidgets import QGraphicsLineItem from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslayoutunitscombobox.py b/tests/src/python/test_qgslayoutunitscombobox.py index 06ed8cc3818..c70c4fda431 100644 --- a/tests/src/python/test_qgslayoutunitscombobox.py +++ b/tests/src/python/test_qgslayoutunitscombobox.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtWidgets import QDoubleSpinBox from qgis.core import QgsLayoutMeasurementConverter, QgsUnitTypes diff --git a/tests/src/python/test_qgslayoutview.py b/tests/src/python/test_qgslayoutview.py index ff7aba284be..d9da8849a1b 100644 --- a/tests/src/python/test_qgslayoutview.py +++ b/tests/src/python/test_qgslayoutview.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt import sip from qgis.PyQt.QtCore import QByteArray, QMimeData, QRectF from qgis.PyQt.QtGui import QTransform diff --git a/tests/src/python/test_qgslegendpatchshape.py b/tests/src/python/test_qgslegendpatchshape.py index b749752ed39..7ae79dcb718 100644 --- a/tests/src/python/test_qgslegendpatchshape.py +++ b/tests/src/python/test_qgslegendpatchshape.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '05/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, QSizeF from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgslegendpatchshapebutton.py b/tests/src/python/test_qgslegendpatchshapebutton.py index a0706bad586..6cd5c853b8c 100644 --- a/tests/src/python/test_qgslegendpatchshapebutton.py +++ b/tests/src/python/test_qgslegendpatchshapebutton.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '20/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsGeometry, QgsLegendPatchShape, QgsSymbol from qgis.gui import QgsLegendPatchShapeButton diff --git a/tests/src/python/test_qgslegendpatchshapewidget.py b/tests/src/python/test_qgslegendpatchshapewidget.py index feb4cc41b49..2f8ebfeef35 100644 --- a/tests/src/python/test_qgslegendpatchshapewidget.py +++ b/tests/src/python/test_qgslegendpatchshapewidget.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '20/04/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsGeometry, QgsLegendPatchShape, QgsSymbol from qgis.gui import QgsLegendPatchShapeWidget diff --git a/tests/src/python/test_qgslineburstsymbollayer.py b/tests/src/python/test_qgslineburstsymbollayer.py index d5addbb8199..dde82091c18 100644 --- a/tests/src/python/test_qgslineburstsymbollayer.py +++ b/tests/src/python/test_qgslineburstsymbollayer.py @@ -19,7 +19,6 @@ __author__ = 'Nyall Dawson' __date__ = 'October 2021' __copyright__ = '(C) 2021, Nyall Dawson' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgslinesegment.py b/tests/src/python/test_qgslinesegment.py index c2b28913986..8e780aeba92 100644 --- a/tests/src/python/test_qgslinesegment.py +++ b/tests/src/python/test_qgslinesegment.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '13/04/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import QgsLineSegment2D, QgsPointXY import unittest diff --git a/tests/src/python/test_qgslinesymbollayers.py b/tests/src/python/test_qgslinesymbollayers.py index ae34c16e7b9..773ce7b7e5d 100644 --- a/tests/src/python/test_qgslinesymbollayers.py +++ b/tests/src/python/test_qgslinesymbollayers.py @@ -10,7 +10,6 @@ __date__ = '2017-01' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgslocaldefaultsettings.py b/tests/src/python/test_qgslocaldefaultsettings.py index 3f791a3c5e5..cd10ae0cdd2 100644 --- a/tests/src/python/test_qgslocaldefaultsettings.py +++ b/tests/src/python/test_qgslocaldefaultsettings.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( QgsBearingNumericFormat, diff --git a/tests/src/python/test_qgslocalizeddatapathregistry.py b/tests/src/python/test_qgslocalizeddatapathregistry.py index 3743adca9eb..d9e05990d9d 100644 --- a/tests/src/python/test_qgslocalizeddatapathregistry.py +++ b/tests/src/python/test_qgslocalizeddatapathregistry.py @@ -15,7 +15,6 @@ import tempfile from pathlib import Path from tempfile import NamedTemporaryFile -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgslocator.py b/tests/src/python/test_qgslocator.py index 5fd3f4793fc..a2c9a19a48a 100644 --- a/tests/src/python/test_qgslocator.py +++ b/tests/src/python/test_qgslocator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from time import sleep -import qgis # NOQA from qgis.PyQt import sip from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( diff --git a/tests/src/python/test_qgslogger.py b/tests/src/python/test_qgslogger.py index c113d3bdea5..afa70645a97 100644 --- a/tests/src/python/test_qgslogger.py +++ b/tests/src/python/test_qgslogger.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2012, The QGIS Project' import os import tempfile -import qgis # NOQA (myFileHandle, myFilename) = tempfile.mkstemp() os.environ['QGIS_DEBUG'] = '2' diff --git a/tests/src/python/test_qgsmapboxglconverter.py b/tests/src/python/test_qgsmapboxglconverter.py index a53f83cf8ad..e013ef7e1b4 100644 --- a/tests/src/python/test_qgsmapboxglconverter.py +++ b/tests/src/python/test_qgsmapboxglconverter.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import ( Qt, QCoreApplication, diff --git a/tests/src/python/test_qgsmapcanvas.py b/tests/src/python/test_qgsmapcanvas.py index e49739df3a5..584e61de247 100644 --- a/tests/src/python/test_qgsmapcanvas.py +++ b/tests/src/python/test_qgsmapcanvas.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import time -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QDir, QTime, Qt from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsmapcanvasannotationitem.py b/tests/src/python/test_qgsmapcanvasannotationitem.py index f8555d29219..18f03e8b709 100644 --- a/tests/src/python/test_qgsmapcanvasannotationitem.py +++ b/tests/src/python/test_qgsmapcanvasannotationitem.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '24/1/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF, QSizeF from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsmapclippingregion.py b/tests/src/python/test_qgsmapclippingregion.py index 7b6cba5ae9b..8ebb0ce3347 100644 --- a/tests/src/python/test_qgsmapclippingregion.py +++ b/tests/src/python/test_qgsmapclippingregion.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2020-06' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsGeometry, QgsMapClippingRegion, QgsVectorLayer from qgis.testing import unittest diff --git a/tests/src/python/test_qgsmapclippingutils.py b/tests/src/python/test_qgsmapclippingutils.py index 96a8f73775e..487d7fd2ed0 100644 --- a/tests/src/python/test_qgsmapclippingutils.py +++ b/tests/src/python/test_qgsmapclippingutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2020-06' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsmaplayer.py b/tests/src/python/test_qgsmaplayer.py index 0614ee0f184..e11ebcece73 100644 --- a/tests/src/python/test_qgsmaplayer.py +++ b/tests/src/python/test_qgsmaplayer.py @@ -14,7 +14,6 @@ import os import shutil import tempfile -import qgis # NOQA import sip from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsmaplayeraction.py b/tests/src/python/test_qgsmaplayeraction.py index cb646662c49..d16d29131a3 100644 --- a/tests/src/python/test_qgsmaplayeraction.py +++ b/tests/src/python/test_qgsmaplayeraction.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA switch sip api from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer from qgis.gui import QgsMapLayerAction import unittest diff --git a/tests/src/python/test_qgsmaplayercombobox.py b/tests/src/python/test_qgsmaplayercombobox.py index 3bd80364bde..075900f075e 100644 --- a/tests/src/python/test_qgsmaplayercombobox.py +++ b/tests/src/python/test_qgsmaplayercombobox.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '14/06/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsmaplayerfactory.py b/tests/src/python/test_qgsmaplayerfactory.py index c918cde65d3..bd44f3cc41e 100644 --- a/tests/src/python/test_qgsmaplayerfactory.py +++ b/tests/src/python/test_qgsmaplayerfactory.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( QgsAnnotationLayer, QgsCoordinateTransformContext, diff --git a/tests/src/python/test_qgsmaplayermodel.py b/tests/src/python/test_qgsmaplayermodel.py index 16b3e7e4074..e729e53f510 100644 --- a/tests/src/python/test_qgsmaplayermodel.py +++ b/tests/src/python/test_qgsmaplayermodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/11/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, QModelIndex, Qt from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsmaplayerproxymodel.py b/tests/src/python/test_qgsmaplayerproxymodel.py index e4442db18de..0256fcd4ff7 100644 --- a/tests/src/python/test_qgsmaplayerproxymodel.py +++ b/tests/src/python/test_qgsmaplayerproxymodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '22/08/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsmaplayerutils.py b/tests/src/python/test_qgsmaplayerutils.py index 52dde4d4d33..51c471214a2 100644 --- a/tests/src/python/test_qgsmaplayerutils.py +++ b/tests/src/python/test_qgsmaplayerutils.py @@ -10,7 +10,6 @@ __date__ = '2021-05' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsAnnotationLayer, QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsmaprenderer.py b/tests/src/python/test_qgsmaprenderer.py index 8433125926d..44bf2b4bfb6 100644 --- a/tests/src/python/test_qgsmaprenderer.py +++ b/tests/src/python/test_qgsmaprenderer.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from random import uniform -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtGui import QImage, QPainter from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsmaprenderercache.py b/tests/src/python/test_qgsmaprenderercache.py index 23aec7bc440..df42bc085ae 100644 --- a/tests/src/python/test_qgsmaprenderercache.py +++ b/tests/src/python/test_qgsmaprenderercache.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from time import sleep -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtGui import QImage from qgis.core import ( diff --git a/tests/src/python/test_qgsmapthemecollection.py b/tests/src/python/test_qgsmapthemecollection.py index 2166a7cb485..ef147f0e69e 100644 --- a/tests/src/python/test_qgsmapthemecollection.py +++ b/tests/src/python/test_qgsmapthemecollection.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '8/03/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsMapThemeCollection, QgsProject, QgsVectorLayer from qgis.gui import QgsLayerTreeMapCanvasBridge, QgsMapCanvas diff --git a/tests/src/python/test_qgsmapunitscale.py b/tests/src/python/test_qgsmapunitscale.py index 26e21a8f39f..e99ea171107 100644 --- a/tests/src/python/test_qgsmapunitscale.py +++ b/tests/src/python/test_qgsmapunitscale.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2015-09' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsMapSettings, diff --git a/tests/src/python/test_qgsmargins.py b/tests/src/python/test_qgsmargins.py index 7ba9fa0d93f..47f004961a1 100644 --- a/tests/src/python/test_qgsmargins.py +++ b/tests/src/python/test_qgsmargins.py @@ -10,8 +10,6 @@ __date__ = '2017-01' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA - from qgis.core import QgsMargins from qgis.testing import unittest diff --git a/tests/src/python/test_qgsmarkerlinesymbollayer.py b/tests/src/python/test_qgsmarkerlinesymbollayer.py index b56bc2ebb8c..de1b4aa96e3 100644 --- a/tests/src/python/test_qgsmarkerlinesymbollayer.py +++ b/tests/src/python/test_qgsmarkerlinesymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2018, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsmatrix4x4.py b/tests/src/python/test_qgsmatrix4x4.py index 35d92dd0314..c5891bdcf3d 100644 --- a/tests/src/python/test_qgsmatrix4x4.py +++ b/tests/src/python/test_qgsmatrix4x4.py @@ -11,7 +11,6 @@ __author__ = '(C) 2023 by Martin Dobias' __date__ = '18/07/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, QgsMatrix4x4, diff --git a/tests/src/python/test_qgsmediawidget.py b/tests/src/python/test_qgsmediawidget.py index 3f6f1cc7baf..37013528bbd 100644 --- a/tests/src/python/test_qgsmediawidget.py +++ b/tests/src/python/test_qgsmediawidget.py @@ -9,7 +9,6 @@ __author__ = 'Mathieu Pellerin' __date__ = '25/01/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.gui import QgsMediaWidget import unittest diff --git a/tests/src/python/test_qgsmergedfeaturerenderer.py b/tests/src/python/test_qgsmergedfeaturerenderer.py index e02210a5099..8caed00b40b 100644 --- a/tests/src/python/test_qgsmergedfeaturerenderer.py +++ b/tests/src/python/test_qgsmergedfeaturerenderer.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.PyQt.QtGui import QColor from qgis.core import ( diff --git a/tests/src/python/test_qgsmeshlayerelevationproperties.py b/tests/src/python/test_qgsmeshlayerelevationproperties.py index a4e219ff055..214f06079aa 100644 --- a/tests/src/python/test_qgsmeshlayerelevationproperties.py +++ b/tests/src/python/test_qgsmeshlayerelevationproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsmeshlayerprofilegenerator.py b/tests/src/python/test_qgsmeshlayerprofilegenerator.py index e513eaf300f..fb60d9b9327 100644 --- a/tests/src/python/test_qgsmeshlayerprofilegenerator.py +++ b/tests/src/python/test_qgsmeshlayerprofilegenerator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( Qgis, QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsmessagelog.py b/tests/src/python/test_qgsmessagelog.py index 7fdb3bd02b0..fb278afe875 100644 --- a/tests/src/python/test_qgsmessagelog.py +++ b/tests/src/python/test_qgsmessagelog.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/06/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsmetadatabase.py b/tests/src/python/test_qgsmetadatabase.py index 9e631aaa30a..f1562bd7a03 100644 --- a/tests/src/python/test_qgsmetadatabase.py +++ b/tests/src/python/test_qgsmetadatabase.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '19/03/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsmetadatawidget.py b/tests/src/python/test_qgsmetadatawidget.py index 5af8801c624..31158930ffd 100644 --- a/tests/src/python/test_qgsmetadatawidget.py +++ b/tests/src/python/test_qgsmetadatawidget.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/03/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsmultiedittoolbutton.py b/tests/src/python/test_qgsmultiedittoolbutton.py index c8e86958a2a..8f303a83d58 100644 --- a/tests/src/python/test_qgsmultiedittoolbutton.py +++ b/tests/src/python/test_qgsmultiedittoolbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/03/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.gui import QgsMultiEditToolButton import unittest diff --git a/tests/src/python/test_qgsnetworkaccessmanager.py b/tests/src/python/test_qgsnetworkaccessmanager.py index e5703dc6145..242d1b34215 100644 --- a/tests/src/python/test_qgsnetworkaccessmanager.py +++ b/tests/src/python/test_qgsnetworkaccessmanager.py @@ -15,7 +15,6 @@ import socketserver import threading from functools import partial -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest from qgis.PyQt.QtTest import QSignalSpy diff --git a/tests/src/python/test_qgsnetworkcontentfetcher.py b/tests/src/python/test_qgsnetworkcontentfetcher.py index 64abfbc17a6..8dd5c43ca07 100644 --- a/tests/src/python/test_qgsnetworkcontentfetcher.py +++ b/tests/src/python/test_qgsnetworkcontentfetcher.py @@ -15,7 +15,6 @@ import os import socketserver import threading -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtNetwork import QNetworkReply, QNetworkRequest from qgis.core import QgsNetworkContentFetcher diff --git a/tests/src/python/test_qgsnetworkcontentfetcherregistry.py b/tests/src/python/test_qgsnetworkcontentfetcherregistry.py index bc175166f6a..9b814192f0c 100644 --- a/tests/src/python/test_qgsnetworkcontentfetcherregistry.py +++ b/tests/src/python/test_qgsnetworkcontentfetcherregistry.py @@ -16,7 +16,6 @@ import os import socketserver import threading -import qgis # NOQA from qgis.PyQt.QtNetwork import QNetworkReply from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsnetworkcontentfetchertask.py b/tests/src/python/test_qgsnetworkcontentfetchertask.py index 9be59e75c9d..8a58a44a383 100644 --- a/tests/src/python/test_qgsnetworkcontentfetchertask.py +++ b/tests/src/python/test_qgsnetworkcontentfetchertask.py @@ -16,7 +16,6 @@ import os import socketserver import threading -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.PyQt.QtNetwork import QNetworkReply from qgis.core import ( diff --git a/tests/src/python/test_qgsnetworkreply.py b/tests/src/python/test_qgsnetworkreply.py index 408164ca258..7190afa51bd 100644 --- a/tests/src/python/test_qgsnetworkreply.py +++ b/tests/src/python/test_qgsnetworkreply.py @@ -10,7 +10,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/06/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.core import QgsNetworkReplyContent import unittest diff --git a/tests/src/python/test_qgsnoapplication.py b/tests/src/python/test_qgsnoapplication.py index f18fefc0ac8..a75e7f0ce23 100644 --- a/tests/src/python/test_qgsnoapplication.py +++ b/tests/src/python/test_qgsnoapplication.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import sys -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import QgsApplication from qgis.testing import unittest diff --git a/tests/src/python/test_qgsnominatimgeocoder.py b/tests/src/python/test_qgsnominatimgeocoder.py index 18827346658..ed82aaef292 100644 --- a/tests/src/python/test_qgsnominatimgeocoder.py +++ b/tests/src/python/test_qgsnominatimgeocoder.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( QgsNominatimGeocoder, diff --git a/tests/src/python/test_qgsnullsymbolrenderer.py b/tests/src/python/test_qgsnullsymbolrenderer.py index 8752d014657..47db6356190 100644 --- a/tests/src/python/test_qgsnullsymbolrenderer.py +++ b/tests/src/python/test_qgsnullsymbolrenderer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2016, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsMultiRenderChecker, diff --git a/tests/src/python/test_qgsnumericformat.py b/tests/src/python/test_qgsnumericformat.py index 591897239ea..90cb570b7da 100644 --- a/tests/src/python/test_qgsnumericformat.py +++ b/tests/src/python/test_qgsnumericformat.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '6/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( QgsBasicNumericFormat, diff --git a/tests/src/python/test_qgsnumericformatgui.py b/tests/src/python/test_qgsnumericformatgui.py index 479c881f5d4..a90155a52f3 100644 --- a/tests/src/python/test_qgsnumericformatgui.py +++ b/tests/src/python/test_qgsnumericformatgui.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '6/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsobjectcustomproperties.py b/tests/src/python/test_qgsobjectcustomproperties.py index 6e3dd9c044f..31b45990f5f 100644 --- a/tests/src/python/test_qgsobjectcustomproperties.py +++ b/tests/src/python/test_qgsobjectcustomproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '02/06/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsObjectCustomProperties import unittest diff --git a/tests/src/python/test_qgsogcutils.py b/tests/src/python/test_qgsogcutils.py index cad424d81cd..28616d75781 100644 --- a/tests/src/python/test_qgsogcutils.py +++ b/tests/src/python/test_qgsogcutils.py @@ -13,7 +13,6 @@ __author__ = 'René-Luc Dhont' __date__ = '21/06/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsField, QgsOgcUtils, QgsVectorLayer diff --git a/tests/src/python/test_qgsopacitywidget.py b/tests/src/python/test_qgsopacitywidget.py index eb64111e973..80ce77f7919 100644 --- a/tests/src/python/test_qgsopacitywidget.py +++ b/tests/src/python/test_qgsopacitywidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '30/05/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy import unittest diff --git a/tests/src/python/test_qgsoptional.py b/tests/src/python/test_qgsoptional.py index 5eba0612ae8..4b12bf48796 100644 --- a/tests/src/python/test_qgsoptional.py +++ b/tests/src/python/test_qgsoptional.py @@ -14,7 +14,6 @@ test_qgsoptional.py ***************************************************************************/ ''' -import qgis # NOQA from qgis.core import QgsExpression, QgsOptionalExpression from qgis.testing import unittest diff --git a/tests/src/python/test_qgsorientedbox3d.py b/tests/src/python/test_qgsorientedbox3d.py index 0548892a32f..ef6c1228a75 100644 --- a/tests/src/python/test_qgsorientedbox3d.py +++ b/tests/src/python/test_qgsorientedbox3d.py @@ -12,7 +12,6 @@ __date__ = '10/07/2023' __copyright__ = 'Copyright 2023, The QGIS Project' import math -import qgis # NOQA from qgis.core import ( QgsCoordinateReferenceSystem, QgsCoordinateTransform, diff --git a/tests/src/python/test_qgsowsconnection.py b/tests/src/python/test_qgsowsconnection.py index 839ed8de63e..a75757d3b1e 100644 --- a/tests/src/python/test_qgsowsconnection.py +++ b/tests/src/python/test_qgsowsconnection.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12.09.2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import QgsDataSourceUri, QgsOwsConnection, QgsSettings import unittest diff --git a/tests/src/python/test_qgspallabeling_base.py b/tests/src/python/test_qgspallabeling_base.py index eea0f17de12..60fb93dc47f 100644 --- a/tests/src/python/test_qgspallabeling_base.py +++ b/tests/src/python/test_qgspallabeling_base.py @@ -22,7 +22,6 @@ import shutil import sys from collections.abc import Callable -import qgis # NOQA from qgis.PyQt.QtCore import QSize, Qt, qDebug from qgis.PyQt.QtGui import QColor, QFont from qgis.core import ( diff --git a/tests/src/python/test_qgspallabeling_canvas.py b/tests/src/python/test_qgspallabeling_canvas.py index 0b8209b586d..75d429c73ea 100644 --- a/tests/src/python/test_qgspallabeling_canvas.py +++ b/tests/src/python/test_qgspallabeling_canvas.py @@ -17,7 +17,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os import sys -import qgis # NOQA from qgis.PyQt.QtCore import qDebug from qgis.core import QgsVectorLayerSimpleLabeling diff --git a/tests/src/python/test_qgspallabeling_layout.py b/tests/src/python/test_qgspallabeling_layout.py index 67d243649a9..b4b56c71470 100644 --- a/tests/src/python/test_qgspallabeling_layout.py +++ b/tests/src/python/test_qgspallabeling_layout.py @@ -18,7 +18,6 @@ import os import subprocess import sys -import qgis # NOQA from qgis.PyQt.QtCore import QRect, QRectF, QSize, QSizeF, qDebug from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtPrintSupport import QPrinter diff --git a/tests/src/python/test_qgspallabeling_placement.py b/tests/src/python/test_qgspallabeling_placement.py index 6f3bbaad883..dbe27178e29 100644 --- a/tests/src/python/test_qgspallabeling_placement.py +++ b/tests/src/python/test_qgspallabeling_placement.py @@ -16,7 +16,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os import sys -import qgis # NOQA from qgis.PyQt.QtCore import qDebug from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgspallabeling_server.py b/tests/src/python/test_qgspallabeling_server.py index 7bfcc94dec2..eab7ec5c6ad 100644 --- a/tests/src/python/test_qgspallabeling_server.py +++ b/tests/src/python/test_qgspallabeling_server.py @@ -19,7 +19,6 @@ import os import shutil import sys -import qgis # NOQA from qgis.PyQt.QtCore import qDebug from qgis.core import QgsApplication, QgsProject, QgsSettings diff --git a/tests/src/python/test_qgspallabeling_tests.py b/tests/src/python/test_qgspallabeling_tests.py index bba832b0780..3f919e19761 100644 --- a/tests/src/python/test_qgspallabeling_tests.py +++ b/tests/src/python/test_qgspallabeling_tests.py @@ -15,7 +15,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QPointF, QSizeF, Qt from qgis.PyQt.QtGui import QFont from qgis.core import ( diff --git a/tests/src/python/test_qgspanelwidget.py b/tests/src/python/test_qgspanelwidget.py index 88a0440d27d..742bc3c9c4e 100644 --- a/tests/src/python/test_qgspanelwidget.py +++ b/tests/src/python/test_qgspanelwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/08/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtWidgets import QDialog, QWidget from qgis.gui import QgsPanelWidget import unittest diff --git a/tests/src/python/test_qgspanelwidgetstack.py b/tests/src/python/test_qgspanelwidgetstack.py index 81abbd3bf59..4e53059242a 100644 --- a/tests/src/python/test_qgspanelwidgetstack.py +++ b/tests/src/python/test_qgspanelwidgetstack.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/10/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.gui import QgsPanelWidget, QgsPanelWidgetStack import unittest diff --git a/tests/src/python/test_qgspathresolver.py b/tests/src/python/test_qgspathresolver.py index a2dc12c0684..0f7240421fd 100644 --- a/tests/src/python/test_qgspathresolver.py +++ b/tests/src/python/test_qgspathresolver.py @@ -13,7 +13,6 @@ import gc import os import tempfile -import qgis # NOQA from qgis.core import ( QgsApplication, QgsPathResolver, diff --git a/tests/src/python/test_qgsplot.py b/tests/src/python/test_qgsplot.py index 0e1b006e9ed..d699c81f164 100644 --- a/tests/src/python/test_qgsplot.py +++ b/tests/src/python/test_qgsplot.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '28/3/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSizeF, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspoint.py b/tests/src/python/test_qgspoint.py index f3efffb0b19..ca93433b9a7 100644 --- a/tests/src/python/test_qgspoint.py +++ b/tests/src/python/test_qgspoint.py @@ -9,7 +9,6 @@ __author__ = 'Tim Sutton' __date__ = '20/08/2012' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF from qgis.core import QgsPoint, QgsPointXY, QgsWkbTypes import unittest diff --git a/tests/src/python/test_qgspointcloudattributebyramprenderer.py b/tests/src/python/test_qgspointcloudattributebyramprenderer.py index 25e8b783220..2d32361babe 100644 --- a/tests/src/python/test_qgspointcloudattributebyramprenderer.py +++ b/tests/src/python/test_qgspointcloudattributebyramprenderer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, Qt from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgspointcloudattributecombobox.py b/tests/src/python/test_qgspointcloudattributecombobox.py index e6b8f055e96..0d5db53483d 100644 --- a/tests/src/python/test_qgspointcloudattributecombobox.py +++ b/tests/src/python/test_qgspointcloudattributecombobox.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsPointCloudAttribute, diff --git a/tests/src/python/test_qgspointcloudattributemodel.py b/tests/src/python/test_qgspointcloudattributemodel.py index 0c94c2d9dd1..c111151915c 100644 --- a/tests/src/python/test_qgspointcloudattributemodel.py +++ b/tests/src/python/test_qgspointcloudattributemodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.core import ( QgsPointCloudAttribute, diff --git a/tests/src/python/test_qgspointcloudclassifiedrenderer.py b/tests/src/python/test_qgspointcloudclassifiedrenderer.py index 9b5caa58892..ab078e96d6d 100644 --- a/tests/src/python/test_qgspointcloudclassifiedrenderer.py +++ b/tests/src/python/test_qgspointcloudclassifiedrenderer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize, Qt from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspointcloudelevationproperties.py b/tests/src/python/test_qgspointcloudelevationproperties.py index e7aced497ab..25583422b3d 100644 --- a/tests/src/python/test_qgspointcloudelevationproperties.py +++ b/tests/src/python/test_qgspointcloudelevationproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspointcloudextentrenderer.py b/tests/src/python/test_qgspointcloudextentrenderer.py index 74d04363cd0..9cb92b91dbf 100644 --- a/tests/src/python/test_qgspointcloudextentrenderer.py +++ b/tests/src/python/test_qgspointcloudextentrenderer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '04/12/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, Qt from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspointcloudlayerprofilegenerator.py b/tests/src/python/test_qgspointcloudlayerprofilegenerator.py index 7dc6b4a7a98..3870cc7145d 100644 --- a/tests/src/python/test_qgspointcloudlayerprofilegenerator.py +++ b/tests/src/python/test_qgspointcloudlayerprofilegenerator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.PyQt.QtGui import QColor from qgis.core import ( diff --git a/tests/src/python/test_qgspointcloudprovider.py b/tests/src/python/test_qgspointcloudprovider.py index 5108e91f0db..6f45ceecc19 100644 --- a/tests/src/python/test_qgspointcloudprovider.py +++ b/tests/src/python/test_qgspointcloudprovider.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsPointCloudLayer, QgsProviderRegistry, diff --git a/tests/src/python/test_qgspointcloudrgbrenderer.py b/tests/src/python/test_qgspointcloudrgbrenderer.py index a6cac7611d7..96aa562ddff 100644 --- a/tests/src/python/test_qgspointcloudrgbrenderer.py +++ b/tests/src/python/test_qgspointcloudrgbrenderer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspointdisplacementrenderer.py b/tests/src/python/test_qgspointdisplacementrenderer.py index 44e3cddba35..055a033fc8b 100644 --- a/tests/src/python/test_qgspointdisplacementrenderer.py +++ b/tests/src/python/test_qgspointdisplacementrenderer.py @@ -24,7 +24,6 @@ __copyright__ = '(C) 2016, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspolymorphicrelation.py b/tests/src/python/test_qgspolymorphicrelation.py index 3e4e18754d7..d3a60846d5c 100644 --- a/tests/src/python/test_qgspolymorphicrelation.py +++ b/tests/src/python/test_qgspolymorphicrelation.py @@ -9,7 +9,6 @@ __author__ = 'Ivan Ivanov' __date__ = '11/1/2021' __copyright__ = 'Copyright 2021, QGIS Project' -import qgis # NOQA from qgis.core import ( QgsFeature, diff --git a/tests/src/python/test_qgspostgresdomain.py b/tests/src/python/test_qgspostgresdomain.py index 3c2a0f29ccb..8972d8bcd24 100644 --- a/tests/src/python/test_qgspostgresdomain.py +++ b/tests/src/python/test_qgspostgresdomain.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA from qgis.core import QgsProject, QgsVectorLayer import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgspostgrestransaction.py b/tests/src/python/test_qgspostgrestransaction.py index f2203d96e5c..d63a3c4c198 100644 --- a/tests/src/python/test_qgspostgrestransaction.py +++ b/tests/src/python/test_qgspostgrestransaction.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( Qgis, QgsDataSourceUri, diff --git a/tests/src/python/test_qgsprocessingbatch.py b/tests/src/python/test_qgsprocessingbatch.py index 6f09068d0f4..4690400c15e 100644 --- a/tests/src/python/test_qgsprocessingbatch.py +++ b/tests/src/python/test_qgsprocessingbatch.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsProcessingBatchFeedback, QgsProcessingFeedback import unittest diff --git a/tests/src/python/test_qgsprofileexporter.py b/tests/src/python/test_qgsprofileexporter.py index 5a409056519..87e06f26111 100644 --- a/tests/src/python/test_qgsprofileexporter.py +++ b/tests/src/python/test_qgsprofileexporter.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os import tempfile -import qgis # NOQA from qgis.core import ( Qgis, QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsprofilepoint.py b/tests/src/python/test_qgsprofilepoint.py index 4a442375cec..9542b2a029f 100644 --- a/tests/src/python/test_qgsprofilepoint.py +++ b/tests/src/python/test_qgsprofilepoint.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.core import QgsProfilePoint import unittest diff --git a/tests/src/python/test_qgsprofilerequest.py b/tests/src/python/test_qgsprofilerequest.py index 9c0009b94ea..1b03099603c 100644 --- a/tests/src/python/test_qgsprofilerequest.py +++ b/tests/src/python/test_qgsprofilerequest.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsproject.py b/tests/src/python/test_qgsproject.py index be29700c551..88df2c71f56 100644 --- a/tests/src/python/test_qgsproject.py +++ b/tests/src/python/test_qgsproject.py @@ -18,7 +18,6 @@ from shutil import copyfile from tempfile import TemporaryDirectory from zipfile import ZipFile -import qgis # NOQA from osgeo import ogr from qgis.PyQt import sip from qgis.PyQt.QtCore import QT_VERSION_STR, QTemporaryDir diff --git a/tests/src/python/test_qgsprojectbadlayers.py b/tests/src/python/test_qgsprojectbadlayers.py index 9ccffbe0715..3d4b5d97d28 100644 --- a/tests/src/python/test_qgsprojectbadlayers.py +++ b/tests/src/python/test_qgsprojectbadlayers.py @@ -13,7 +13,6 @@ import filecmp import os from shutil import copyfile -import qgis # NOQA from qgis.PyQt.QtCore import QSize, QTemporaryDir from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument, QDomNode diff --git a/tests/src/python/test_qgsprojectdisplaysettings.py b/tests/src/python/test_qgsprojectdisplaysettings.py index df605e1752e..b70c5cca4d3 100644 --- a/tests/src/python/test_qgsprojectdisplaysettings.py +++ b/tests/src/python/test_qgsprojectdisplaysettings.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsprojectelevationproperties.py b/tests/src/python/test_qgsprojectelevationproperties.py index 380317be785..35c98c738ae 100644 --- a/tests/src/python/test_qgsprojectelevationproperties.py +++ b/tests/src/python/test_qgsprojectelevationproperties.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsprojectgpssettings.py b/tests/src/python/test_qgsprojectgpssettings.py index e9626802fb0..7905faaddee 100644 --- a/tests/src/python/test_qgsprojectgpssettings.py +++ b/tests/src/python/test_qgsprojectgpssettings.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsprojectionselectionwidgets.py b/tests/src/python/test_qgsprojectionselectionwidgets.py index dee8fa43ce3..53c86f29737 100644 --- a/tests/src/python/test_qgsprojectionselectionwidgets.py +++ b/tests/src/python/test_qgsprojectionselectionwidgets.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/11/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtWidgets import QComboBox from qgis.core import QgsCoordinateReferenceSystem, QgsProject diff --git a/tests/src/python/test_qgsprojectmetadata.py b/tests/src/python/test_qgsprojectmetadata.py index dcd245d7135..54446251102 100644 --- a/tests/src/python/test_qgsprojectmetadata.py +++ b/tests/src/python/test_qgsprojectmetadata.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '19/03/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsprojectrelationmanager.py b/tests/src/python/test_qgsprojectrelationmanager.py index bcc23b1985d..6dc33de126c 100644 --- a/tests/src/python/test_qgsprojectrelationmanager.py +++ b/tests/src/python/test_qgsprojectrelationmanager.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( QgsProject, QgsRelation, diff --git a/tests/src/python/test_qgsprojectstylesettings.py b/tests/src/python/test_qgsprojectstylesettings.py index f833dd7740e..d42258ce3c2 100644 --- a/tests/src/python/test_qgsprojectstylesettings.py +++ b/tests/src/python/test_qgsprojectstylesettings.py @@ -9,7 +9,6 @@ __author__ = 'Mathieu Pellerin' __date__ = '09/05/2022' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import ( QCoreApplication, QEvent, diff --git a/tests/src/python/test_qgsprojecttimesettings.py b/tests/src/python/test_qgsprojecttimesettings.py index 711539675bf..f39c069b346 100644 --- a/tests/src/python/test_qgsprojecttimesettings.py +++ b/tests/src/python/test_qgsprojecttimesettings.py @@ -9,7 +9,6 @@ __author__ = 'Samweli Mwakisambwe' __date__ = '6/3/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsprojectutils.py b/tests/src/python/test_qgsprojectutils.py index 2f867159c2d..8dd823233ac 100644 --- a/tests/src/python/test_qgsprojectutils.py +++ b/tests/src/python/test_qgsprojectutils.py @@ -10,7 +10,6 @@ __date__ = '2021-07' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsCoordinateTransformContext, QgsGroupLayer, diff --git a/tests/src/python/test_qgsprojectviewsettings.py b/tests/src/python/test_qgsprojectviewsettings.py index 909a803bf97..53b29cb4362 100644 --- a/tests/src/python/test_qgsprojectviewsettings.py +++ b/tests/src/python/test_qgsprojectviewsettings.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgspropertyoverridebutton.py b/tests/src/python/test_qgspropertyoverridebutton.py index a013ac4c07a..3e7897cf93f 100644 --- a/tests/src/python/test_qgspropertyoverridebutton.py +++ b/tests/src/python/test_qgspropertyoverridebutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '11/01/2019' __copyright__ = 'Copyright 2019, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsproviderconnectioncombobox.py b/tests/src/python/test_qgsproviderconnectioncombobox.py index a07ea43b0f8..cf90bf32abc 100644 --- a/tests/src/python/test_qgsproviderconnectioncombobox.py +++ b/tests/src/python/test_qgsproviderconnectioncombobox.py @@ -13,7 +13,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsProviderRegistry, QgsVectorLayer diff --git a/tests/src/python/test_qgsproviderguiregistry.py b/tests/src/python/test_qgsproviderguiregistry.py index f4b7b2297fe..82fdbd51839 100644 --- a/tests/src/python/test_qgsproviderguiregistry.py +++ b/tests/src/python/test_qgsproviderguiregistry.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import sys -import qgis # NOQA from qgis.gui import QgsGui import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsproviderregistry.py b/tests/src/python/test_qgsproviderregistry.py index 446e1ded318..efd95b98144 100644 --- a/tests/src/python/test_qgsproviderregistry.py +++ b/tests/src/python/test_qgsproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/03/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, QgsMapLayerType, diff --git a/tests/src/python/test_qgsprovidersublayerdetails.py b/tests/src/python/test_qgsprovidersublayerdetails.py index 99d12013403..cf7039c7df3 100644 --- a/tests/src/python/test_qgsprovidersublayerdetails.py +++ b/tests/src/python/test_qgsprovidersublayerdetails.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( Qgis, QgsCoordinateTransformContext, diff --git a/tests/src/python/test_qgsprovidersublayermodel.py b/tests/src/python/test_qgsprovidersublayermodel.py index 61f87dc5598..305f14be6b5 100644 --- a/tests/src/python/test_qgsprovidersublayermodel.py +++ b/tests/src/python/test_qgsprovidersublayermodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '05/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QModelIndex, Qt from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsrandommarkersymbollayer.py b/tests/src/python/test_qgsrandommarkersymbollayer.py index f3c48178d68..2dec6b10af4 100644 --- a/tests/src/python/test_qgsrandommarkersymbollayer.py +++ b/tests/src/python/test_qgsrandommarkersymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2019, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsrange.py b/tests/src/python/test_qgsrange.py index 661758cc354..ed59039bce1 100644 --- a/tests/src/python/test_qgsrange.py +++ b/tests/src/python/test_qgsrange.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '11.04.2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate from qgis.core import QgsDateRange, QgsDoubleRange, QgsIntRange from qgis.testing import unittest diff --git a/tests/src/python/test_qgsrangeslider.py b/tests/src/python/test_qgsrangeslider.py index 04f621ddcfc..eb20b18e5a0 100644 --- a/tests/src/python/test_qgsrangeslider.py +++ b/tests/src/python/test_qgsrangeslider.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2020-11-25' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtWidgets import QSlider diff --git a/tests/src/python/test_qgsrangewidgets.py b/tests/src/python/test_qgsrangewidgets.py index 61ddbe70d7e..ab536c7c429 100644 --- a/tests/src/python/test_qgsrangewidgets.py +++ b/tests/src/python/test_qgsrangewidgets.py @@ -9,7 +9,6 @@ __author__ = 'Tobias Reber' __date__ = '20/05/2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.core import NULL, QgsFeature, QgsGeometry, QgsPointXY, QgsVectorLayer from qgis.gui import QgsGui diff --git a/tests/src/python/test_qgsrasterattributetable.py b/tests/src/python/test_qgsrasterattributetable.py index c679c4d84f6..44d074f08e4 100644 --- a/tests/src/python/test_qgsrasterattributetable.py +++ b/tests/src/python/test_qgsrasterattributetable.py @@ -17,7 +17,6 @@ import os import shutil import numpy as np -import qgis # NOQA from osgeo import gdal, osr from qgis.PyQt.QtCore import QTemporaryDir, QVariant from qgis.PyQt.QtGui import QColor diff --git a/tests/src/python/test_qgsrasterattributetablemodel.py b/tests/src/python/test_qgsrasterattributetablemodel.py index 25670333902..96053881c42 100644 --- a/tests/src/python/test_qgsrasterattributetablemodel.py +++ b/tests/src/python/test_qgsrasterattributetablemodel.py @@ -13,7 +13,6 @@ __author__ = 'Alessandro Pasotti' __date__ = '04/09/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.Qt import Qt from qgis.PyQt.QtCore import QModelIndex, QVariant from qgis.PyQt.QtGui import QColor diff --git a/tests/src/python/test_qgsrasterattributetablewidget.py b/tests/src/python/test_qgsrasterattributetablewidget.py index d2775b22151..1955d294871 100644 --- a/tests/src/python/test_qgsrasterattributetablewidget.py +++ b/tests/src/python/test_qgsrasterattributetablewidget.py @@ -13,7 +13,6 @@ __author__ = 'Alessandro Pasotti' __date__ = '20/08/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir, QVariant from qgis.PyQt.QtWidgets import QDialog, QVBoxLayout from qgis.core import ( diff --git a/tests/src/python/test_qgsrasterbandcombobox.py b/tests/src/python/test_qgsrasterbandcombobox.py index dcc12833d67..396b5bd9a73 100644 --- a/tests/src/python/test_qgsrasterbandcombobox.py +++ b/tests/src/python/test_qgsrasterbandcombobox.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QFileInfo from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsRasterLayer diff --git a/tests/src/python/test_qgsrastercolorrampshader.py b/tests/src/python/test_qgsrastercolorrampshader.py index bd749917746..a95d9227c2d 100644 --- a/tests/src/python/test_qgsrastercolorrampshader.py +++ b/tests/src/python/test_qgsrastercolorrampshader.py @@ -10,7 +10,6 @@ __author__ = 'Nyall Dawson' __date__ = '17/08/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.core import QgsColorRampShader, QgsGradientColorRamp, QgsGradientStop from qgis.testing import unittest diff --git a/tests/src/python/test_qgsrasterfilewriter.py b/tests/src/python/test_qgsrasterfilewriter.py index a378188e278..cd7714ab981 100644 --- a/tests/src/python/test_qgsrasterfilewriter.py +++ b/tests/src/python/test_qgsrasterfilewriter.py @@ -13,7 +13,6 @@ import glob import os import tempfile -import qgis # NOQA from osgeo import gdal from qgis.PyQt.QtCore import QDir, QTemporaryFile from qgis.core import ( diff --git a/tests/src/python/test_qgsrasterfilewritertask.py b/tests/src/python/test_qgsrasterfilewritertask.py index 66413a8475e..c01a0e9bc5c 100644 --- a/tests/src/python/test_qgsrasterfilewritertask.py +++ b/tests/src/python/test_qgsrasterfilewritertask.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsrasterlayer.py b/tests/src/python/test_qgsrasterlayer.py index 9b67e696543..22fb63a7dd3 100644 --- a/tests/src/python/test_qgsrasterlayer.py +++ b/tests/src/python/test_qgsrasterlayer.py @@ -18,7 +18,6 @@ import os from shutil import copyfile import numpy as np -import qgis # NOQA from osgeo import gdal from qgis.PyQt.QtCore import QFileInfo, QSize, QTemporaryDir from qgis.PyQt.QtGui import QColor, QResizeEvent diff --git a/tests/src/python/test_qgsrasterlayerelevationproperties.py b/tests/src/python/test_qgsrasterlayerelevationproperties.py index f14dc809102..f5b46d2a4d9 100644 --- a/tests/src/python/test_qgsrasterlayerelevationproperties.py +++ b/tests/src/python/test_qgsrasterlayerelevationproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA import os from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsrasterlayerprofilegenerator.py b/tests/src/python/test_qgsrasterlayerprofilegenerator.py index d8fb8524582..b3f3f4b815d 100644 --- a/tests/src/python/test_qgsrasterlayerprofilegenerator.py +++ b/tests/src/python/test_qgsrasterlayerprofilegenerator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( Qgis, QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsrasterlayerproperties.py b/tests/src/python/test_qgsrasterlayerproperties.py index 13ae02f37ad..afddf97bca9 100644 --- a/tests/src/python/test_qgsrasterlayerproperties.py +++ b/tests/src/python/test_qgsrasterlayerproperties.py @@ -13,7 +13,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import pathlib import typing -import qgis # NOQA from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtWidgets import QWidget from qgis.core import QgsMapLayer, QgsProject, QgsRasterLayer diff --git a/tests/src/python/test_qgsrasterlayerrenderer.py b/tests/src/python/test_qgsrasterlayerrenderer.py index a1c86ed515f..0befb79ec3c 100644 --- a/tests/src/python/test_qgsrasterlayerrenderer.py +++ b/tests/src/python/test_qgsrasterlayerrenderer.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsrasterlinesymbollayer.py b/tests/src/python/test_qgsrasterlinesymbollayer.py index a5f3510b47b..4c9b7f783a2 100644 --- a/tests/src/python/test_qgsrasterlinesymbollayer.py +++ b/tests/src/python/test_qgsrasterlinesymbollayer.py @@ -20,7 +20,6 @@ __date__ = 'March 2019' __copyright__ = '(C) 2019, Nyall Dawson' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsrasterpipe.py b/tests/src/python/test_qgsrasterpipe.py index 150c3a1cbf2..bf89fba8c5c 100644 --- a/tests/src/python/test_qgsrasterpipe.py +++ b/tests/src/python/test_qgsrasterpipe.py @@ -22,7 +22,6 @@ __author__ = 'Nyall Dawson' __date__ = 'June 2021' __copyright__ = '(C) 2021, Nyall Dawson' -import qgis # NOQA from qgis.core import ( QgsExpressionContext, QgsProperty, diff --git a/tests/src/python/test_qgsrasterrange.py b/tests/src/python/test_qgsrasterrange.py index 45a7fe1a509..d9ad239c57a 100644 --- a/tests/src/python/test_qgsrasterrange.py +++ b/tests/src/python/test_qgsrasterrange.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '07/06/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA switch sip api from qgis.core import QgsRasterRange from qgis.testing import unittest diff --git a/tests/src/python/test_qgsrasterrendererutils.py b/tests/src/python/test_qgsrasterrendererutils.py index aacad4e659f..bca3ea164f5 100644 --- a/tests/src/python/test_qgsrasterrendererutils.py +++ b/tests/src/python/test_qgsrasterrendererutils.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '15/09/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtGui import QColor from qgis.core import QgsColorRampShader, QgsRasterRendererUtils diff --git a/tests/src/python/test_qgsrasterrerderer_createsld.py b/tests/src/python/test_qgsrasterrerderer_createsld.py index 6e830350237..e177c453cda 100644 --- a/tests/src/python/test_qgsrasterrerderer_createsld.py +++ b/tests/src/python/test_qgsrasterrerderer_createsld.py @@ -22,7 +22,6 @@ __copyright__ = '(C) 2018, Luigi Pirelli' import os import random -import qgis # NOQA from qgis.PyQt.QtCore import ( QFileInfo, ) diff --git a/tests/src/python/test_qgsrasterresampler.py b/tests/src/python/test_qgsrasterresampler.py index 1cf8ecf060e..2eaea3cea3f 100644 --- a/tests/src/python/test_qgsrasterresampler.py +++ b/tests/src/python/test_qgsrasterresampler.py @@ -16,7 +16,6 @@ import struct import tempfile from contextlib import contextmanager -import qgis # NOQA from osgeo import gdal from qgis.PyQt.QtGui import qRed from qgis.core import ( diff --git a/tests/src/python/test_qgsrastertransparencywidget.py b/tests/src/python/test_qgsrastertransparencywidget.py index 62a29d348f0..9e90ae50c6d 100644 --- a/tests/src/python/test_qgsrastertransparencywidget.py +++ b/tests/src/python/test_qgsrastertransparencywidget.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import pathlib -import qgis # NOQA switch sip api from qgis.core import QgsRasterLayer, QgsRasterRange from qgis.gui import QgsMapCanvas, QgsRasterTransparencyWidget from qgis.testing import TestCase, unittest diff --git a/tests/src/python/test_qgsratiolockbutton.py b/tests/src/python/test_qgsratiolockbutton.py index 1e38257bf73..285a63006db 100644 --- a/tests/src/python/test_qgsratiolockbutton.py +++ b/tests/src/python/test_qgsratiolockbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtWidgets import QDoubleSpinBox import unittest diff --git a/tests/src/python/test_qgsreadwritecontext.py b/tests/src/python/test_qgsreadwritecontext.py index 1dfc685faaf..30fc4e41677 100644 --- a/tests/src/python/test_qgsreadwritecontext.py +++ b/tests/src/python/test_qgsreadwritecontext.py @@ -10,7 +10,6 @@ __author__ = 'Denis Rouzaud' __date__ = '28.02.2018' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.core import Qgis, QgsReadWriteContext from qgis.testing import unittest diff --git a/tests/src/python/test_qgsrectangle.py b/tests/src/python/test_qgsrectangle.py index 8ad9a0f3917..2ca026ab12f 100644 --- a/tests/src/python/test_qgsrectangle.py +++ b/tests/src/python/test_qgsrectangle.py @@ -9,7 +9,6 @@ __author__ = 'Tim Sutton' __date__ = '20/08/2012' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.core import QgsPointXY, QgsRectangle, QgsVector import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsreferencedgeometry.py b/tests/src/python/test_qgsreferencedgeometry.py index 2c2926df8ce..e4a15d67b41 100644 --- a/tests/src/python/test_qgsreferencedgeometry.py +++ b/tests/src/python/test_qgsreferencedgeometry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '31/08/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsrelation.py b/tests/src/python/test_qgsrelation.py index 5bd3af3195a..5a9589cfb2f 100644 --- a/tests/src/python/test_qgsrelation.py +++ b/tests/src/python/test_qgsrelation.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( QgsAttributeEditorElement, QgsFeature, diff --git a/tests/src/python/test_qgsrelationeditorwidgetregistry.py b/tests/src/python/test_qgsrelationeditorwidgetregistry.py index 5f0bf509b17..ab19d759c51 100644 --- a/tests/src/python/test_qgsrelationeditorwidgetregistry.py +++ b/tests/src/python/test_qgsrelationeditorwidgetregistry.py @@ -9,7 +9,6 @@ __author__ = 'Matthias Kuhn' __date__ = '28/11/2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtWidgets import ( QCheckBox, QGridLayout, diff --git a/tests/src/python/test_qgsrelationeditwidget.py b/tests/src/python/test_qgsrelationeditwidget.py index 053ca3879b7..56ee000370c 100644 --- a/tests/src/python/test_qgsrelationeditwidget.py +++ b/tests/src/python/test_qgsrelationeditwidget.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTimer from qgis.PyQt.QtWidgets import ( QDialog, diff --git a/tests/src/python/test_qgsrelationmanager.py b/tests/src/python/test_qgsrelationmanager.py index 96db7feecb1..27842c99b32 100644 --- a/tests/src/python/test_qgsrelationmanager.py +++ b/tests/src/python/test_qgsrelationmanager.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '17/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsPolymorphicRelation, diff --git a/tests/src/python/test_qgsrelationpostgres.py b/tests/src/python/test_qgsrelationpostgres.py index b53812d5969..11983ab7402 100644 --- a/tests/src/python/test_qgsrelationpostgres.py +++ b/tests/src/python/test_qgsrelationpostgres.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.core import QgsProject, QgsVectorLayer import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsrendercontext.py b/tests/src/python/test_qgsrendercontext.py index 3e1343ef24d..b989aa5edc1 100644 --- a/tests/src/python/test_qgsrendercontext.py +++ b/tests/src/python/test_qgsrendercontext.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '16/01/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDateTime, QSize from qgis.PyQt.QtGui import QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsrendereditemresults.py b/tests/src/python/test_qgsrendereditemresults.py index 8210710504f..2113a17df83 100644 --- a/tests/src/python/test_qgsrendereditemresults.py +++ b/tests/src/python/test_qgsrendereditemresults.py @@ -11,7 +11,6 @@ __author__ = '(C) 2021 by Nyall Dawson' __date__ = '03/09/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsRectangle, QgsRenderContext, diff --git a/tests/src/python/test_qgsrenderer.py b/tests/src/python/test_qgsrenderer.py index ee14522888b..d7370271f5d 100644 --- a/tests/src/python/test_qgsrenderer.py +++ b/tests/src/python/test_qgsrenderer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '07/06/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsreport.py b/tests/src/python/test_qgsreport.py index 7b030888259..c91ce45052e 100644 --- a/tests/src/python/test_qgsreport.py +++ b/tests/src/python/test_qgsreport.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '29/12/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( QgsFeature, diff --git a/tests/src/python/test_qgsrubberband.py b/tests/src/python/test_qgsrubberband.py index c239f83d577..a2a9ed6cb61 100644 --- a/tests/src/python/test_qgsrubberband.py +++ b/tests/src/python/test_qgsrubberband.py @@ -6,7 +6,6 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -import qgis # NOQA from qgis.gui import QgsRubberBand import unittest diff --git a/tests/src/python/test_qgsscalebarrendererregistry.py b/tests/src/python/test_qgsscalebarrendererregistry.py index 656f7f2ff46..6af4094666c 100644 --- a/tests/src/python/test_qgsscalebarrendererregistry.py +++ b/tests/src/python/test_qgsscalebarrendererregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '20/03/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsScaleBarRenderer, QgsScaleBarRendererRegistry import unittest diff --git a/tests/src/python/test_qgsscalecalculator.py b/tests/src/python/test_qgsscalecalculator.py index 4761fab5bcd..fb9293595fb 100644 --- a/tests/src/python/test_qgsscalecalculator.py +++ b/tests/src/python/test_qgsscalecalculator.py @@ -9,7 +9,6 @@ __author__ = 'Mathieu Pellerin' __date__ = '30/12/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsscalewidget.py b/tests/src/python/test_qgsscalewidget.py index 5e9a7a12b53..2476d7a852b 100644 --- a/tests/src/python/test_qgsscalewidget.py +++ b/tests/src/python/test_qgsscalewidget.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2019, The QGIS Project' import math -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.gui import QgsScaleWidget import unittest diff --git a/tests/src/python/test_qgsscreenproperties.py b/tests/src/python/test_qgsscreenproperties.py index 68edbb8b872..9ef738ce163 100644 --- a/tests/src/python/test_qgsscreenproperties.py +++ b/tests/src/python/test_qgsscreenproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '22/06/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QGuiApplication from qgis.core import ( QgsScreenProperties, diff --git a/tests/src/python/test_qgssearchwidgettoolbutton.py b/tests/src/python/test_qgssearchwidgettoolbutton.py index c4d5e685a51..5ab2ebda0b8 100644 --- a/tests/src/python/test_qgssearchwidgettoolbutton.py +++ b/tests/src/python/test_qgssearchwidgettoolbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '18/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.gui import QgsSearchWidgetToolButton, QgsSearchWidgetWrapper import unittest diff --git a/tests/src/python/test_qgssearchwidgetwrapper.py b/tests/src/python/test_qgssearchwidgetwrapper.py index fc2f68cb509..ae2ad1a0750 100644 --- a/tests/src/python/test_qgssearchwidgetwrapper.py +++ b/tests/src/python/test_qgssearchwidgetwrapper.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2016-05' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtWidgets import QWidget from qgis.core import QgsFeature, QgsProject, QgsRelation, QgsVectorLayer diff --git a/tests/src/python/test_qgsselectioncontext.py b/tests/src/python/test_qgsselectioncontext.py index 3b1bbc0e4e0..061fdb0346e 100644 --- a/tests/src/python/test_qgsselectioncontext.py +++ b/tests/src/python/test_qgsselectioncontext.py @@ -6,7 +6,6 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. """ -import qgis # NOQA from qgis.core import QgsSelectionContext from qgis.testing import unittest diff --git a/tests/src/python/test_qgssensormanager.py b/tests/src/python/test_qgssensormanager.py index 171c54e30c3..037426afccc 100644 --- a/tests/src/python/test_qgssensormanager.py +++ b/tests/src/python/test_qgssensormanager.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2023, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, QLocale, QTemporaryDir, QIODevice, QBuffer from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgssensormodel.py b/tests/src/python/test_qgssensormodel.py index c636c1080ed..f24e52981c1 100644 --- a/tests/src/python/test_qgssensormodel.py +++ b/tests/src/python/test_qgssensormodel.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2023, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QEvent, QLocale, QTemporaryDir, QIODevice, QBuffer, QDateTime from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgssensorregistry.py b/tests/src/python/test_qgssensorregistry.py index 0297c5a5b4d..29ef06cd229 100644 --- a/tests/src/python/test_qgssensorregistry.py +++ b/tests/src/python/test_qgssensorregistry.py @@ -9,7 +9,6 @@ __author__ = 'Mathieu Pellerin' __date__ = '19/03/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.core import QgsSensorRegistry, QgsSensorAbstractMetadata, QgsTcpSocketSensor, QgsUdpSocketSensor import unittest diff --git a/tests/src/python/test_qgsserver_accesscontrol.py b/tests/src/python/test_qgsserver_accesscontrol.py index 31be7777dc3..d9cb6726ec9 100644 --- a/tests/src/python/test_qgsserver_accesscontrol.py +++ b/tests/src/python/test_qgsserver_accesscontrol.py @@ -14,7 +14,6 @@ import shutil import tempfile from math import sqrt -import qgis # NOQA from osgeo import gdal from osgeo.gdalconst import GA_ReadOnly from qgis.PyQt.QtCore import QSize diff --git a/tests/src/python/test_qgsserver_accesscontrol_wms_getlegendgraphic.py b/tests/src/python/test_qgsserver_accesscontrol_wms_getlegendgraphic.py index bdff6d56336..3175b006dd5 100644 --- a/tests/src/python/test_qgsserver_accesscontrol_wms_getlegendgraphic.py +++ b/tests/src/python/test_qgsserver_accesscontrol_wms_getlegendgraphic.py @@ -13,7 +13,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import urllib.parse -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.testing import unittest diff --git a/tests/src/python/test_qgsserver_cachemanager.py b/tests/src/python/test_qgsserver_cachemanager.py index d0c50ff6171..227dbcc705c 100644 --- a/tests/src/python/test_qgsserver_cachemanager.py +++ b/tests/src/python/test_qgsserver_cachemanager.py @@ -18,7 +18,6 @@ import urllib.error import urllib.parse import urllib.request -import qgis # NOQA from qgis.PyQt.QtCore import QBuffer, QByteArray, QIODevice, QSize from qgis.PyQt.QtGui import QImage from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgsserver_configcache.py b/tests/src/python/test_qgsserver_configcache.py index 181d12caabd..206818985aa 100644 --- a/tests/src/python/test_qgsserver_configcache.py +++ b/tests/src/python/test_qgsserver_configcache.py @@ -15,7 +15,6 @@ import os from pathlib import Path from time import time -import qgis # NOQA from qgis.core import QgsApplication from qgis.server import QgsConfigCache, QgsServerSettings from qgis.testing import unittest diff --git a/tests/src/python/test_qgsserver_wms_dimension.py b/tests/src/python/test_qgsserver_wms_dimension.py index 982eba01aaf..6267742a331 100644 --- a/tests/src/python/test_qgsserver_wms_dimension.py +++ b/tests/src/python/test_qgsserver_wms_dimension.py @@ -17,7 +17,6 @@ import urllib.error import urllib.parse import urllib.request -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.testing import unittest diff --git a/tests/src/python/test_qgsshortcutsmanager.py b/tests/src/python/test_qgsshortcutsmanager.py index c1dfcbf7550..46ad684d954 100644 --- a/tests/src/python/test_qgsshortcutsmanager.py +++ b/tests/src/python/test_qgsshortcutsmanager.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '28/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtWidgets import QAction, QShortcut, QWidget from qgis.core import QgsSettings diff --git a/tests/src/python/test_qgssimplefillsymbollayer.py b/tests/src/python/test_qgssimplefillsymbollayer.py index 282428ad801..e54ff4b310e 100644 --- a/tests/src/python/test_qgssimplefillsymbollayer.py +++ b/tests/src/python/test_qgssimplefillsymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2020, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QPointF, QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgssimplelinesymbollayer.py b/tests/src/python/test_qgssimplelinesymbollayer.py index ad6b9eddd62..d4a78910c3b 100644 --- a/tests/src/python/test_qgssimplelinesymbollayer.py +++ b/tests/src/python/test_qgssimplelinesymbollayer.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2018, Nyall Dawson' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgssingleitemmodel.py b/tests/src/python/test_qgssingleitemmodel.py index f30ab39ae63..97eaa7513ae 100644 --- a/tests/src/python/test_qgssingleitemmodel.py +++ b/tests/src/python/test_qgssingleitemmodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '28/3/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtGui import QColor from qgis.core import QgsSingleItemModel diff --git a/tests/src/python/test_qgssinglesymbolrenderer.py b/tests/src/python/test_qgssinglesymbolrenderer.py index dc4767ca9ad..46395c00edc 100644 --- a/tests/src/python/test_qgssinglesymbolrenderer.py +++ b/tests/src/python/test_qgssinglesymbolrenderer.py @@ -24,7 +24,6 @@ __copyright__ = '(C) 2015, Matthias Kuhn' import os -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsFeatureRequest, diff --git a/tests/src/python/test_qgssourcewidgetproviderregistry.py b/tests/src/python/test_qgssourcewidgetproviderregistry.py index 8ba853f9308..a7ac6bf381d 100644 --- a/tests/src/python/test_qgssourcewidgetproviderregistry.py +++ b/tests/src/python/test_qgssourcewidgetproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '23/12/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsVectorLayer from qgis.gui import ( diff --git a/tests/src/python/test_qgsspatialindex.py b/tests/src/python/test_qgsspatialindex.py index 56e41181701..0bfa11041db 100644 --- a/tests/src/python/test_qgsspatialindex.py +++ b/tests/src/python/test_qgsspatialindex.py @@ -9,7 +9,6 @@ __author__ = 'Alexander Bruy' __date__ = '20/01/2011' __copyright__ = 'Copyright 2012, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsFeature, diff --git a/tests/src/python/test_qgssphere.py b/tests/src/python/test_qgssphere.py index 72120af72e4..bc855d35acd 100644 --- a/tests/src/python/test_qgssphere.py +++ b/tests/src/python/test_qgssphere.py @@ -12,7 +12,6 @@ __date__ = '14/07/2023' __copyright__ = 'Copyright 2023, The QGIS Project' import math -import qgis # NOQA from qgis.core import ( QgsSphere, QgsPoint, diff --git a/tests/src/python/test_qgsstringstatisticalsummary.py b/tests/src/python/test_qgsstringstatisticalsummary.py index 1b26f96e1b7..66e9fb3b646 100644 --- a/tests/src/python/test_qgsstringstatisticalsummary.py +++ b/tests/src/python/test_qgsstringstatisticalsummary.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '07/05/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.core import QgsStringStatisticalSummary from qgis.testing import unittest diff --git a/tests/src/python/test_qgsstringutils.py b/tests/src/python/test_qgsstringutils.py index 8b541e58ba8..5120255300e 100644 --- a/tests/src/python/test_qgsstringutils.py +++ b/tests/src/python/test_qgsstringutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '30/08/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( QgsStringReplacement, diff --git a/tests/src/python/test_qgsstylemodel.py b/tests/src/python/test_qgsstylemodel.py index 932eedb76d9..f6a5f35d0a0 100644 --- a/tests/src/python/test_qgsstylemodel.py +++ b/tests/src/python/test_qgsstylemodel.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '10/09/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QModelIndex, QSize, Qt import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgssubsetstringeditorproviderregistry.py b/tests/src/python/test_qgssubsetstringeditorproviderregistry.py index 01d2f4193e5..9975ac27f05 100644 --- a/tests/src/python/test_qgssubsetstringeditorproviderregistry.py +++ b/tests/src/python/test_qgssubsetstringeditorproviderregistry.py @@ -9,7 +9,6 @@ __author__ = 'Even Rouault' __date__ = '15/11/2020' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import Qt from qgis.core import QgsVectorLayer from qgis.gui import ( diff --git a/tests/src/python/test_qgssvgcache.py b/tests/src/python/test_qgssvgcache.py index 0b67d41618b..d5e45054d82 100644 --- a/tests/src/python/test_qgssvgcache.py +++ b/tests/src/python/test_qgssvgcache.py @@ -15,7 +15,6 @@ import socketserver import threading import time -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgssvgsourcelineedit.py b/tests/src/python/test_qgssvgsourcelineedit.py index 7d907e427c1..71d1c9a6f78 100644 --- a/tests/src/python/test_qgssvgsourcelineedit.py +++ b/tests/src/python/test_qgssvgsourcelineedit.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.gui import QgsSvgSourceLineEdit import unittest diff --git a/tests/src/python/test_qgssymbol.py b/tests/src/python/test_qgssymbol.py index 2f68a7ef3f5..a586fb19162 100644 --- a/tests/src/python/test_qgssymbol.py +++ b/tests/src/python/test_qgssymbol.py @@ -19,7 +19,6 @@ __author__ = 'Nyall Dawson' __date__ = 'January 2016' __copyright__ = '(C) 2016, Nyall Dawson' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize, Qt from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgssymbolbutton.py b/tests/src/python/test_qgssymbolbutton.py index aab7aa54bec..a0032591dd4 100644 --- a/tests/src/python/test_qgssymbolbutton.py +++ b/tests/src/python/test_qgssymbolbutton.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '23/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsFillSymbol, QgsMarkerSymbol, QgsSymbol diff --git a/tests/src/python/test_qgssymbolexpressionvariables.py b/tests/src/python/test_qgssymbolexpressionvariables.py index cea6da37af3..acf7fc45a96 100644 --- a/tests/src/python/test_qgssymbolexpressionvariables.py +++ b/tests/src/python/test_qgssymbolexpressionvariables.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2016, Matthiasd Kuhn' import os -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsFillSymbol, diff --git a/tests/src/python/test_qgssymbollayer.py b/tests/src/python/test_qgssymbollayer.py index 72e65f88086..a9fb248ea94 100644 --- a/tests/src/python/test_qgssymbollayer.py +++ b/tests/src/python/test_qgssymbollayer.py @@ -24,7 +24,6 @@ __copyright__ = '(C) 2012, Massimo Endrighi' import os -import qgis # NOQA import qgis.core from osgeo import ogr from qgis.PyQt.QtCore import ( diff --git a/tests/src/python/test_qgssymbollayer_createsld.py b/tests/src/python/test_qgssymbollayer_createsld.py index 30d4e4fd4c7..608f07eaa16 100644 --- a/tests/src/python/test_qgssymbollayer_createsld.py +++ b/tests/src/python/test_qgssymbollayer_createsld.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2012, Andrea Aime' import os -import qgis # NOQA from qgis.PyQt.QtCore import ( QDir, QFile, diff --git a/tests/src/python/test_qgssymbollayer_readsld.py b/tests/src/python/test_qgssymbollayer_readsld.py index 93ca87c4358..f617a5a5cc7 100644 --- a/tests/src/python/test_qgssymbollayer_readsld.py +++ b/tests/src/python/test_qgssymbollayer_readsld.py @@ -21,7 +21,6 @@ __copyright__ = '(C) 2017, Jorge Gustavo Rocha' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgssymbollayerregistry.py b/tests/src/python/test_qgssymbollayerregistry.py index 64e50d5933e..2cc8fe9f1ab 100644 --- a/tests/src/python/test_qgssymbollayerregistry.py +++ b/tests/src/python/test_qgssymbollayerregistry.py @@ -9,7 +9,6 @@ __author__ = 'Denis Rouzaud' __date__ = '26/11/2021' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.PyQt import sip from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgssymbollayerutils.py b/tests/src/python/test_qgssymbollayerutils.py index 320b3c20c2e..04ce9cadc3e 100644 --- a/tests/src/python/test_qgssymbollayerutils.py +++ b/tests/src/python/test_qgssymbollayerutils.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import math -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QMimeData, QPointF, QSize, QSizeF, Qt from qgis.PyQt.QtXml import QDomDocument, QDomElement from qgis.PyQt.QtGui import QColor, QImage, QPolygonF diff --git a/tests/src/python/test_qgstablecell.py b/tests/src/python/test_qgstablecell.py index d74f15998a0..edaa57771f3 100644 --- a/tests/src/python/test_qgstablecell.py +++ b/tests/src/python/test_qgstablecell.py @@ -9,7 +9,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '10/01/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.core import ( QgsBearingNumericFormat, diff --git a/tests/src/python/test_qgstabwidget.py b/tests/src/python/test_qgstabwidget.py index 3796c4d225a..6063f90fa52 100644 --- a/tests/src/python/test_qgstabwidget.py +++ b/tests/src/python/test_qgstabwidget.py @@ -14,7 +14,6 @@ test_qgstabwidget.py ***************************************************************************/ ''' -import qgis # NOQA from qgis.PyQt.QtWidgets import QWidget from qgis.gui import QgsTabWidget import unittest diff --git a/tests/src/python/test_qgstaskmanager.py b/tests/src/python/test_qgstaskmanager.py index 0d432376377..e0a93f7cb3d 100644 --- a/tests/src/python/test_qgstaskmanager.py +++ b/tests/src/python/test_qgstaskmanager.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os from time import sleep -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.PyQt.QtTest import QSignalSpy from qgis.core import QgsApplication, QgsTask diff --git a/tests/src/python/test_qgstemporalutils.py b/tests/src/python/test_qgstemporalutils.py index 9d04a64f038..f15a823f5e1 100644 --- a/tests/src/python/test_qgstemporalutils.py +++ b/tests/src/python/test_qgstemporalutils.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '13/3/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, Qt, QTime from qgis.core import ( QgsDateTimeRange, diff --git a/tests/src/python/test_qgsterrainprovider.py b/tests/src/python/test_qgsterrainprovider.py index 7233f648820..e9db11648ae 100644 --- a/tests/src/python/test_qgsterrainprovider.py +++ b/tests/src/python/test_qgsterrainprovider.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import math import os -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgstextblock.py b/tests/src/python/test_qgstextblock.py index 4b0ebe28650..6fdd9fa76a9 100644 --- a/tests/src/python/test_qgstextblock.py +++ b/tests/src/python/test_qgstextblock.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsStringUtils, QgsTextBlock, QgsTextFragment import unittest diff --git a/tests/src/python/test_qgstextcharacterformat.py b/tests/src/python/test_qgstextcharacterformat.py index 1ef351e1314..f33bd6b58d5 100644 --- a/tests/src/python/test_qgstextcharacterformat.py +++ b/tests/src/python/test_qgstextcharacterformat.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgstextdocument.py b/tests/src/python/test_qgstextdocument.py index 27ba2fecdbb..20917dc6003 100644 --- a/tests/src/python/test_qgstextdocument.py +++ b/tests/src/python/test_qgstextdocument.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgstextformatwidget.py b/tests/src/python/test_qgstextformatwidget.py index 5a38676141e..eec7aaadad2 100644 --- a/tests/src/python/test_qgstextformatwidget.py +++ b/tests/src/python/test_qgstextformatwidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '2016-09' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QPointF, QSizeF, Qt from qgis.PyQt.QtGui import QColor, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgstextfragment.py b/tests/src/python/test_qgstextfragment.py index a0fa088c6f8..ebacbf6784d 100644 --- a/tests/src/python/test_qgstextfragment.py +++ b/tests/src/python/test_qgstextfragment.py @@ -11,7 +11,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QColor from qgis.core import QgsStringUtils, QgsTextCharacterFormat, QgsTextFragment import unittest diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 1c398040784..fa01f7c3efe 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os from typing import Optional -import qgis # NOQA from PyQt5.QtSvg import QSvgGenerator from qgis.PyQt.QtCore import ( QT_VERSION_STR, diff --git a/tests/src/python/test_qgstiledsceneboundingvolume.py b/tests/src/python/test_qgstiledsceneboundingvolume.py index 118358a7c5c..fd74ba37d9b 100644 --- a/tests/src/python/test_qgstiledsceneboundingvolume.py +++ b/tests/src/python/test_qgstiledsceneboundingvolume.py @@ -13,7 +13,6 @@ __copyright__ = "Copyright 2023, The QGIS Project" import unittest -import qgis # NOQA from qgis.core import ( Qgis, QgsSphere, diff --git a/tests/src/python/test_qgstiledsceneelevationproperties.py b/tests/src/python/test_qgstiledsceneelevationproperties.py index 3949e2eea02..a7f121963f7 100644 --- a/tests/src/python/test_qgstiledsceneelevationproperties.py +++ b/tests/src/python/test_qgstiledsceneelevationproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '23/08/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA import tempfile import os diff --git a/tests/src/python/test_qgstiledscenelayer.py b/tests/src/python/test_qgstiledscenelayer.py index af834b1dfbf..05cef8efaa4 100644 --- a/tests/src/python/test_qgstiledscenelayer.py +++ b/tests/src/python/test_qgstiledscenelayer.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '27/06/2023' __copyright__ = 'Copyright 2023, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtGui import QPainter from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgstiledscenerender.py b/tests/src/python/test_qgstiledscenerender.py index 10c81a83a22..62758adb8cf 100644 --- a/tests/src/python/test_qgstiledscenerender.py +++ b/tests/src/python/test_qgstiledscenerender.py @@ -9,7 +9,6 @@ __author__ = "Nyall Dawson" __date__ = "27/06/2023" __copyright__ = "Copyright 2023, The QGIS Project" -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.core import ( QgsRectangle, diff --git a/tests/src/python/test_qgstiledscenerequest.py b/tests/src/python/test_qgstiledscenerequest.py index 26cf33d8241..3e359ad385a 100644 --- a/tests/src/python/test_qgstiledscenerequest.py +++ b/tests/src/python/test_qgstiledscenerequest.py @@ -13,7 +13,6 @@ __copyright__ = 'Copyright 2023, The QGIS Project' import unittest -import qgis # NOQA from qgis.core import ( Qgis, QgsTiledSceneRequest, diff --git a/tests/src/python/test_qgstiledscenetile.py b/tests/src/python/test_qgstiledscenetile.py index 4b68475945a..8b23d6e17dc 100644 --- a/tests/src/python/test_qgstiledscenetile.py +++ b/tests/src/python/test_qgstiledscenetile.py @@ -13,7 +13,6 @@ __copyright__ = "Copyright 2023, The QGIS Project" import unittest -import qgis # NOQA from qgis.PyQt.QtCore import QUrl from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgstiles.py b/tests/src/python/test_qgstiles.py index 632b293d5f6..3aee9f77666 100644 --- a/tests/src/python/test_qgstiles.py +++ b/tests/src/python/test_qgstiles.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '04/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QSize from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgstreewidgetitem.py b/tests/src/python/test_qgstreewidgetitem.py index a39d383e8b9..716c6d4bc80 100644 --- a/tests/src/python/test_qgstreewidgetitem.py +++ b/tests/src/python/test_qgstreewidgetitem.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '12/07/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.core import NULL from qgis.gui import QgsTreeWidgetItem, QgsTreeWidgetItemObject diff --git a/tests/src/python/test_qgsunittypes.py b/tests/src/python/test_qgsunittypes.py index 43804626367..8eb3f8fc0c8 100644 --- a/tests/src/python/test_qgsunittypes.py +++ b/tests/src/python/test_qgsunittypes.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '03.02.2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QLocale from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsunsetattributevalue.py b/tests/src/python/test_qgsunsetattributevalue.py index 750312d417f..fa8f0e8f2f0 100644 --- a/tests/src/python/test_qgsunsetattributevalue.py +++ b/tests/src/python/test_qgsunsetattributevalue.py @@ -11,7 +11,6 @@ __author__ = '(C) 2020 by Nyall Dawson' __date__ = '29/07/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.core import QgsUnsetAttributeValue import unittest from qgis.testing import start_app, QgisTestCase diff --git a/tests/src/python/test_qgsvaliditychecks.py b/tests/src/python/test_qgsvaliditychecks.py index 4bebc76515f..a03b58154f9 100644 --- a/tests/src/python/test_qgsvaliditychecks.py +++ b/tests/src/python/test_qgsvaliditychecks.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '03/12/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import ( QgsAbstractValidityCheck, diff --git a/tests/src/python/test_qgsvalidityresultswidget.py b/tests/src/python/test_qgsvalidityresultswidget.py index 89f0e832150..f5e4e63306b 100644 --- a/tests/src/python/test_qgsvalidityresultswidget.py +++ b/tests/src/python/test_qgsvalidityresultswidget.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '03/12/2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import QgsValidityCheckResult from qgis.gui import QgsValidityCheckResultsModel diff --git a/tests/src/python/test_qgsvectorfieldmarkersymbollayer.py b/tests/src/python/test_qgsvectorfieldmarkersymbollayer.py index 866c473d1e3..73c26e3bdf1 100644 --- a/tests/src/python/test_qgsvectorfieldmarkersymbollayer.py +++ b/tests/src/python/test_qgsvectorfieldmarkersymbollayer.py @@ -19,7 +19,6 @@ __author__ = 'Nyall Dawson' __date__ = 'November 2021' __copyright__ = '(C) 2021, Nyall Dawson' -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QVariant from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_qgsvectorfilewriter.py b/tests/src/python/test_qgsvectorfilewriter.py index a08489e55ef..5afdf48a211 100644 --- a/tests/src/python/test_qgsvectorfilewriter.py +++ b/tests/src/python/test_qgsvectorfilewriter.py @@ -15,7 +15,6 @@ import tempfile import json import osgeo.gdal # NOQA -import qgis # NOQA from osgeo import gdal, ogr from qgis.PyQt.QtCore import ( QByteArray, diff --git a/tests/src/python/test_qgsvectorfilewriter_postgres.py b/tests/src/python/test_qgsvectorfilewriter_postgres.py index d735a5152ef..816a4dd4482 100644 --- a/tests/src/python/test_qgsvectorfilewriter_postgres.py +++ b/tests/src/python/test_qgsvectorfilewriter_postgres.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QVariant from qgis.core import ( QgsCoordinateReferenceSystem, diff --git a/tests/src/python/test_qgsvectorfilewritertask.py b/tests/src/python/test_qgsvectorfilewritertask.py index e176d0f62a6..52c26d70ac4 100644 --- a/tests/src/python/test_qgsvectorfilewritertask.py +++ b/tests/src/python/test_qgsvectorfilewritertask.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication, QDir from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py index 58f6d6af0d7..51a73c9d772 100644 --- a/tests/src/python/test_qgsvectorlayer.py +++ b/tests/src/python/test_qgsvectorlayer.py @@ -17,7 +17,6 @@ import os import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import ( QDate, QDateTime, diff --git a/tests/src/python/test_qgsvectorlayercache.py b/tests/src/python/test_qgsvectorlayercache.py index 03cdb4494a4..6e958d2c59e 100644 --- a/tests/src/python/test_qgsvectorlayercache.py +++ b/tests/src/python/test_qgsvectorlayercache.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '08/06/2017' __copyright__ = 'Copyright 2017, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsvectorlayereditbuffer.py b/tests/src/python/test_qgsvectorlayereditbuffer.py index 098b5da77f8..eef3407fa1b 100644 --- a/tests/src/python/test_qgsvectorlayereditbuffer.py +++ b/tests/src/python/test_qgsvectorlayereditbuffer.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir, QVariant from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsvectorlayereditbuffergroup.py b/tests/src/python/test_qgsvectorlayereditbuffergroup.py index 34a0eca1c66..497ee468114 100644 --- a/tests/src/python/test_qgsvectorlayereditbuffergroup.py +++ b/tests/src/python/test_qgsvectorlayereditbuffergroup.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QTemporaryDir from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsvectorlayereditutils.py b/tests/src/python/test_qgsvectorlayereditutils.py index dff665936a0..365419e316d 100644 --- a/tests/src/python/test_qgsvectorlayereditutils.py +++ b/tests/src/python/test_qgsvectorlayereditutils.py @@ -15,7 +15,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import ( QVariant, ) diff --git a/tests/src/python/test_qgsvectorlayerelevationproperties.py b/tests/src/python/test_qgsvectorlayerelevationproperties.py index 4d0ec9793e8..2d9357677cd 100644 --- a/tests/src/python/test_qgsvectorlayerelevationproperties.py +++ b/tests/src/python/test_qgsvectorlayerelevationproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '09/11/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsvectorlayerfeaturecounter.py b/tests/src/python/test_qgsvectorlayerfeaturecounter.py index 2309a34aa3a..cd72d8be27c 100644 --- a/tests/src/python/test_qgsvectorlayerfeaturecounter.py +++ b/tests/src/python/test_qgsvectorlayerfeaturecounter.py @@ -9,7 +9,6 @@ __author__ = 'Mathieu Pellerin' __date__ = '08/02/2021' __copyright__ = 'Copyright 2021, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( diff --git a/tests/src/python/test_qgsvectorlayerprofilegenerator.py b/tests/src/python/test_qgsvectorlayerprofilegenerator.py index 175b25fc62d..5b3026750c6 100644 --- a/tests/src/python/test_qgsvectorlayerprofilegenerator.py +++ b/tests/src/python/test_qgsvectorlayerprofilegenerator.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2022, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsvectorlayerrenderer.py b/tests/src/python/test_qgsvectorlayerrenderer.py index 03202f2d6f1..df9aee2bda8 100644 --- a/tests/src/python/test_qgsvectorlayerrenderer.py +++ b/tests/src/python/test_qgsvectorlayerrenderer.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2020, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QSize from qgis.PyQt.QtGui import QColor diff --git a/tests/src/python/test_qgsvectorlayershapefile.py b/tests/src/python/test_qgsvectorlayershapefile.py index a8ea8f489ec..2b7be659a3a 100644 --- a/tests/src/python/test_qgsvectorlayershapefile.py +++ b/tests/src/python/test_qgsvectorlayershapefile.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2012, The QGIS Project' import os -import qgis # NOQA from qgis.core import QgsVectorLayer from qgis.gui import QgsGui import unittest diff --git a/tests/src/python/test_qgsvectorlayertemporalproperties.py b/tests/src/python/test_qgsvectorlayertemporalproperties.py index 116d924bbed..aa201be7df8 100644 --- a/tests/src/python/test_qgsvectorlayertemporalproperties.py +++ b/tests/src/python/test_qgsvectorlayertemporalproperties.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '73/05/2020' __copyright__ = 'Copyright 2020, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QTime, QVariant from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsvectorlayerutils.py b/tests/src/python/test_qgsvectorlayerutils.py index ae35c81e476..f3dd58a6b17 100644 --- a/tests/src/python/test_qgsvectorlayerutils.py +++ b/tests/src/python/test_qgsvectorlayerutils.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2016, The QGIS Project' import shutil import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QVariant from qgis.core import ( NULL, diff --git a/tests/src/python/test_qgsvectorlayerutils_postgres.py b/tests/src/python/test_qgsvectorlayerutils_postgres.py index 5936f1fd09a..50ae082859b 100644 --- a/tests/src/python/test_qgsvectorlayerutils_postgres.py +++ b/tests/src/python/test_qgsvectorlayerutils_postgres.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2021, The QGIS Project' import os -import qgis # NOQA from qgis.core import ( NULL, QgsDefaultValue, diff --git a/tests/src/python/test_qgsvectortile.py b/tests/src/python/test_qgsvectortile.py index c38e1cc2eb6..64d232eb352 100644 --- a/tests/src/python/test_qgsvectortile.py +++ b/tests/src/python/test_qgsvectortile.py @@ -18,7 +18,6 @@ import shutil import tempfile from pathlib import Path -import qgis # NOQA from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( Qgis, diff --git a/tests/src/python/test_qgsvectorwarper.py b/tests/src/python/test_qgsvectorwarper.py index eef8fe04599..9bdf57a2db5 100644 --- a/tests/src/python/test_qgsvectorwarper.py +++ b/tests/src/python/test_qgsvectorwarper.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '01/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.analysis import ( QgsGcpPoint, diff --git a/tests/src/python/test_qgsvirtuallayerdefinition.py b/tests/src/python/test_qgsvirtuallayerdefinition.py index 8f58d2876e2..c8733aedaba 100644 --- a/tests/src/python/test_qgsvirtuallayerdefinition.py +++ b/tests/src/python/test_qgsvirtuallayerdefinition.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2015, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QUrl, QVariant from qgis.core import ( QgsField, diff --git a/tests/src/python/test_qgsvirtuallayertask.py b/tests/src/python/test_qgsvirtuallayertask.py index aae8bda514a..9f184e5149a 100644 --- a/tests/src/python/test_qgsvirtuallayertask.py +++ b/tests/src/python/test_qgsvirtuallayertask.py @@ -11,7 +11,6 @@ __copyright__ = 'Copyright 2018, The QGIS Project' import os -import qgis # NOQA from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( QgsApplication, diff --git a/tests/src/python/test_qgsvtpk.py b/tests/src/python/test_qgsvtpk.py index cbdf251e4fc..bd00b77e3a1 100644 --- a/tests/src/python/test_qgsvtpk.py +++ b/tests/src/python/test_qgsvtpk.py @@ -9,7 +9,6 @@ __author__ = 'Nyall Dawson' __date__ = '04/03/2022' __copyright__ = 'Copyright 2022, The QGIS Project' -import qgis # NOQA from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( diff --git a/tests/src/python/test_qgsxmlutils.py b/tests/src/python/test_qgsxmlutils.py index 6372e56f084..4658782da8e 100644 --- a/tests/src/python/test_qgsxmlutils.py +++ b/tests/src/python/test_qgsxmlutils.py @@ -9,7 +9,6 @@ __author__ = 'Matthias Kuhn' __date__ = '18/11/2016' __copyright__ = 'Copyright 2016, The QGIS Project' -import qgis # NOQA switch sip api from qgis.PyQt.QtCore import QDate, QDateTime, QTime, QVariant from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument diff --git a/tests/src/python/test_qgszonalstatistics.py b/tests/src/python/test_qgszonalstatistics.py index 8022d843b09..043d27c6bfc 100644 --- a/tests/src/python/test_qgszonalstatistics.py +++ b/tests/src/python/test_qgszonalstatistics.py @@ -12,7 +12,6 @@ __copyright__ = 'Copyright 2013, The QGIS Project' import os import shutil -import qgis # NOQA from qgis.PyQt.QtCore import QDir, QFile, QTemporaryDir from qgis.analysis import QgsZonalStatistics from qgis.core import ( diff --git a/tests/src/python/test_selective_masking.py b/tests/src/python/test_selective_masking.py index 4e9a689a0b7..7620588f019 100644 --- a/tests/src/python/test_selective_masking.py +++ b/tests/src/python/test_selective_masking.py @@ -16,7 +16,6 @@ import os import subprocess import tempfile -import qgis # NOQA from qgis.PyQt.QtCore import QRectF, QSize, Qt, QUuid from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.core import ( diff --git a/tests/src/python/test_syntactic_sugar.py b/tests/src/python/test_syntactic_sugar.py index e1709c926f2..30a652d8246 100644 --- a/tests/src/python/test_syntactic_sugar.py +++ b/tests/src/python/test_syntactic_sugar.py @@ -9,7 +9,6 @@ __author__ = 'Matthias Kuhn' __date__ = '12.8.2015' __copyright__ = 'Copyright 2015, The QGIS Project' -import qgis # NOQA from qgis.core import QgsEditError, QgsFeature, QgsVectorLayer, edit import unittest diff --git a/tests/src/python/test_testrunner.py b/tests/src/python/test_testrunner.py index 022d88b9eb2..6be11fc6d4f 100644 --- a/tests/src/python/test_testrunner.py +++ b/tests/src/python/test_testrunner.py @@ -9,7 +9,6 @@ __author__ = 'Alessandro Pasotti' __date__ = '19.11.2018' __copyright__ = 'Copyright 2018, The QGIS Project' -import qgis # NOQA from qgis.core import Qgis from qgis.testing import unittest diff --git a/tests/src/python/test_versioncompare.py b/tests/src/python/test_versioncompare.py index 4a0f8ac9f1a..9622811518f 100644 --- a/tests/src/python/test_versioncompare.py +++ b/tests/src/python/test_versioncompare.py @@ -14,7 +14,6 @@ test_versioncompare.py ***************************************************************************/ ''' -import qgis # NOQA from pyplugin_installer.version_compare import compareVersions import unittest diff --git a/tests/src/python/utilities.py b/tests/src/python/utilities.py index fded7af238f..dceaac9bf25 100644 --- a/tests/src/python/utilities.py +++ b/tests/src/python/utilities.py @@ -16,7 +16,6 @@ import stat import sys import tempfile -import qgis # NOQA try: from urllib2 import HTTPError, URLError, urlopen From 7925d7261ed23d4b937785cb76010f333d584079 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 23 Nov 2023 14:13:57 +1000 Subject: [PATCH 90/97] Cleanup files --- python/pyplugin_installer/installer.py | 9 +++++---- .../qgsplugininstallerinstallingdialog.py | 3 ++- tests/src/python/test_qgscheckablecombobox.py | 8 +++++--- .../python/test_qgsencodingselectiondialog.py | 4 +++- tests/src/python/test_qgsextentgroupbox.py | 10 +++++----- tests/src/python/test_qgsextentwidget.py | 9 ++++++--- tests/src/python/test_qgsfilterlineedit.py | 19 +++++++------------ tests/src/python/test_qgsfloatingwidget.py | 8 ++++---- .../test_qgslayoutitempropertiesdialog.py | 5 +++-- .../src/python/test_qgslayoutunitscombobox.py | 12 ++++++++---- tests/src/python/test_qgsopacitywidget.py | 5 +++-- tests/src/python/test_qgsratiolockbutton.py | 8 +++++--- 12 files changed, 56 insertions(+), 44 deletions(-) diff --git a/python/pyplugin_installer/installer.py b/python/pyplugin_installer/installer.py index fe41fc2341b..043ad2e089f 100644 --- a/python/pyplugin_installer/installer.py +++ b/python/pyplugin_installer/installer.py @@ -46,6 +46,7 @@ from qgis.core import Qgis, QgsApplication, QgsMessageLog, QgsNetworkAccessManag from qgis.gui import QgsMessageBar, QgsPasswordLineEdit, QgsHelp from qgis.utils import (iface, startPlugin, unloadPlugin, loadPlugin, OverrideCursor, reloadPlugin, updateAvailablePlugins, plugins_metadata_parser, isPluginLoaded) +from qgis import utils from .installer_data import (repositories, plugins, officialRepo, reposGroup, removeDir) from .qgsplugininstallerinstallingdialog import QgsPluginInstallerInstallingDialog @@ -322,7 +323,7 @@ class QgsPluginInstaller(QObject): dlg = QgsPluginInstallerInstallingDialog(iface.mainWindow(), plugin, stable=stable) dlg.exec_() - plugin_path = qgis.utils.home_plugin_path + "/" + key + plugin_path = utils.home_plugin_path + "/" + key if dlg.result(): error = True infoString = (self.tr("Plugin installation failed"), dlg.result()) @@ -386,7 +387,7 @@ class QgsPluginInstaller(QObject): dlg.exec_() if dlg.result(): # revert installation - pluginDir = qgis.utils.home_plugin_path + "/" + plugin["id"] + pluginDir = utils.home_plugin_path + "/" + plugin["id"] result = removeDir(pluginDir) if QDir(pluginDir).exists(): error = True @@ -435,7 +436,7 @@ class QgsPluginInstaller(QObject): unloadPlugin(key) except: pass - pluginDir = qgis.utils.home_plugin_path + "/" + plugin["id"] + pluginDir = utils.home_plugin_path + "/" + plugin["id"] result = removeDir(pluginDir) if result: QApplication.restoreOverrideCursor() @@ -611,7 +612,7 @@ class QgsPluginInstaller(QObject): QgsHelp.openHelp("plugins/plugins.html#the-install-from-zip-tab") return - pluginsDirectory = qgis.utils.home_plugin_path + pluginsDirectory = utils.home_plugin_path if not QDir(pluginsDirectory).exists(): QDir().mkpath(pluginsDirectory) diff --git a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py index c15da88ec51..deaf5144bf4 100644 --- a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py +++ b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py @@ -30,6 +30,7 @@ from qgis.PyQt.QtWidgets import QDialog from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply from qgis.core import QgsNetworkAccessManager, QgsApplication, QgsNetworkRequestParameters +from qgis import utils from .ui_qgsplugininstallerinstallingbase import Ui_QgsPluginInstallerInstallingDialogBase from .installer_data import removeDir, repositories @@ -141,7 +142,7 @@ class QgsPluginInstallerInstallingDialog(QDialog, Ui_QgsPluginInstallerInstallin self.file.close() self.stateChanged(0) reply.deleteLater() - pluginDir = qgis.utils.home_plugin_path + pluginDir = utils.home_plugin_path tmpPath = self.file.fileName() # make sure that the parent directory exists if not QDir(pluginDir).exists(): diff --git a/tests/src/python/test_qgscheckablecombobox.py b/tests/src/python/test_qgscheckablecombobox.py index 90de3c37d88..eb3683b4b83 100644 --- a/tests/src/python/test_qgscheckablecombobox.py +++ b/tests/src/python/test_qgscheckablecombobox.py @@ -13,6 +13,8 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtTest import QSignalSpy import unittest + +from qgis.gui import QgsCheckableComboBox from qgis.testing import start_app, QgisTestCase start_app() @@ -22,7 +24,7 @@ class TestQgsCheckableComboBox(QgisTestCase): def testGettersSetters(self): """ test widget getters/setters """ - w = qgis.gui.QgsCheckableComboBox() + w = QgsCheckableComboBox() w.setSeparator('|') self.assertEqual(w.separator(), '|') @@ -44,7 +46,7 @@ class TestQgsCheckableComboBox(QgisTestCase): def test_ChangedSignals(self): """ test that signals are correctly emitted when clearing""" - w = qgis.gui.QgsCheckableComboBox() + w = QgsCheckableComboBox() w.addItems(['One', 'Two', 'Three']) @@ -54,7 +56,7 @@ class TestQgsCheckableComboBox(QgisTestCase): self.assertEqual(len(checkedItemsChanged_spy), 1) def test_readonly(self): - w = qgis.gui.QgsCheckableComboBox() + w = QgsCheckableComboBox() w.setEditable(False) w.show() # Should not crash diff --git a/tests/src/python/test_qgsencodingselectiondialog.py b/tests/src/python/test_qgsencodingselectiondialog.py index 1c26e39346b..9b900080910 100644 --- a/tests/src/python/test_qgsencodingselectiondialog.py +++ b/tests/src/python/test_qgsencodingselectiondialog.py @@ -11,6 +11,8 @@ __copyright__ = 'Copyright 2017, The QGIS Project' import unittest + +from qgis.gui import QgsEncodingSelectionDialog from qgis.testing import start_app, QgisTestCase start_app() @@ -20,7 +22,7 @@ class TestQgsEncodingSelectionDialog(QgisTestCase): def testGettersSetters(self): """ test dialog getters/setters """ - dlg = qgis.gui.QgsEncodingSelectionDialog(encoding='UTF-16') + dlg = QgsEncodingSelectionDialog(encoding='UTF-16') self.assertEqual(dlg.encoding(), 'UTF-16') dlg.setEncoding('UTF-8') self.assertEqual(dlg.encoding(), 'UTF-8') diff --git a/tests/src/python/test_qgsextentgroupbox.py b/tests/src/python/test_qgsextentgroupbox.py index 7f43af603b8..e3093ca4421 100644 --- a/tests/src/python/test_qgsextentgroupbox.py +++ b/tests/src/python/test_qgsextentgroupbox.py @@ -34,7 +34,7 @@ class TestQgsExtentGroupBox(QgisTestCase): def testGettersSetters(self): """ test widget getters/setters """ - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setOriginalExtent(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:3111')) self.assertEqual(w.originalExtent(), QgsRectangle(1, 2, 3, 4)) @@ -80,7 +80,7 @@ class TestQgsExtentGroupBox(QgisTestCase): self.assertEqual(len(spy), 3) def test_SettingExtent(self): - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() spy = QSignalSpy(w.extentChanged) @@ -121,7 +121,7 @@ class TestQgsExtentGroupBox(QgisTestCase): QgsProject.instance().removeAllMapLayers() def testSetOutputCrs(self): - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setCheckable(True) # ensure setting output crs doesn't change state of group box @@ -146,7 +146,7 @@ class TestQgsExtentGroupBox(QgisTestCase): self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20)) # repeat, this time using original extents - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326')) w.setOriginalExtent(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326')) @@ -182,7 +182,7 @@ class TestQgsExtentGroupBox(QgisTestCase): self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20)) # custom extent - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326')) w.setOutputExtentFromUser(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326')) diff --git a/tests/src/python/test_qgsextentwidget.py b/tests/src/python/test_qgsextentwidget.py index a591741f111..2e35cca534e 100644 --- a/tests/src/python/test_qgsextentwidget.py +++ b/tests/src/python/test_qgsextentwidget.py @@ -20,7 +20,10 @@ from qgis.core import ( QgsRectangle, QgsVectorLayer, ) -from qgis.gui import QgsExtentWidget +from qgis.gui import ( + QgsExtentWidget, + QgsExtentGroupBox +) import unittest from qgis.testing import start_app, QgisTestCase @@ -112,7 +115,7 @@ class TestQgsExtentWidget(QgisTestCase): self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20)) # repeat, this time using original extents - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326')) w.setOriginalExtent(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326')) @@ -148,7 +151,7 @@ class TestQgsExtentWidget(QgisTestCase): self.assertEqual(w.outputExtent().toString(20), QgsRectangle(1, 2, 3, 4).toString(20)) # custom extent - w = qgis.gui.QgsExtentGroupBox() + w = QgsExtentGroupBox() w.setOutputCrs(QgsCoordinateReferenceSystem('epsg:4326')) w.setOutputExtentFromUser(QgsRectangle(1, 2, 3, 4), QgsCoordinateReferenceSystem('epsg:4326')) diff --git a/tests/src/python/test_qgsfilterlineedit.py b/tests/src/python/test_qgsfilterlineedit.py index 3fc014ab194..0681725bfc0 100644 --- a/tests/src/python/test_qgsfilterlineedit.py +++ b/tests/src/python/test_qgsfilterlineedit.py @@ -12,11 +12,7 @@ __copyright__ = 'Copyright 2016, The QGIS Project' from qgis.gui import QgsFilterLineEdit -try: - from qgis.PyQt.QtTest import QSignalSpy - use_signal_spy = True -except: - use_signal_spy = False +from qgis.PyQt.QtTest import QSignalSpy import unittest from qgis.testing import start_app, QgisTestCase @@ -28,7 +24,7 @@ class TestQgsFilterLineEdit(QgisTestCase): def testGettersSetters(self): """ test widget getters/setters """ - w = qgis.gui.QgsFilterLineEdit() + w = QgsFilterLineEdit() w.setNullValue('null') self.assertEqual(w.nullValue(), 'null') @@ -46,7 +42,7 @@ class TestQgsFilterLineEdit(QgisTestCase): def testNullValueHandling(self): """ test widget handling of null values """ - w = qgis.gui.QgsFilterLineEdit() + w = QgsFilterLineEdit() # start with no null value w.setValue(None) @@ -77,7 +73,7 @@ class TestQgsFilterLineEdit(QgisTestCase): def testClearToNull(self): """ test clearing widget """ - w = qgis.gui.QgsFilterLineEdit() + w = QgsFilterLineEdit() w.setValue('abc') w.clearValue() @@ -97,7 +93,7 @@ class TestQgsFilterLineEdit(QgisTestCase): def testClearToDefault(self): # test clearing to default value - w = qgis.gui.QgsFilterLineEdit() + w = QgsFilterLineEdit() w.setClearMode(QgsFilterLineEdit.ClearToDefault) w.setValue('abc') @@ -118,7 +114,7 @@ class TestQgsFilterLineEdit(QgisTestCase): def test_selectedText(self): """ test that NULL value is selected on focus and not-null value is not""" - w = qgis.gui.QgsFilterLineEdit(nullValue='my_null_value') + w = QgsFilterLineEdit(nullValue='my_null_value') w.clearValue() self.assertEqual(w.selectedText(), 'my_null_value') @@ -128,11 +124,10 @@ class TestQgsFilterLineEdit(QgisTestCase): w.clearValue() self.assertEqual(w.selectedText(), 'my_null_value') - @unittest.skipIf(not use_signal_spy, "No QSignalSpy available") def test_ChangedSignals(self): """ test that signals are correctly emitted when clearing""" - w = qgis.gui.QgsFilterLineEdit() + w = QgsFilterLineEdit() cleared_spy = QSignalSpy(w.cleared) w.setValue('1') diff --git a/tests/src/python/test_qgsfloatingwidget.py b/tests/src/python/test_qgsfloatingwidget.py index 36be92ec2ad..e39e83fad5f 100644 --- a/tests/src/python/test_qgsfloatingwidget.py +++ b/tests/src/python/test_qgsfloatingwidget.py @@ -40,7 +40,7 @@ class TestQgsFloatingWidget(QgisTestCase): main_frame.setAttribute(103) main_frame.show() - fw = qgis.gui.QgsFloatingWidget(main_frame) + fw = QgsFloatingWidget(main_frame) fw.setMinimumSize(100, 50) fw.setAnchorWidget(anchor_widget) @@ -89,7 +89,7 @@ class TestQgsFloatingWidget(QgisTestCase): main_frame.setAttribute(103) main_frame.show() - fw = qgis.gui.QgsFloatingWidget(main_frame) + fw = QgsFloatingWidget(main_frame) fw.setMinimumSize(100, 50) fw.setAnchorWidget(anchor_widget) @@ -133,7 +133,7 @@ class TestQgsFloatingWidget(QgisTestCase): main_frame.setAttribute(103) main_frame.show() - fw = qgis.gui.QgsFloatingWidget(main_frame) + fw = QgsFloatingWidget(main_frame) fw.setMinimumSize(100, 50) fw.setAnchorWidget(anchor_widget) @@ -171,7 +171,7 @@ class TestQgsFloatingWidget(QgisTestCase): main_frame.setAttribute(103) main_frame.show() - fw = qgis.gui.QgsFloatingWidget(main_frame) + fw = QgsFloatingWidget(main_frame) fw.setMinimumSize(300, 50) fw.setAnchorWidget(anchor_widget) diff --git a/tests/src/python/test_qgslayoutitempropertiesdialog.py b/tests/src/python/test_qgslayoutitempropertiesdialog.py index 1b14baff0db..fcf2c6e7e49 100644 --- a/tests/src/python/test_qgslayoutitempropertiesdialog.py +++ b/tests/src/python/test_qgslayoutitempropertiesdialog.py @@ -9,6 +9,7 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' +import unittest from qgis.core import ( QgsLayout, @@ -18,7 +19,7 @@ from qgis.core import ( QgsProject, QgsUnitTypes, ) -import unittest +from qgis.gui import QgsLayoutItemPropertiesDialog from qgis.testing import start_app, QgisTestCase start_app() @@ -28,7 +29,7 @@ class TestQgsLayoutItemPropertiesDialog(QgisTestCase): def testGettersSetters(self): """ test dialog getters/setters """ - dlg = qgis.gui.QgsLayoutItemPropertiesDialog() + dlg = QgsLayoutItemPropertiesDialog() l = QgsLayout(QgsProject.instance()) l.initializeDefaults() diff --git a/tests/src/python/test_qgslayoutunitscombobox.py b/tests/src/python/test_qgslayoutunitscombobox.py index c70c4fda431..e78e99d4901 100644 --- a/tests/src/python/test_qgslayoutunitscombobox.py +++ b/tests/src/python/test_qgslayoutunitscombobox.py @@ -11,7 +11,11 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtWidgets import QDoubleSpinBox -from qgis.core import QgsLayoutMeasurementConverter, QgsUnitTypes +from qgis.core import ( + QgsLayoutMeasurementConverter, + QgsUnitTypes +) +from qgis.gui import QgsLayoutUnitsComboBox import unittest from qgis.testing import start_app, QgisTestCase @@ -22,14 +26,14 @@ class TestQgsLayoutUnitsComboBox(QgisTestCase): def testGettersSetters(self): """ test widget getters/setters """ - w = qgis.gui.QgsLayoutUnitsComboBox() + w = QgsLayoutUnitsComboBox() w.setUnit(QgsUnitTypes.LayoutPixels) self.assertEqual(w.unit(), QgsUnitTypes.LayoutPixels) def test_ChangedSignals(self): """ test that signals are correctly emitted when setting unit""" - w = qgis.gui.QgsLayoutUnitsComboBox() + w = QgsLayoutUnitsComboBox() spy = QSignalSpy(w.changed) w.setUnit(QgsUnitTypes.LayoutPixels) @@ -39,7 +43,7 @@ class TestQgsLayoutUnitsComboBox(QgisTestCase): def testLinkedWidgets(self): """ test linking spin boxes to combobox""" - w = qgis.gui.QgsLayoutUnitsComboBox() + w = QgsLayoutUnitsComboBox() self.assertFalse(w.converter()) c = QgsLayoutMeasurementConverter() w.setConverter(c) diff --git a/tests/src/python/test_qgsopacitywidget.py b/tests/src/python/test_qgsopacitywidget.py index 80ce77f7919..222f9d2c088 100644 --- a/tests/src/python/test_qgsopacitywidget.py +++ b/tests/src/python/test_qgsopacitywidget.py @@ -11,6 +11,7 @@ __copyright__ = 'Copyright 2017, The QGIS Project' from qgis.PyQt.QtTest import QSignalSpy +from qgis.gui import QgsOpacityWidget import unittest from qgis.testing import start_app, QgisTestCase @@ -21,7 +22,7 @@ class TestQgsOpacityWidget(QgisTestCase): def testGettersSetters(self): """ test widget getters/setters """ - w = qgis.gui.QgsOpacityWidget() + w = QgsOpacityWidget() w.setOpacity(0.2) self.assertEqual(w.opacity(), 0.2) @@ -35,7 +36,7 @@ class TestQgsOpacityWidget(QgisTestCase): def test_ChangedSignals(self): """ test that signals are correctly emitted when setting opacity""" - w = qgis.gui.QgsOpacityWidget() + w = QgsOpacityWidget() spy = QSignalSpy(w.opacityChanged) w.setOpacity(0.2) diff --git a/tests/src/python/test_qgsratiolockbutton.py b/tests/src/python/test_qgsratiolockbutton.py index 285a63006db..37866904c6e 100644 --- a/tests/src/python/test_qgsratiolockbutton.py +++ b/tests/src/python/test_qgsratiolockbutton.py @@ -9,9 +9,11 @@ __author__ = 'Nyall Dawson' __date__ = '18/07/2017' __copyright__ = 'Copyright 2017, The QGIS Project' +import unittest from qgis.PyQt.QtWidgets import QDoubleSpinBox -import unittest + +from qgis.gui import QgsRatioLockButton from qgis.testing import start_app, QgisTestCase start_app() @@ -21,7 +23,7 @@ class TestQgsRatioLockButton(QgisTestCase): def testLinkedWidgets(self): """ test linking spin boxes to combobox""" - w = qgis.gui.QgsRatioLockButton() + w = QgsRatioLockButton() spin_width = QDoubleSpinBox() spin_width.setMaximum(100000) @@ -94,7 +96,7 @@ class TestQgsRatioLockButton(QgisTestCase): self.assertEqual(spin_height.value(), 1000) def testResetRatio(self): - w = qgis.gui.QgsRatioLockButton() + w = QgsRatioLockButton() spin_width = QDoubleSpinBox() spin_width.setMaximum(100000) From e9cbd075b79e8938509c9d77eaa90e731a2cfea4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 24 Nov 2023 07:39:52 +1000 Subject: [PATCH 91/97] Fix sip import --- tests/src/python/test_qgsmaplayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/python/test_qgsmaplayer.py b/tests/src/python/test_qgsmaplayer.py index e11ebcece73..05d3c1d04a2 100644 --- a/tests/src/python/test_qgsmaplayer.py +++ b/tests/src/python/test_qgsmaplayer.py @@ -14,7 +14,7 @@ import os import shutil import tempfile -import sip +from qgis.PyQt import sip from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( From 98c19b38ac93bf9f7d8011facd1ecdbe786788e9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 24 Nov 2023 07:44:47 +1000 Subject: [PATCH 92/97] Cleanup qgis.utils.home_plugin_path --- python/pyplugin_installer/installer.py | 23 +++++++++++++------ python/pyplugin_installer/installer_data.py | 8 +++++-- .../qgsplugininstallerinstallingdialog.py | 4 ++-- python/utils.py | 3 +++ src/python/qgspythonutilsimpl.cpp | 2 +- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/python/pyplugin_installer/installer.py b/python/pyplugin_installer/installer.py index 043ad2e089f..804166fa9c6 100644 --- a/python/pyplugin_installer/installer.py +++ b/python/pyplugin_installer/installer.py @@ -44,9 +44,18 @@ from qgis.PyQt.QtNetwork import QNetworkRequest from qgis.core import Qgis, QgsApplication, QgsMessageLog, QgsNetworkAccessManager, QgsSettings, QgsSettingsTree, QgsNetworkRequestParameters from qgis.gui import QgsMessageBar, QgsPasswordLineEdit, QgsHelp -from qgis.utils import (iface, startPlugin, unloadPlugin, loadPlugin, OverrideCursor, - reloadPlugin, updateAvailablePlugins, plugins_metadata_parser, isPluginLoaded) -from qgis import utils +from qgis.utils import ( + iface, + startPlugin, + unloadPlugin, + loadPlugin, + OverrideCursor, + reloadPlugin, + updateAvailablePlugins, + plugins_metadata_parser, + isPluginLoaded, + HOME_PLUGIN_PATH +) from .installer_data import (repositories, plugins, officialRepo, reposGroup, removeDir) from .qgsplugininstallerinstallingdialog import QgsPluginInstallerInstallingDialog @@ -323,7 +332,7 @@ class QgsPluginInstaller(QObject): dlg = QgsPluginInstallerInstallingDialog(iface.mainWindow(), plugin, stable=stable) dlg.exec_() - plugin_path = utils.home_plugin_path + "/" + key + plugin_path = HOME_PLUGIN_PATH + "/" + key if dlg.result(): error = True infoString = (self.tr("Plugin installation failed"), dlg.result()) @@ -387,7 +396,7 @@ class QgsPluginInstaller(QObject): dlg.exec_() if dlg.result(): # revert installation - pluginDir = utils.home_plugin_path + "/" + plugin["id"] + pluginDir = HOME_PLUGIN_PATH + "/" + plugin["id"] result = removeDir(pluginDir) if QDir(pluginDir).exists(): error = True @@ -436,7 +445,7 @@ class QgsPluginInstaller(QObject): unloadPlugin(key) except: pass - pluginDir = utils.home_plugin_path + "/" + plugin["id"] + pluginDir = HOME_PLUGIN_PATH + "/" + plugin["id"] result = removeDir(pluginDir) if result: QApplication.restoreOverrideCursor() @@ -612,7 +621,7 @@ class QgsPluginInstaller(QObject): QgsHelp.openHelp("plugins/plugins.html#the-install-from-zip-tab") return - pluginsDirectory = utils.home_plugin_path + pluginsDirectory = HOME_PLUGIN_PATH if not QDir(pluginsDirectory).exists(): QDir().mkpath(pluginsDirectory) diff --git a/python/pyplugin_installer/installer_data.py b/python/pyplugin_installer/installer_data.py index a8af38edda8..32a0c275f61 100644 --- a/python/pyplugin_installer/installer_data.py +++ b/python/pyplugin_installer/installer_data.py @@ -42,7 +42,11 @@ import configparser import qgis.utils from qgis.core import QgsNetworkAccessManager, QgsApplication from qgis.gui import QgsGui -from qgis.utils import iface, plugin_paths +from qgis.utils import ( + iface, + plugin_paths, + HOME_PLUGIN_PATH +) from .version_compare import pyQgisVersion, compareVersions, normalizeVersion, isCompatible @@ -133,7 +137,7 @@ def removeDir(path): if QFile(path).exists(): result = QCoreApplication.translate("QgsPluginInstaller", "Failed to remove the directory:") + "\n" + path + "\n" + QCoreApplication.translate("QgsPluginInstaller", "Check permissions or remove it manually") # restore plugin directory if removed by QDir().rmpath() - pluginDir = qgis.utils.home_plugin_path + pluginDir = HOME_PLUGIN_PATH if not QDir(pluginDir).exists(): QDir().mkpath(pluginDir) return result diff --git a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py index deaf5144bf4..b10325a59ad 100644 --- a/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py +++ b/python/pyplugin_installer/qgsplugininstallerinstallingdialog.py @@ -30,7 +30,7 @@ from qgis.PyQt.QtWidgets import QDialog from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply from qgis.core import QgsNetworkAccessManager, QgsApplication, QgsNetworkRequestParameters -from qgis import utils +from qgis.utils import HOME_PLUGIN_PATH from .ui_qgsplugininstallerinstallingbase import Ui_QgsPluginInstallerInstallingDialogBase from .installer_data import removeDir, repositories @@ -142,7 +142,7 @@ class QgsPluginInstallerInstallingDialog(QDialog, Ui_QgsPluginInstallerInstallin self.file.close() self.stateChanged(0) reply.deleteLater() - pluginDir = utils.home_plugin_path + pluginDir = HOME_PLUGIN_PATH tmpPath = self.file.fileName() # make sure that the parent directory exists if not QDir(pluginDir).exists(): diff --git a/python/utils.py b/python/utils.py index 6cf04ed388a..9ba00379cd6 100644 --- a/python/utils.py +++ b/python/utils.py @@ -222,6 +222,9 @@ def initInterface(pointer): ####################### # PLUGINS +# The current path for home directory Python plugins. +HOME_PLUGIN_PATH: Optional[str] = None + # list of plugin paths. it gets filled in by the QGIS python library plugin_paths = [] diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp index dc565175475..c0c99760a7c 100644 --- a/src/python/qgspythonutilsimpl.cpp +++ b/src/python/qgspythonutilsimpl.cpp @@ -134,7 +134,7 @@ bool QgsPythonUtilsImpl::checkSystemImports() // tell the utils script where to look for the plugins runString( QStringLiteral( "qgis.utils.plugin_paths = [%1]" ).arg( pluginpaths.join( ',' ) ) ); runString( QStringLiteral( "qgis.utils.sys_plugin_path = \"%1\"" ).arg( pluginsPath() ) ); - runString( QStringLiteral( "qgis.utils.home_plugin_path = %1" ).arg( homePluginsPath() ) ); // note - homePluginsPath() returns a python expression, not a string literal + runString( QStringLiteral( "qgis.utils.HOME_PLUGIN_PATH = %1" ).arg( homePluginsPath() ) ); // note - homePluginsPath() returns a python expression, not a string literal #ifdef Q_OS_WIN runString( "if oldhome: os.environ['HOME']=oldhome\n" ); From 0042744c4526c65b141909d17d28fe6f6809b986 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 15 Nov 2023 11:47:38 +1000 Subject: [PATCH 93/97] Add help page keys for more settings tabs --- src/app/3d/qgs3doptions.cpp | 6 ++++++ src/app/3d/qgs3doptions.h | 2 +- src/app/options/qgsadvancedoptions.cpp | 5 +++++ src/app/options/qgsadvancedoptions.h | 1 + src/app/options/qgscustomprojectionoptions.cpp | 2 +- src/app/options/qgselevationoptions.cpp | 5 +++++ src/app/options/qgselevationoptions.h | 2 +- src/app/options/qgsfontoptions.cpp | 5 +++++ src/app/options/qgsfontoptions.h | 2 +- src/app/options/qgsgpsdeviceoptions.cpp | 5 +++++ src/app/options/qgsgpsdeviceoptions.h | 2 +- src/app/options/qgsgpsoptions.cpp | 5 +++++ src/app/options/qgsgpsoptions.h | 2 +- src/app/options/qgsrasterrenderingoptions.cpp | 5 +++++ src/app/options/qgsrasterrenderingoptions.h | 2 +- src/app/options/qgsrenderingoptions.cpp | 5 +++++ src/app/options/qgsrenderingoptions.h | 2 +- src/app/options/qgsvectorrenderingoptions.cpp | 5 +++++ src/app/options/qgsvectorrenderingoptions.h | 2 +- 19 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/app/3d/qgs3doptions.cpp b/src/app/3d/qgs3doptions.cpp index 0582c16aba0..ba0e79f6dad 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -60,6 +60,12 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent ) mGpuMemoryLimit->setValue( settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble() ); } +QString Qgs3DOptionsWidget::helpKey() const +{ + // typo IS correct here! + return QStringLiteral( "introduction/qgis_configuration.html#d-settings" ); +} + void Qgs3DOptionsWidget::apply() { QgsSettings settings; diff --git a/src/app/3d/qgs3doptions.h b/src/app/3d/qgs3doptions.h index 6a036d55444..e23ff691367 100644 --- a/src/app/3d/qgs3doptions.h +++ b/src/app/3d/qgs3doptions.h @@ -36,7 +36,7 @@ class Qgs3DOptionsWidget : public QgsOptionsPageWidget, private Ui::Qgs3DOptions * Constructor for Qgs3DOptionsWidget with the specified \a parent widget. */ Qgs3DOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; }; diff --git a/src/app/options/qgsadvancedoptions.cpp b/src/app/options/qgsadvancedoptions.cpp index 1e4b650e3b1..19f85717f1f 100644 --- a/src/app/options/qgsadvancedoptions.cpp +++ b/src/app/options/qgsadvancedoptions.cpp @@ -61,6 +61,11 @@ QgsAdvancedSettingsWidget::~QgsAdvancedSettingsWidget() { } +QString QgsAdvancedSettingsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#advanced-settings" ); +} + void QgsAdvancedSettingsWidget::apply() { // the old settings editor applies changes immediately diff --git a/src/app/options/qgsadvancedoptions.h b/src/app/options/qgsadvancedoptions.h index 856c7b43cb4..a10f0b3bae3 100644 --- a/src/app/options/qgsadvancedoptions.h +++ b/src/app/options/qgsadvancedoptions.h @@ -46,6 +46,7 @@ class QgsAdvancedSettingsWidget : public QgsOptionsPageWidget, private Ui::QgsAd */ QgsAdvancedSettingsWidget( QWidget *parent ); ~QgsAdvancedSettingsWidget() override; + QString helpKey() const override; void apply() override; private: diff --git a/src/app/options/qgscustomprojectionoptions.cpp b/src/app/options/qgscustomprojectionoptions.cpp index 60963360d42..9813f48e905 100644 --- a/src/app/options/qgscustomprojectionoptions.cpp +++ b/src/app/options/qgscustomprojectionoptions.cpp @@ -420,7 +420,7 @@ QString QgsCustomProjectionOptionsWidget::multiLineWktToSingleLine( const QStrin QString QgsCustomProjectionOptionsWidget::helpKey() const { - return QStringLiteral( "working_with_projections/working_with_projections.html" ); + return QStringLiteral( "introduction/qgis_configuration.html#user-defined-crs" ); } diff --git a/src/app/options/qgselevationoptions.cpp b/src/app/options/qgselevationoptions.cpp index f85886fa66c..960d57761e7 100644 --- a/src/app/options/qgselevationoptions.cpp +++ b/src/app/options/qgselevationoptions.cpp @@ -38,6 +38,11 @@ QgsElevationOptionsWidget::QgsElevationOptionsWidget( QWidget *parent ) mButtonBackgroundColor->setToNull(); } +QString QgsElevationOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#elevation-settings" ); +} + void QgsElevationOptionsWidget::apply() { QgsElevationProfileWidget::settingBackgroundColor->setValue( diff --git a/src/app/options/qgselevationoptions.h b/src/app/options/qgselevationoptions.h index 98e1dfdbf80..8b6d10c04a1 100644 --- a/src/app/options/qgselevationoptions.h +++ b/src/app/options/qgselevationoptions.h @@ -35,7 +35,7 @@ class QgsElevationOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsEl * Constructor for QgsElevationOptionsWidget with the specified \a parent widget. */ QgsElevationOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; }; diff --git a/src/app/options/qgsfontoptions.cpp b/src/app/options/qgsfontoptions.cpp index 8766f100f80..4acd0741f1f 100644 --- a/src/app/options/qgsfontoptions.cpp +++ b/src/app/options/qgsfontoptions.cpp @@ -104,6 +104,11 @@ QgsFontOptionsWidget::QgsFontOptionsWidget( QWidget *parent ) } +QString QgsFontOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#fonts-settings" ); +} + void QgsFontOptionsWidget::apply() { QMap< QString, QString > replacements; diff --git a/src/app/options/qgsfontoptions.h b/src/app/options/qgsfontoptions.h index 9425be37a86..efea9efdf7c 100644 --- a/src/app/options/qgsfontoptions.h +++ b/src/app/options/qgsfontoptions.h @@ -36,7 +36,7 @@ class QgsFontOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsFontOpt * Constructor for QgsFontOptionsWidget with the specified \a parent widget. */ QgsFontOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; }; diff --git a/src/app/options/qgsgpsdeviceoptions.cpp b/src/app/options/qgsgpsdeviceoptions.cpp index 9c10c47ccd3..260817f8c20 100644 --- a/src/app/options/qgsgpsdeviceoptions.cpp +++ b/src/app/options/qgsgpsdeviceoptions.cpp @@ -89,6 +89,11 @@ QgsGpsDeviceOptionsWidget::QgsGpsDeviceOptionsWidget( QWidget *parent ) mGpsBabelFileWidget->setFilePath( QgsSettingsRegistryCore::settingsGpsBabelPath->value() ); } +QString QgsGpsDeviceOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#gpsbabel" ); +} + void QgsGpsDeviceOptionsWidget::apply() { QgsBabelFormatRegistry::sTreeBabelDevices->deleteAllItems(); diff --git a/src/app/options/qgsgpsdeviceoptions.h b/src/app/options/qgsgpsdeviceoptions.h index e5a5c193ee3..9882b48f5df 100644 --- a/src/app/options/qgsgpsdeviceoptions.h +++ b/src/app/options/qgsgpsdeviceoptions.h @@ -38,7 +38,7 @@ class QgsGpsDeviceOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsGp * Constructor for QgsGpsDeviceOptionsWidget with the specified \a parent widget. */ QgsGpsDeviceOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private slots: diff --git a/src/app/options/qgsgpsoptions.cpp b/src/app/options/qgsgpsoptions.cpp index cea965fe8d9..e0077f00ee9 100644 --- a/src/app/options/qgsgpsoptions.cpp +++ b/src/app/options/qgsgpsoptions.cpp @@ -313,6 +313,11 @@ QgsGpsOptionsWidget::QgsGpsOptionsWidget( QWidget *parent ) refreshDevices(); } +QString QgsGpsOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#gps-settings" ); +} + void QgsGpsOptionsWidget::apply() { if ( QgsSymbol *markerSymbol = mGpsMarkerSymbolButton->symbol() ) diff --git a/src/app/options/qgsgpsoptions.h b/src/app/options/qgsgpsoptions.h index f758040a5cb..c8d642525e3 100644 --- a/src/app/options/qgsgpsoptions.h +++ b/src/app/options/qgsgpsoptions.h @@ -36,7 +36,7 @@ class APP_EXPORT QgsGpsOptionsWidget : public QgsOptionsPageWidget, private Ui:: * Constructor for QgsGpsOptionsWidget with the specified \a parent widget. */ QgsGpsOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private slots: diff --git a/src/app/options/qgsrasterrenderingoptions.cpp b/src/app/options/qgsrasterrenderingoptions.cpp index 174d2909e54..503f8a2da39 100644 --- a/src/app/options/qgsrasterrenderingoptions.cpp +++ b/src/app/options/qgsrasterrenderingoptions.cpp @@ -78,6 +78,11 @@ QgsRasterRenderingOptionsWidget::QgsRasterRenderingOptionsWidget( QWidget *paren spnThreeBandStdDev->setClearValue( QgsRasterMinMaxOrigin::DEFAULT_STDDEV_FACTOR ); } +QString QgsRasterRenderingOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#raster-rendering-settings" ); +} + void QgsRasterRenderingOptionsWidget::apply() { QgsSettings settings; diff --git a/src/app/options/qgsrasterrenderingoptions.h b/src/app/options/qgsrasterrenderingoptions.h index 1adfc77762e..ec6a807efe5 100644 --- a/src/app/options/qgsrasterrenderingoptions.h +++ b/src/app/options/qgsrasterrenderingoptions.h @@ -25,7 +25,7 @@ class QgsRasterRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui: public: QgsRasterRenderingOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private: bool mBlockStoringChanges = false; diff --git a/src/app/options/qgsrenderingoptions.cpp b/src/app/options/qgsrenderingoptions.cpp index 8bc5f00188b..1197c4425b5 100644 --- a/src/app/options/qgsrenderingoptions.cpp +++ b/src/app/options/qgsrenderingoptions.cpp @@ -55,6 +55,11 @@ QgsRenderingOptionsWidget::QgsRenderingOptionsWidget( QWidget *parent ) chkAntiAliasing->setChecked( settings.value( QStringLiteral( "/qgis/enable_anti_aliasing" ), true ).toBool() ); } +QString QgsRenderingOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#rendering-settings" ); +} + void QgsRenderingOptionsWidget::apply() { QgsSettings settings; diff --git a/src/app/options/qgsrenderingoptions.h b/src/app/options/qgsrenderingoptions.h index 60425d007fc..4eb73d7bfae 100644 --- a/src/app/options/qgsrenderingoptions.h +++ b/src/app/options/qgsrenderingoptions.h @@ -28,7 +28,7 @@ class QgsRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsRe public: QgsRenderingOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private: bool mBlockStoringChanges = false; diff --git a/src/app/options/qgsvectorrenderingoptions.cpp b/src/app/options/qgsvectorrenderingoptions.cpp index 42e5975e0ba..d680e72c0f6 100644 --- a/src/app/options/qgsvectorrenderingoptions.cpp +++ b/src/app/options/qgsvectorrenderingoptions.cpp @@ -68,6 +68,11 @@ QgsVectorRenderingOptionsWidget::QgsVectorRenderingOptionsWidget( QWidget *paren mSimplifyAlgorithmComboBox->setCurrentIndex( mSimplifyAlgorithmComboBox->findData( QgsVectorLayer::settingsSimplifyAlgorithm->value() ) ); } +QString QgsVectorRenderingOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#vector-rendering-settings" ); +} + void QgsVectorRenderingOptionsWidget::apply() { QgsSettings settings; diff --git a/src/app/options/qgsvectorrenderingoptions.h b/src/app/options/qgsvectorrenderingoptions.h index 88d3676755b..910bddb78fb 100644 --- a/src/app/options/qgsvectorrenderingoptions.h +++ b/src/app/options/qgsvectorrenderingoptions.h @@ -25,7 +25,7 @@ class QgsVectorRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui: public: QgsVectorRenderingOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private: bool mBlockStoringChanges = false; From e9ff06ee05d5c2999da70e1dbb326a219f4dd192 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Mon, 20 Nov 2023 15:07:26 +0100 Subject: [PATCH 94/97] Use links to internal anchors instead of user visible title --- src/app/3d/qgs3doptions.cpp | 2 +- src/app/options/qgsadvancedoptions.cpp | 4 ++-- src/app/options/qgscodeeditoroptions.cpp | 2 +- src/app/options/qgselevationoptions.cpp | 2 +- src/app/options/qgsfontoptions.cpp | 2 +- src/app/options/qgsgpsdeviceoptions.cpp | 2 +- src/app/options/qgsgpsoptions.cpp | 2 +- src/app/options/qgsrasterrenderingoptions.cpp | 2 +- src/app/options/qgsrenderingoptions.cpp | 2 +- src/app/options/qgsvectorrenderingoptions.cpp | 2 +- src/app/qgscustomization.cpp | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/3d/qgs3doptions.cpp b/src/app/3d/qgs3doptions.cpp index ba0e79f6dad..e2e4097bdba 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -63,7 +63,7 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent ) QString Qgs3DOptionsWidget::helpKey() const { // typo IS correct here! - return QStringLiteral( "introduction/qgis_configuration.html#d-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#d-options" ); } void Qgs3DOptionsWidget::apply() diff --git a/src/app/options/qgsadvancedoptions.cpp b/src/app/options/qgsadvancedoptions.cpp index 19f85717f1f..c7d087b5ed5 100644 --- a/src/app/options/qgsadvancedoptions.cpp +++ b/src/app/options/qgsadvancedoptions.cpp @@ -63,12 +63,12 @@ QgsAdvancedSettingsWidget::~QgsAdvancedSettingsWidget() QString QgsAdvancedSettingsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#advanced-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#optionsadvanced" ); } void QgsAdvancedSettingsWidget::apply() { - // the old settings editor applies changes immediately + // the old settings editor applies changes immediately // new settings tree is performing changes on apply if ( mTreeWidget ) mTreeWidget->applyChanges(); diff --git a/src/app/options/qgscodeeditoroptions.cpp b/src/app/options/qgscodeeditoroptions.cpp index d3511995dad..4a6de3f5fa6 100644 --- a/src/app/options/qgscodeeditoroptions.cpp +++ b/src/app/options/qgscodeeditoroptions.cpp @@ -358,7 +358,7 @@ QgsCodeEditorOptionsWidget::~QgsCodeEditorOptionsWidget() QString QgsCodeEditorOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#code-editor-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#code-editor-options" ); } void QgsCodeEditorOptionsWidget::apply() diff --git a/src/app/options/qgselevationoptions.cpp b/src/app/options/qgselevationoptions.cpp index 960d57761e7..c028848c959 100644 --- a/src/app/options/qgselevationoptions.cpp +++ b/src/app/options/qgselevationoptions.cpp @@ -40,7 +40,7 @@ QgsElevationOptionsWidget::QgsElevationOptionsWidget( QWidget *parent ) QString QgsElevationOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#elevation-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#elevation-options" ); } void QgsElevationOptionsWidget::apply() diff --git a/src/app/options/qgsfontoptions.cpp b/src/app/options/qgsfontoptions.cpp index 4acd0741f1f..d8d8431b0a1 100644 --- a/src/app/options/qgsfontoptions.cpp +++ b/src/app/options/qgsfontoptions.cpp @@ -106,7 +106,7 @@ QgsFontOptionsWidget::QgsFontOptionsWidget( QWidget *parent ) QString QgsFontOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#fonts-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#fonts-options" ); } void QgsFontOptionsWidget::apply() diff --git a/src/app/options/qgsgpsdeviceoptions.cpp b/src/app/options/qgsgpsdeviceoptions.cpp index 260817f8c20..b87a1a0ab81 100644 --- a/src/app/options/qgsgpsdeviceoptions.cpp +++ b/src/app/options/qgsgpsdeviceoptions.cpp @@ -91,7 +91,7 @@ QgsGpsDeviceOptionsWidget::QgsGpsDeviceOptionsWidget( QWidget *parent ) QString QgsGpsDeviceOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#gpsbabel" ); + return QStringLiteral( "introduction/qgis_configuration.html#defining-new-device" ); } void QgsGpsDeviceOptionsWidget::apply() diff --git a/src/app/options/qgsgpsoptions.cpp b/src/app/options/qgsgpsoptions.cpp index e0077f00ee9..888e15995d5 100644 --- a/src/app/options/qgsgpsoptions.cpp +++ b/src/app/options/qgsgpsoptions.cpp @@ -315,7 +315,7 @@ QgsGpsOptionsWidget::QgsGpsOptionsWidget( QWidget *parent ) QString QgsGpsOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#gps-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#gps-options" ); } void QgsGpsOptionsWidget::apply() diff --git a/src/app/options/qgsrasterrenderingoptions.cpp b/src/app/options/qgsrasterrenderingoptions.cpp index 503f8a2da39..cb3ecb48940 100644 --- a/src/app/options/qgsrasterrenderingoptions.cpp +++ b/src/app/options/qgsrasterrenderingoptions.cpp @@ -80,7 +80,7 @@ QgsRasterRenderingOptionsWidget::QgsRasterRenderingOptionsWidget( QWidget *paren QString QgsRasterRenderingOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#raster-rendering-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#raster-rendering-options" ); } void QgsRasterRenderingOptionsWidget::apply() diff --git a/src/app/options/qgsrenderingoptions.cpp b/src/app/options/qgsrenderingoptions.cpp index 1197c4425b5..bad9ca30d11 100644 --- a/src/app/options/qgsrenderingoptions.cpp +++ b/src/app/options/qgsrenderingoptions.cpp @@ -57,7 +57,7 @@ QgsRenderingOptionsWidget::QgsRenderingOptionsWidget( QWidget *parent ) QString QgsRenderingOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#rendering-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#rendering-options" ); } void QgsRenderingOptionsWidget::apply() diff --git a/src/app/options/qgsvectorrenderingoptions.cpp b/src/app/options/qgsvectorrenderingoptions.cpp index d680e72c0f6..4059c2c9d24 100644 --- a/src/app/options/qgsvectorrenderingoptions.cpp +++ b/src/app/options/qgsvectorrenderingoptions.cpp @@ -70,7 +70,7 @@ QgsVectorRenderingOptionsWidget::QgsVectorRenderingOptionsWidget( QWidget *paren QString QgsVectorRenderingOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#vector-rendering-settings" ); + return QStringLiteral( "introduction/qgis_configuration.html#vector-rendering-options" ); } void QgsVectorRenderingOptionsWidget::apply() diff --git a/src/app/qgscustomization.cpp b/src/app/qgscustomization.cpp index b89730cc6df..11ea631f622 100644 --- a/src/app/qgscustomization.cpp +++ b/src/app/qgscustomization.cpp @@ -568,7 +568,7 @@ bool QgsCustomizationDialog::catchOn() void QgsCustomizationDialog::showHelp() { - QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#customization" ) ); + QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#sec-customization" ) ); } From b843f4e83d691dadc523177881f8376954b7ac14 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Mon, 20 Nov 2023 15:08:01 +0100 Subject: [PATCH 95/97] Add more direct links to other settings tabs --- src/app/options/qgsoptions.cpp | 56 ++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/app/options/qgsoptions.cpp b/src/app/options/qgsoptions.cpp index 1d4b298a408..36ae57767b7 100644 --- a/src/app/options/qgsoptions.cpp +++ b/src/app/options/qgsoptions.cpp @@ -2807,13 +2807,65 @@ void QgsOptions::showHelp() { link = QStringLiteral( "introduction/qgis_configuration.html" ); - if ( activeTab == mOptionsPageAuth ) + if ( activeTab == mOptionsPageGeneral ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#general-options" ); + } + else if ( activeTab == mOptionsPageSystem ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#env-options" ); + } + else if ( activeTab == mOptionsPageTransformations ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#transformations-options" ); + } + else if ( activeTab == mOptionsPageDataSources ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#datasources-options" ); + } + else if ( activeTab == mOptionsPageGDAL ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#gdal-options" ); + } + else if ( activeTab == mOptionsPageMapCanvas ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#canvas-legend-options" ); + } + else if ( activeTab == mOptionsPageMapTools ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#maptools-options" ); + } + else if ( activeTab == mOptionsPageDigitizing ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#digitizing-options" ); + } + else if ( activeTab == mOptionsPageColors ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#colors-options" ); + } + else if ( activeTab == mOptionsPageComposer ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#layout-options" ); + } + else if ( activeTab == mOptionsPageNetwork ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#network-options" ); + } + else if ( activeTab == mOptionsLocatorSettings ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#locator-options" ); + } + else if ( activeTab == mOptionsPageAcceleration ) + { + link = QStringLiteral( "introduction/qgis_configuration.html#acceleration-options" ); + } + else if ( activeTab == mOptionsPageAuth ) { link = QStringLiteral( "auth_system/index.html" ); } else if ( activeTab == mOptionsPageVariables ) { - link = QStringLiteral( "introduction/general_tools.html#variables" ); + link = QStringLiteral( "introduction/general_tools.html#general-tools-variables" ); } else if ( activeTab == mOptionsPageCRS ) { From d9f454653f7c7ca5fff352598cfb9d0bb0951cfc Mon Sep 17 00:00:00 2001 From: DelazJ Date: Mon, 20 Nov 2023 14:45:33 +0100 Subject: [PATCH 96/97] Add help link for User profile settings --- src/app/options/qgsuserprofileoptions.cpp | 5 +++++ src/app/options/qgsuserprofileoptions.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/options/qgsuserprofileoptions.cpp b/src/app/options/qgsuserprofileoptions.cpp index 70f47bfaa46..46111501aaf 100644 --- a/src/app/options/qgsuserprofileoptions.cpp +++ b/src/app/options/qgsuserprofileoptions.cpp @@ -80,6 +80,11 @@ QgsUserProfileOptionsWidget::QgsUserProfileOptionsWidget( QWidget *parent ) mActiveProfileIconLabel->setText( tr( "Active Profile (%1) icon", "Active profile icon" ).arg( manager->userProfile()->name() ) ); } +QString QgsUserProfileOptionsWidget::helpKey() const +{ + return QStringLiteral( "introduction/qgis_configuration.html#user-profiles" ); +} + void QgsUserProfileOptionsWidget::apply() { QgsUserProfileManager *manager = QgisApp::instance()->userProfileManager(); diff --git a/src/app/options/qgsuserprofileoptions.h b/src/app/options/qgsuserprofileoptions.h index faa34b5940c..75a994d4740 100644 --- a/src/app/options/qgsuserprofileoptions.h +++ b/src/app/options/qgsuserprofileoptions.h @@ -34,7 +34,7 @@ class APP_EXPORT QgsUserProfileOptionsWidget : public QgsOptionsPageWidget, priv //! Constructor for QgsUserProfileOptionsWidget with the specified \a parent widget. QgsUserProfileOptionsWidget( QWidget *parent ); - + QString helpKey() const override; void apply() override; private slots: From 56b871c816391597cfac227d7c871130235f4e09 Mon Sep 17 00:00:00 2001 From: DelazJ Date: Fri, 24 Nov 2023 11:35:08 +0100 Subject: [PATCH 97/97] Fix more links --- src/app/options/qgscustomprojectionoptions.cpp | 2 +- src/gui/qgsconfigureshortcutsdialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/options/qgscustomprojectionoptions.cpp b/src/app/options/qgscustomprojectionoptions.cpp index 9813f48e905..3d5b8b78334 100644 --- a/src/app/options/qgscustomprojectionoptions.cpp +++ b/src/app/options/qgscustomprojectionoptions.cpp @@ -420,7 +420,7 @@ QString QgsCustomProjectionOptionsWidget::multiLineWktToSingleLine( const QStrin QString QgsCustomProjectionOptionsWidget::helpKey() const { - return QStringLiteral( "introduction/qgis_configuration.html#user-defined-crs" ); + return QStringLiteral( "working_with_projections/working_with_projections" ); } diff --git a/src/gui/qgsconfigureshortcutsdialog.cpp b/src/gui/qgsconfigureshortcutsdialog.cpp index fab30993925..5e85a47fc75 100644 --- a/src/gui/qgsconfigureshortcutsdialog.cpp +++ b/src/gui/qgsconfigureshortcutsdialog.cpp @@ -561,7 +561,7 @@ void QgsConfigureShortcutsDialog::mLeFilter_textChanged( const QString &text ) void QgsConfigureShortcutsDialog::showHelp() { - QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#keyboard-shortcuts" ) ); + QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#shortcuts" ) ); } void QgsConfigureShortcutsDialog::saveShortcutsPdf()