From 8989d5b7d85b2b5c72b8363848b0faa813897b0c Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 15 Sep 2023 08:04:47 +0200 Subject: [PATCH 001/151] Fix build failure --- src/app/vertextool/qgsvertextool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index 0dbc33b57b70..60dfad3aa3b9 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -2440,7 +2440,7 @@ void QgsVertexTool::applyEditsToLayers( QgsVertexTool::VertexEdits &edits ) { QgsFeatureList removedFeatures; double largest = 0; - QgsFeature originalFeature = layer->getFeature( it2.key() ); + QgsFeature originalFeature = layer->getFeature( itFeatEdit.key() ); int largestPartIndex = -1; for ( int i = 0; i < newGeoms.size(); ++i ) { From 089da510818101478b496b8249af968f2434f877 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Fri, 15 Sep 2023 10:04:04 +0200 Subject: [PATCH 002/151] fix translation string --- src/analysis/processing/qgsalgorithmxyztiles.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsalgorithmxyztiles.cpp b/src/analysis/processing/qgsalgorithmxyztiles.cpp index ebf7c07e05d1..86ef9d3a0600 100644 --- a/src/analysis/processing/qgsalgorithmxyztiles.cpp +++ b/src/analysis/processing/qgsalgorithmxyztiles.cpp @@ -346,7 +346,7 @@ QVariantMap QgsXyzTilesDirectoryAlgorithm::processAlgorithm( const QVariantMap & QFile htmlFile( outputHtml ); if ( !htmlFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { - throw QgsProcessingException( QObject::tr( "Could not html file %1" ).arg( outputHtml ) ); + throw QgsProcessingException( QObject::tr( "Could not open html file %1" ).arg( outputHtml ) ); } QTextStream fout( &htmlFile ); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) From adb7f40cfd7eeb7732199713bff85da4f992862f Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 15 Sep 2023 12:15:41 +0200 Subject: [PATCH 003/151] Fix segfault in coordinate transform --- src/core/proj/qgscoordinatetransform.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/proj/qgscoordinatetransform.cpp b/src/core/proj/qgscoordinatetransform.cpp index 9c998ee5f6e7..b78bb2a63494 100644 --- a/src/core/proj/qgscoordinatetransform.cpp +++ b/src/core/proj/qgscoordinatetransform.cpp @@ -666,14 +666,17 @@ QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &r // check if result bbox is geographic and is crossing 180/-180 line: ie. min X is before the 180° and max X is after the -180° bool doHandle180Crossover = false; - double xMin = std::fmod( x[0], 180.0 ); - double xMax = std::fmod( x[nXPoints - 1], 180.0 ); - if ( handle180Crossover - && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) || - ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) ) - && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 ) + if ( nXPoints > 0 && x.size() >= nXPoints ) { - doHandle180Crossover = true; + const double xMin = std::fmod( x[0], 180.0 ); + const double xMax = std::fmod( x[nXPoints - 1], 180.0 ); + if ( handle180Crossover + && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) || + ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) ) + && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 ) + { + doHandle180Crossover = true; + } } // Calculate the bounding box and use that for the extent From 98483645cc36ce1f1ee7e6ced693cc8f062e07ad Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Fri, 15 Sep 2023 16:11:43 +0200 Subject: [PATCH 004/151] Use cmake native FindGSL - this is part of CMake since 3.2 - works with pkgconfig (hence without gsl-config) - properly propagates targets - https://cmake.org/cmake/help/latest/module/FindGSL.html --- cmake/FindGSL.cmake | 138 ------------------------------------ src/analysis/CMakeLists.txt | 6 +- src/app/CMakeLists.txt | 9 --- 3 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 cmake/FindGSL.cmake diff --git a/cmake/FindGSL.cmake b/cmake/FindGSL.cmake deleted file mode 100644 index 28d359f780c5..000000000000 --- a/cmake/FindGSL.cmake +++ /dev/null @@ -1,138 +0,0 @@ -## -## Try to find gnu scientific library GSL -## (see http://www.gnu.org/software/gsl/) -## Once run this will define: -## -## GSL_FOUND = system has GSL lib -## -## GSL_LIBRARIES = full path to the libraries -## on Unix/Linux with additional linker flags from "gsl-config --libs" -## -## CMAKE_GSL_CXX_FLAGS = Unix compiler flags for GSL, essentially "`gsl-config --cxxflags`" -## -## GSL_INCLUDE_DIR = where to find headers -## -## GSL_LINK_DIRECTORIES = link directories, useful for rpath on Unix -## GSL_EXE_LINKER_FLAGS = rpath on Unix -## -## Felix Woelk 07/2004 -## minor corrections Jan Woetzel -## -## www.mip.informatik.uni-kiel.de -## -------------------------------- -## - -IF(WIN32) - FIND_PATH(GSL_INCLUDE_DIR gsl/gsl_blas.h - ${LIB_DIR}/include - $ENV{INCLUDE} - ) - - FIND_LIBRARY(GSL_LIB gsl PATHS - ${LIB_DIR}/lib - $ENV{LIB} - ) - - FIND_LIBRARY(GSLCBLAS_LIB gslcblas cblas PATHS - ${LIB_DIR}/lib - $ENV{LIB} - ) - - SET (GSL_LIBRARIES ${GSL_LIB} ${GSLCBLAS_LIB}) -ELSE(WIN32) - IF(UNIX) - - # try to use framework on mac - # want clean framework path, not unix compatibility path - IF (APPLE) - IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" - OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" - OR NOT CMAKE_FIND_FRAMEWORK) - SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) - SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) - FIND_LIBRARY(GSL_LIBRARIES GSL) - IF (GSL_LIBRARIES) - # they're all the same in a framework - SET (GSL_INCLUDE_DIR ${GSL_LIBRARIES}/Headers CACHE PATH "Path to GSL header files.") - SET (GSL_CONFIG ${GSL_LIBRARIES}/Programs/gsl-config CACHE FILEPATH "Path to gsl-config.") - ENDIF (GSL_LIBRARIES) - SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) - ENDIF () - ENDIF (APPLE) - - IF (NOT GSL_INCLUDE_DIR OR NOT GSL_LIBRARIES OR NOT GSL_CONFIG) - # didn't find OS X framework, or was set by user - SET(GSL_CONFIG_PREFER_PATH "$ENV{GSL_HOME}/bin" CACHE STRING "preferred path to GSL (gsl-config)") - FIND_PROGRAM(GSL_CONFIG gsl-config - ${GSL_CONFIG_PREFER_PATH} - $ENV{LIB_DIR}/bin - /usr/local/bin/ - /usr/bin/ - ) - # MESSAGE("DBG GSL_CONFIG ${GSL_CONFIG}") - - IF (GSL_CONFIG) - # set CXXFLAGS to be fed into CXX_FLAGS by the user: - SET(GSL_CXX_FLAGS "`${GSL_CONFIG} --cflags`") - - # set INCLUDE_DIRS to prefix+include - EXEC_PROGRAM(${GSL_CONFIG} - ARGS --prefix - OUTPUT_VARIABLE GSL_PREFIX) - SET(GSL_INCLUDE_DIR ${GSL_PREFIX}/include CACHE STRING INTERNAL) - - # set link libraries and link flags - EXEC_PROGRAM(${GSL_CONFIG} - ARGS --libs - OUTPUT_VARIABLE GSL_LIBRARIES) - - ## extract link dirs for rpath - EXEC_PROGRAM(${GSL_CONFIG} - ARGS ${LIBS_ARG} - OUTPUT_VARIABLE GSL_CONFIG_LIBS ) - - ## split off the link dirs (for rpath) - ## use regular expression to match wildcard equivalent "-L*" - ## with is a space or a semicolon - STRING(REGEX MATCHALL "[-][L]([^ ;])+" - GSL_LINK_DIRECTORIES_WITH_PREFIX - "${GSL_CONFIG_LIBS}" ) - # MESSAGE("DBG GSL_LINK_DIRECTORIES_WITH_PREFIX=${GSL_LINK_DIRECTORIES_WITH_PREFIX}") - - ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES - - IF (GSL_LINK_DIRECTORIES_WITH_PREFIX) - STRING(REGEX REPLACE "[-][L]" "" GSL_LINK_DIRECTORIES ${GSL_LINK_DIRECTORIES_WITH_PREFIX} ) - ENDIF (GSL_LINK_DIRECTORIES_WITH_PREFIX) - SET(GSL_EXE_LINKER_FLAGS "-Wl,-rpath,${GSL_LINK_DIRECTORIES}" CACHE STRING INTERNAL) - # MESSAGE("DBG GSL_LINK_DIRECTORIES=${GSL_LINK_DIRECTORIES}") - # MESSAGE("DBG GSL_EXE_LINKER_FLAGS=${GSL_EXE_LINKER_FLAGS}") - - # ADD_DEFINITIONS(-DHAVE_GSL) - # SET(GSL_DEFINITIONS "-DHAVE_GSL") - MARK_AS_ADVANCED( - GSL_CXX_FLAGS - GSL_INCLUDE_DIR - GSL_LIBRARIES - GSL_LINK_DIRECTORIES - GSL_DEFINITIONS - ) - - ELSE(GSL_CONFIG) - MESSAGE(FATAL_ERROR "FindGSL.cmake: gsl-config not found. Please install the libgsl development libraries or set the path with -DGSL_CONFIG=/path/to/gsl-config.") - ENDIF(GSL_CONFIG) - ENDIF (NOT GSL_INCLUDE_DIR OR NOT GSL_LIBRARIES OR NOT GSL_CONFIG) - ENDIF(UNIX) -ENDIF(WIN32) - - -IF(GSL_LIBRARIES) - IF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS) - - SET(GSL_FOUND 1) - - MESSAGE(STATUS "Found GSL: ${GSL_LIBRARIES}") - - ENDIF(GSL_INCLUDE_DIR OR GSL_CXX_FLAGS) -ENDIF(GSL_LIBRARIES) - diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 47c8cfdfed62..28b686769ad9 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -464,10 +464,6 @@ include_directories(SYSTEM ${SQLITE3_INCLUDE_DIR}) include_directories(BEFORE raster) include_directories(BEFORE mesh) -if(HAVE_GSL) - include_directories(SYSTEM ${GSL_INCLUDE_DIR}) -endif() - if(NOT MSVC) set_source_files_properties( ${BISON_QgsRasterCalcParser_OUTPUTS} @@ -577,7 +573,7 @@ target_link_libraries( ) if(HAVE_GSL) - target_link_libraries(qgis_analysis ${GSL_LIBRARIES}) + target_link_libraries(qgis_analysis GSL::gsl) endif() if(HAVE_OPENCL) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4d2310a03146..64f2cfd10355 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -599,15 +599,6 @@ if (WITH_3D) target_link_libraries(qgis_app qgis_3d) endif() -if (HAVE_GEOREFERENCER) - include_directories(SYSTEM - ${GSL_INCLUDE_DIR} - ) - target_link_libraries(qgis_app - ${GSL_LIBRARIES} - ) -endif() - GENERATE_EXPORT_HEADER( qgis_app BASE_NAME APP From 0ad16cad09332962d75a00eb71ef3b2278048d0c Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Fri, 15 Sep 2023 09:12:50 +0700 Subject: [PATCH 005/151] [ogr] Fix assert when opening GeoJSON (and other) datasets --- src/core/providers/ogr/qgsogrproviderutils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/providers/ogr/qgsogrproviderutils.cpp b/src/core/providers/ogr/qgsogrproviderutils.cpp index 7b1c515a4643..001f9f812543 100644 --- a/src/core/providers/ogr/qgsogrproviderutils.cpp +++ b/src/core/providers/ogr/qgsogrproviderutils.cpp @@ -1553,7 +1553,7 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName, { if ( !ds->canBeShared ) continue; - Q_ASSERT( ds->refCount > 0 ); + Q_ASSERT( sDeferDatasetClosingCounter > 0 || ds->refCount > 0 ); QString layerName; OGRLayerH hLayer; @@ -2296,7 +2296,7 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName, { if ( !ds->canBeShared ) continue; - Q_ASSERT( ds->refCount > 0 ); + Q_ASSERT( sDeferDatasetClosingCounter > 0 || ds->refCount > 0 ); auto iter2 = ds->setLayers.find( layerName ); if ( iter2 == ds->setLayers.end() ) From 0e938e1dda3c1132d41762c5daea64c29206a71c Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Fri, 15 Sep 2023 17:07:27 +0200 Subject: [PATCH 006/151] Fix MingW64 build: revert f3661ed --- .github/workflows/mingw64.yml | 4 ++-- ms-windows/mingw/mingwdeps.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mingw64.yml b/.github/workflows/mingw64.yml index 5291161d936f..59cbfd71e7cb 100644 --- a/.github/workflows/mingw64.yml +++ b/.github/workflows/mingw64.yml @@ -37,10 +37,10 @@ jobs: run: printf '[main]\ngpgcheck=True\ninstallonly_limit=3\nclean_requirements_on_remove=True\nbest=False\nskip_if_unavailable=True\ntsflags=nodocs' > /etc/dnf/dnf.conf - name: Update system - run: dnf5 -y update + run: dnf -y update - name: Install core dependencies - run: dnf5 -y install zip + run: dnf -y install zip - name: Install build dependencies run: ./ms-windows/mingw/mingwdeps.sh diff --git a/ms-windows/mingw/mingwdeps.sh b/ms-windows/mingw/mingwdeps.sh index c44f71786c65..88f9d352ea82 100755 --- a/ms-windows/mingw/mingwdeps.sh +++ b/ms-windows/mingw/mingwdeps.sh @@ -5,7 +5,7 @@ # just the "tsflags=nodocs" line printf '[main]\ngpgcheck=True\ninstallonly_limit=3\nclean_requirements_on_remove=True\nbest=False\nskip_if_unavailable=True\ntsflags=nodocs' > /etc/dnf/dnf.conf -dnf5 install -y --nogpgcheck \ +dnf install -y --nogpgcheck \ mingw64-dlfcn \ mingw64-exiv2 \ mingw64-fcgi \ From d09ad87ab7c411e8b06231122b7a8d285475547e Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Fri, 15 Sep 2023 17:10:58 +0200 Subject: [PATCH 007/151] MingW64 build: remove workaround Revert 5703f03 --- ms-windows/mingw/build.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ms-windows/mingw/build.sh b/ms-windows/mingw/build.sh index 3e783377165d..fcc030cea927 100755 --- a/ms-windows/mingw/build.sh +++ b/ms-windows/mingw/build.sh @@ -76,10 +76,7 @@ mkdir -p "$BUILDDIR" ( CRSSYNC_BIN=$(readlink -f "$SRCDIR")/build/output/bin/crssync cd "$BUILDDIR" - rpm --eval "%{mingw64_cmake}" > mingw64-cmake.sh - sed -i -e 's/%__cmake/cmake/' mingw64-cmake.sh - chmod +x mingw64-cmake.sh - ./mingw$bits-cmake.sh \ + mingw$bits-cmake \ -DCMAKE_CROSS_COMPILING=1 \ -DUSE_CCACHE=ON \ -DCMAKE_BUILD_TYPE=$buildtype \ From 8723b82ec19a3bec7f39b46128c798c7a2ee4230 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 15 Sep 2023 16:15:59 +0200 Subject: [PATCH 008/151] qgsvirtualpointcloudprovider.cpp: add a missing variable initialization Fixes the following compiler warning: ``` In file included from /home/even/qgis/qgis/src/core/pointcloud/qgspointclouddataprovider.h:26, from /home/even/qgis/qgis/src/core/providers/vpc/qgsvirtualpointcloudprovider.h:21, from /home/even/qgis/qgis/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp:21: /home/even/qgis/qgis/src/core/pointcloud/qgspointcloudsubindex.h: In member function 'void QgsVirtualPointCloudProvider::parseFile()': /home/even/qgis/qgis/src/core/pointcloud/qgspointcloudsubindex.h:47:9: warning: 'count' may be used uninitialized in this function [-Wmaybe-uninitialized] 47 | , mPointCount( count ) | ^~~~~~~~~~~~~~~~~~~~ /home/even/qgis/qgis/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp:196:12: note: 'count' was declared here 196 | qint64 count; | ^~~~~ ``` --- src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp b/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp index 1a15d2903cbf..1b8dfe1625b7 100644 --- a/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp +++ b/src/core/providers/vpc/qgsvirtualpointcloudprovider.cpp @@ -193,7 +193,7 @@ void QgsVirtualPointCloudProvider::parseFile() } QString uri; - qint64 count; + qint64 count = 0; QgsRectangle extent; QgsGeometry geometry; QgsDoubleRange zRange; From b9d0b4ce57169cea7d21990942c136c2f13b9b0a Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 16 Sep 2023 12:08:11 +0200 Subject: [PATCH 009/151] [cmake] Prefer pkgconfig for spatialite and use a target --- cmake/FindSpatiaLite.cmake | 141 +++++++++++---------- external/qspatialite/CMakeLists.txt | 3 +- src/analysis/CMakeLists.txt | 1 - src/core/CMakeLists.txt | 8 +- src/plugins/offline_editing/CMakeLists.txt | 6 - src/providers/spatialite/CMakeLists.txt | 4 +- src/providers/virtual/CMakeLists.txt | 4 - src/quickgui/CMakeLists.txt | 2 - src/quickgui/plugin/CMakeLists.txt | 1 - tests/src/quickgui/CMakeLists.txt | 2 - tests/src/quickgui/app/CMakeLists.txt | 2 - 11 files changed, 80 insertions(+), 94 deletions(-) diff --git a/cmake/FindSpatiaLite.cmake b/cmake/FindSpatiaLite.cmake index 86d03e190a46..acb3b0ed4ecc 100644 --- a/cmake/FindSpatiaLite.cmake +++ b/cmake/FindSpatiaLite.cmake @@ -11,69 +11,80 @@ # SPATIALITE_INCLUDE_DIR # SPATIALITE_LIBRARY -# This macro checks if the symbol exists -include(CheckLibraryExists) +find_package(PkgConfig REQUIRED) +pkg_search_module(PC_SPATIALITE IMPORTED_TARGET spatialite) - -# FIND_PATH and FIND_LIBRARY normally search standard locations -# before the specified paths. To search non-standard paths first, -# FIND_* is invoked first with specified paths and NO_DEFAULT_PATH -# and then again with no specified paths to search the default -# locations. When an earlier FIND_* succeeds, subsequent FIND_*s -# searching for the same item do nothing. - -# try to use sqlite framework on mac -# want clean framework path, not unix compatibility path -IF (APPLE AND NOT IOS) - IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" - OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" - OR NOT CMAKE_FIND_FRAMEWORK) - SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) - SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) - FIND_PATH(SPATIALITE_INCLUDE_DIR SQLite3/spatialite.h) - # if no SpatiaLite header, we don't want SQLite find below to succeed - IF (SPATIALITE_INCLUDE_DIR) - FIND_LIBRARY(SPATIALITE_LIBRARY SQLite3) - # FIND_PATH doesn't add "Headers" for a framework - SET (SPATIALITE_INCLUDE_DIR ${SPATIALITE_LIBRARY}/Headers CACHE PATH "Path to a file." FORCE) - ENDIF (SPATIALITE_INCLUDE_DIR) - SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) - ENDIF () -ENDIF (APPLE AND NOT IOS) - -FIND_PATH(SPATIALITE_INCLUDE_DIR spatialite.h - /usr/include - "$ENV{INCLUDE}" - "$ENV{LIB_DIR}/include" - "$ENV{LIB_DIR}/include/spatialite" - ) - -FIND_LIBRARY(SPATIALITE_LIBRARY NAMES spatialite_i spatialite PATHS - /usr/lib - $ENV{LIB} - $ENV{LIB_DIR}/lib - ) - -IF (SPATIALITE_INCLUDE_DIR AND SPATIALITE_LIBRARY) - SET(SPATIALITE_FOUND TRUE) -ENDIF (SPATIALITE_INCLUDE_DIR AND SPATIALITE_LIBRARY) - - -IF (SPATIALITE_FOUND) - - IF (NOT SPATIALITE_FIND_QUIETLY) - MESSAGE(STATUS "Found SpatiaLite: ${SPATIALITE_LIBRARY}") - ENDIF (NOT SPATIALITE_FIND_QUIETLY) - - IF(APPLE) - # no extra LDFLAGS used in link test, may fail in OS X SDK - SET(CMAKE_REQUIRED_LIBRARIES "-F/Library/Frameworks" ${CMAKE_REQUIRED_LIBRARIES}) - ENDIF(APPLE) - -ELSE (SPATIALITE_FOUND) - - IF (SPATIALITE_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find SpatiaLite. Include: ${SPATIALITE_INCLUDE_DIR} Library: ${SPATIALITE_LIBRARY}") - ENDIF (SPATIALITE_FIND_REQUIRED) - -ENDIF (SPATIALITE_FOUND) +if(PC_SPATIALITE_FOUND) + add_library(spatialite::spatialite ALIAS PkgConfig::PC_SPATIALITE) +else() + # This macro checks if the symbol exists + include(CheckLibraryExists) + + + # FIND_PATH and FIND_LIBRARY normally search standard locations + # before the specified paths. To search non-standard paths first, + # FIND_* is invoked first with specified paths and NO_DEFAULT_PATH + # and then again with no specified paths to search the default + # locations. When an earlier FIND_* succeeds, subsequent FIND_*s + # searching for the same item do nothing. + + # try to use sqlite framework on mac + # want clean framework path, not unix compatibility path + IF (APPLE AND NOT IOS) + IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" + OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" + OR NOT CMAKE_FIND_FRAMEWORK) + SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) + SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) + FIND_PATH(SPATIALITE_INCLUDE_DIR SQLite3/spatialite.h) + # if no SpatiaLite header, we don't want SQLite find below to succeed + IF (SPATIALITE_INCLUDE_DIR) + FIND_LIBRARY(SPATIALITE_LIBRARY SQLite3) + # FIND_PATH doesn't add "Headers" for a framework + SET (SPATIALITE_INCLUDE_DIR ${SPATIALITE_LIBRARY}/Headers CACHE PATH "Path to a file." FORCE) + ENDIF (SPATIALITE_INCLUDE_DIR) + SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) + ENDIF () + ENDIF (APPLE AND NOT IOS) + + FIND_PATH(SPATIALITE_INCLUDE_DIR spatialite.h + /usr/include + "$ENV{INCLUDE}" + "$ENV{LIB_DIR}/include" + "$ENV{LIB_DIR}/include/spatialite" + ) + + FIND_LIBRARY(SPATIALITE_LIBRARY NAMES spatialite_i spatialite PATHS + /usr/lib + $ENV{LIB} + $ENV{LIB_DIR}/lib + ) + + IF (SPATIALITE_INCLUDE_DIR AND SPATIALITE_LIBRARY) + SET(SPATIALITE_FOUND TRUE) + ENDIF (SPATIALITE_INCLUDE_DIR AND SPATIALITE_LIBRARY) + + + IF (SPATIALITE_FOUND) + add_library(spatialite::spatialite UNKNOWN IMPORTED) + target_link_libraries(spatialite::spatialite INTERFACE ${SPATIALITE_LIBRARY}) + target_include_directories(spatialite::spatialite INTERFACE ${SPATIALITE_INCLUDE_DIR}) + set_target_properties(spatialite::spatialite PROPERTIES IMPORTED_LOCATION ${SPATIALITE_LIBRARY}) + + IF (NOT SPATIALITE_FIND_QUIETLY) + MESSAGE(STATUS "Found SpatiaLite: ${SPATIALITE_LIBRARY}") + ENDIF (NOT SPATIALITE_FIND_QUIETLY) + + IF(APPLE) + # no extra LDFLAGS used in link test, may fail in OS X SDK + SET(CMAKE_REQUIRED_LIBRARIES "-F/Library/Frameworks" ${CMAKE_REQUIRED_LIBRARIES}) + ENDIF(APPLE) + + ELSE (SPATIALITE_FOUND) + + IF (SPATIALITE_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find SpatiaLite. Include: ${SPATIALITE_INCLUDE_DIR} Library: ${SPATIALITE_LIBRARY}") + ENDIF (SPATIALITE_FIND_REQUIRED) + + ENDIF (SPATIALITE_FOUND) +endif() diff --git a/external/qspatialite/CMakeLists.txt b/external/qspatialite/CMakeLists.txt index 0e5df6a8a1f1..33536dafe913 100644 --- a/external/qspatialite/CMakeLists.txt +++ b/external/qspatialite/CMakeLists.txt @@ -17,8 +17,7 @@ add_library(qsqlspatialite SHARED ${QSQLSPATIALITE_SRC}) target_link_libraries(qsqlspatialite ${Qt5Core_LIBRARIES} ${Qt5Sql_LIBRARIES} - ${SQLITE3_LIBRARY} - ${SPATIALITE_LIBRARY} + spatialite::spatialite qgis_core ) diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 56eb61a20625..b17575e62f52 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -455,7 +455,6 @@ if (WITH_PDAL AND PDAL_2_5_OR_HIGHER) ) endif() -include_directories(SYSTEM ${SPATIALITE_INCLUDE_DIR}) include_directories(SYSTEM ${SPATIALINDEX_INCLUDE_DIR}) include_directories(SYSTEM ${SQLITE3_INCLUDE_DIR}) include_directories(BEFORE raster) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e617fce13fc5..d3471c2485cb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2259,12 +2259,6 @@ target_include_directories(qgis_core SYSTEM PUBLIC ${EXIV2_INCLUDE_DIR} ) -if (WITH_SPATIALITE) - target_include_directories(qgis_core SYSTEM PUBLIC - ${SPATIALITE_INCLUDE_DIR} - ) -endif() - target_include_directories(qgis_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} @@ -2441,7 +2435,7 @@ if (NOT IOS) endif() if (WITH_SPATIALITE) - target_link_libraries(qgis_core ${SPATIALITE_LIBRARY}) + target_link_libraries(qgis_core spatialite::spatialite) endif() if (BUILD_WITH_QT6) diff --git a/src/plugins/offline_editing/CMakeLists.txt b/src/plugins/offline_editing/CMakeLists.txt index bb1c90af5459..c7ca21558d36 100644 --- a/src/plugins/offline_editing/CMakeLists.txt +++ b/src/plugins/offline_editing/CMakeLists.txt @@ -36,12 +36,6 @@ target_compile_features(plugin_offlineediting PRIVATE cxx_std_17) target_link_libraries(plugin_offlineediting qgis_core qgis_gui - ${SPATIALITE_LIBRARY} -) - -include_directories(SYSTEM - ${SPATIALITE_INCLUDE_DIR} - ${SQLITE3_INCLUDE_DIR} ) include_directories( diff --git a/src/providers/spatialite/CMakeLists.txt b/src/providers/spatialite/CMakeLists.txt index a9de25a4059b..efaad9a47c81 100644 --- a/src/providers/spatialite/CMakeLists.txt +++ b/src/providers/spatialite/CMakeLists.txt @@ -38,7 +38,7 @@ target_include_directories(provider_spatialite_a PUBLIC target_link_libraries(provider_spatialite_a qgis_core - ${SPATIALITE_LIBRARY} + spatialite::spatialite ) # require c++17 @@ -95,7 +95,7 @@ else() target_link_libraries(provider_spatialite qgis_core - ${SPATIALITE_LIBRARY} + spatialite::spatialite ) if (WITH_GUI) diff --git a/src/providers/virtual/CMakeLists.txt b/src/providers/virtual/CMakeLists.txt index 281e1156c03b..524740624c32 100644 --- a/src/providers/virtual/CMakeLists.txt +++ b/src/providers/virtual/CMakeLists.txt @@ -40,8 +40,6 @@ target_include_directories(provider_virtuallayer_a PUBLIC target_link_libraries(provider_virtuallayer_a qgis_core - ${SQLITE3_LIBRARY} - ${SPATIALITE_LIBRARY} ) # require c++17 @@ -97,8 +95,6 @@ else() qgis_core ${QT_VERSION_BASE}::Core ${QT_VERSION_BASE}::Widgets - ${SQLITE3_LIBRARY} - ${SPATIALITE_LIBRARY} ) if (WITH_GUI) diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt index 95b853860bef..e1f1e0a85266 100644 --- a/src/quickgui/CMakeLists.txt +++ b/src/quickgui/CMakeLists.txt @@ -32,8 +32,6 @@ include_directories(SYSTEM ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${EXPAT_INCLUDE_DIR} - ${SQLITE3_INCLUDE_DIR} - ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt index fa42bede1d40..585e5edf71dc 100644 --- a/src/quickgui/plugin/CMakeLists.txt +++ b/src/quickgui/plugin/CMakeLists.txt @@ -35,7 +35,6 @@ include_directories(SYSTEM ${GEOS_INCLUDE_DIR} ${EXPAT_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} - ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index 2e27caff95d8..ea16e3f8a109 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -22,8 +22,6 @@ include_directories(SYSTEM ${SPATIALINDEX_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} ${EXPAT_INCLUDE_DIR} - ${SQLITE3_INCLUDE_DIR} - ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt index ca552545042d..dd2c90a8258f 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -27,8 +27,6 @@ include_directories(SYSTEM ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${EXPAT_INCLUDE_DIR} - ${SQLITE3_INCLUDE_DIR} - ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) From 62e7df780c48e2cbbc36570d2b416745c7528c27 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 17 Sep 2023 17:33:59 +0200 Subject: [PATCH 010/151] [GPX provider] Add a 'time' attribute for the waypoint layer (fixes #54119) --- src/providers/gpx/gpsdata.cpp | 16 ++++++++++++++++ src/providers/gpx/gpsdata.h | 4 ++++ src/providers/gpx/qgsgpxfeatureiterator.cpp | 3 +++ src/providers/gpx/qgsgpxprovider.cpp | 6 +++--- src/providers/gpx/qgsgpxprovider.h | 3 ++- tests/src/python/test_provider_gpx.py | 9 +++++++++ tests/testdata/gpx_test_suite.gpx | 2 ++ 7 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/providers/gpx/gpsdata.cpp b/src/providers/gpx/gpsdata.cpp index 020cc0ee8974..2e0d91a21b3b 100644 --- a/src/providers/gpx/gpsdata.cpp +++ b/src/providers/gpx/gpsdata.cpp @@ -585,6 +585,17 @@ bool QgsGPXHandler::startElement( const XML_Char *qName, const XML_Char **attr ) else parseModes.push( ParsingUnknown ); } + else if ( !std::strcmp( qName, "time" ) ) + { + if ( parseModes.top() == ParsingWaypoint ) + { + mDateTime = &mWpt.time; + mCharBuffer.clear(); + parseModes.push( ParsingDateTime ); + } + else + parseModes.push( ParsingUnknown ); + } else if ( !std::strcmp( qName, "sym" ) ) { if ( parseModes.top() == ParsingWaypoint ) @@ -734,6 +745,11 @@ bool QgsGPXHandler::endElement( const std::string &qName ) *mString = mCharBuffer; mCharBuffer.clear(); } + else if ( parseModes.top() == ParsingDateTime ) + { + *mDateTime = QDateTime::fromString( mCharBuffer, Qt::ISODateWithMs ); + mCharBuffer.clear(); + } parseModes.pop(); return true; diff --git a/src/providers/gpx/gpsdata.h b/src/providers/gpx/gpsdata.h index 22ff4b157e8a..4e795bdcb6fd 100644 --- a/src/providers/gpx/gpsdata.h +++ b/src/providers/gpx/gpsdata.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -92,6 +93,7 @@ class QgsWaypoint : public QgsGpsPoint public: void writeXml( QTextStream &stream ) override; QgsFeatureId id; + QDateTime time; }; @@ -344,6 +346,7 @@ class QgsGPXHandler ParsingDouble, ParsingInt, ParsingString, + ParsingDateTime, ParsingUnknown }; @@ -361,6 +364,7 @@ class QgsGPXHandler QString *mString = nullptr; double *mDouble = nullptr; int *mInt = nullptr; + QDateTime *mDateTime = nullptr; QString mCharBuffer; }; diff --git a/src/providers/gpx/qgsgpxfeatureiterator.cpp b/src/providers/gpx/qgsgpxfeatureiterator.cpp index d9dbed7faf1b..f01e275ad9e6 100644 --- a/src/providers/gpx/qgsgpxfeatureiterator.cpp +++ b/src/providers/gpx/qgsgpxfeatureiterator.cpp @@ -398,6 +398,9 @@ void QgsGPXFeatureIterator::readAttributes( QgsFeature &feature, const QgsWaypoi case QgsGPXProvider::URLNameAttr: feature.setAttribute( i, QVariant( wpt.urlname ) ); break; + case QgsGPXProvider::TimeAttr: + feature.setAttribute( i, QVariant( wpt.time ) ); + break; } } } diff --git a/src/providers/gpx/qgsgpxprovider.cpp b/src/providers/gpx/qgsgpxprovider.cpp index 8bf0c2610e73..a7291f4a839d 100644 --- a/src/providers/gpx/qgsgpxprovider.cpp +++ b/src/providers/gpx/qgsgpxprovider.cpp @@ -48,17 +48,17 @@ const QStringList QgsGPXProvider::sAttributeNames = { "name", "elevation", "symbol", "number", "comment", "description", "source", - "url", "url name" + "url", "url name", "time" }; const QList< QVariant::Type > QgsGPXProvider::sAttributeTypes = { QVariant::String, QVariant::Double, QVariant::String, QVariant::Int, QVariant::String, QVariant::String, QVariant::String, - QVariant::String, QVariant::String + QVariant::String, QVariant::String, QVariant::DateTime, }; const QList< QgsGPXProvider::DataType > QgsGPXProvider::sAttributedUsedForLayerType = { QgsGPXProvider::AllType, QgsGPXProvider::WaypointType, QgsGPXProvider::TrkRteType, QgsGPXProvider::TrkRteType, QgsGPXProvider::AllType, QgsGPXProvider::AllType, QgsGPXProvider::AllType, QgsGPXProvider::AllType, - QgsGPXProvider::AllType, QgsGPXProvider::AllType + QgsGPXProvider::AllType, QgsGPXProvider::AllType, QgsGPXProvider::WaypointType, }; const QString GPX_KEY = QStringLiteral( "gpx" ); diff --git a/src/providers/gpx/qgsgpxprovider.h b/src/providers/gpx/qgsgpxprovider.h index 5842cdbcdd63..25fd3cb443b4 100644 --- a/src/providers/gpx/qgsgpxprovider.h +++ b/src/providers/gpx/qgsgpxprovider.h @@ -92,7 +92,8 @@ class QgsGPXProvider final: public QgsVectorDataProvider }; enum Attribute { NameAttr = 0, EleAttr, SymAttr, NumAttr, - CmtAttr, DscAttr, SrcAttr, URLAttr, URLNameAttr + CmtAttr, DscAttr, SrcAttr, URLAttr, URLNameAttr, + TimeAttr }; private: diff --git a/tests/src/python/test_provider_gpx.py b/tests/src/python/test_provider_gpx.py index ec9dd1eeddde..a4133130c8e2 100644 --- a/tests/src/python/test_provider_gpx.py +++ b/tests/src/python/test_provider_gpx.py @@ -11,6 +11,8 @@ import os +from qgis.PyQt.QtCore import Qt, QDateTime, QVariant + from qgis.core import ( QgsFeature, QgsPathResolver, @@ -220,6 +222,13 @@ def test_absolute_relative_uri(self): self.assertEqual(meta.absoluteToRelativeUri(absolute_uri, context), relative_uri) self.assertEqual(meta.relativeToAbsoluteUri(relative_uri, context), absolute_uri) + def test_waypoint_layer(self): + vl = QgsVectorLayer(f'{unitTestDataPath()}/gpx_test_suite.gpx' + "?type=waypoint", 'test2', 'gpx') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.fields().field("time").type(), QVariant.DateTime) + values = [f["time"] for f in vl.getFeatures()] + self.assertEqual(values[0], QDateTime(2023, 4, 25, 9, 52, 14, 0, Qt.TimeSpec(1))) + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/gpx_test_suite.gpx b/tests/testdata/gpx_test_suite.gpx index b1ea563ee60d..ac9e2d5f5353 100644 --- a/tests/testdata/gpx_test_suite.gpx +++ b/tests/testdata/gpx_test_suite.gpx @@ -5,12 +5,14 @@ 3 Digitized in QGIS 0 + Apple 2 Digitized in QGIS 0 + Orange From 173014016bdfa06b1b2c76f849eec7c4e228e91b Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 06:12:18 +0200 Subject: [PATCH 011/151] [cmake] Use native find EXPAT logic and switch to using target EXPAT::EXPAT --- CMakeLists.txt | 2 +- cmake/FindExpat.cmake | 57 --------------------------- src/core/CMakeLists.txt | 3 +- src/providers/gpx/CMakeLists.txt | 1 - src/providers/wfs/CMakeLists.txt | 2 - src/quickgui/CMakeLists.txt | 1 - src/quickgui/plugin/CMakeLists.txt | 1 - tests/src/quickgui/CMakeLists.txt | 1 - tests/src/quickgui/app/CMakeLists.txt | 1 - 9 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 cmake/FindExpat.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 359434bf5a3d..8ae13427370b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,7 +361,7 @@ if(WITH_CORE) find_package(Proj) find_package(GEOS) find_package(GDAL) - find_package(Expat REQUIRED) + find_package(EXPAT REQUIRED) find_package(Spatialindex REQUIRED) find_package(LibZip REQUIRED) diff --git a/cmake/FindExpat.cmake b/cmake/FindExpat.cmake deleted file mode 100644 index c213dc9c0446..000000000000 --- a/cmake/FindExpat.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# Find Expat -# ~~~~~~~~~~ -# Copyright (c) 2007, Martin Dobias -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -# CMake module to search for Expat library -# (library for parsing XML files) -# -# If it's found it sets EXPAT_FOUND to TRUE -# and following variables are set: -# EXPAT_INCLUDE_DIR -# EXPAT_LIBRARY - -# FIND_PATH and FIND_LIBRARY normally search standard locations -# before the specified paths. To search non-standard paths first, -# FIND_* is invoked first with specified paths and NO_DEFAULT_PATH -# and then again with no specified paths to search the default -# locations. When an earlier FIND_* succeeds, subsequent FIND_*s -# searching for the same item do nothing. -FIND_PATH(EXPAT_INCLUDE_DIR expat.h - "$ENV{LIB_DIR}/include/" - "$ENV{LIB_DIR}/include/expat" - c:/msys/local/include - NO_DEFAULT_PATH - ) -FIND_PATH(EXPAT_INCLUDE_DIR expat.h) -#libexpat needed for msvc version -FIND_LIBRARY(EXPAT_LIBRARY NAMES expat libexpat PATHS - "$ENV{LIB_DIR}/lib" - c:/msys/local/lib - NO_DEFAULT_PATH - ) -FIND_LIBRARY(EXPAT_LIBRARY NAMES expat libexpat) - -IF (EXPAT_INCLUDE_DIR AND EXPAT_LIBRARY) - SET(EXPAT_FOUND TRUE) -ENDIF (EXPAT_INCLUDE_DIR AND EXPAT_LIBRARY) - - -IF (EXPAT_FOUND) - - IF (NOT EXPAT_FIND_QUIETLY) - MESSAGE(STATUS "Found Expat: ${EXPAT_LIBRARY}") - ENDIF (NOT EXPAT_FIND_QUIETLY) - -ELSE (EXPAT_FOUND) - - IF (EXPAT_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find Expat") - ELSE (EXPAT_FIND_REQUIRED) - IF (NOT EXPAT_FIND_QUIETLY) - MESSAGE(STATUS "Could not find Expat") - ENDIF (NOT EXPAT_FIND_QUIETLY) - ENDIF (EXPAT_FIND_REQUIRED) - -ENDIF (EXPAT_FOUND) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 79fb3e24394e..4bc864554c44 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2252,7 +2252,6 @@ target_include_directories(qgis_core SYSTEM PUBLIC ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} # before GEOS for case-insensitive filesystems ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} @@ -2421,7 +2420,7 @@ target_link_libraries(qgis_core ${GEOS_LIBRARY} ${GDAL_LIBRARY} ${SPATIALINDEX_LIBRARY} - ${EXPAT_LIBRARY} + EXPAT::EXPAT ${SQLITE3_LIBRARY} ${LIBZIP_LIBRARY} ${Protobuf_LITE_LIBRARY} diff --git a/src/providers/gpx/CMakeLists.txt b/src/providers/gpx/CMakeLists.txt index 4adace3b04a0..5b49f3f17b70 100644 --- a/src/providers/gpx/CMakeLists.txt +++ b/src/providers/gpx/CMakeLists.txt @@ -34,7 +34,6 @@ add_library(provider_gpx MODULE ${GPX_SRCS}) target_compile_features(provider_gpx PRIVATE cxx_std_17) target_link_libraries(provider_gpx - ${EXPAT_LIBRARY} qgis_core ) diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index e7c9f2cd8c58..045f8757be8f 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -62,7 +62,6 @@ target_include_directories(provider_wfs_a PUBLIC ) target_link_libraries(provider_wfs_a - ${EXPAT_LIBRARY} qgis_core ) @@ -120,7 +119,6 @@ else() target_compile_definitions(provider_wfs PRIVATE "-DQT_NO_FOREACH") target_link_libraries (provider_wfs - ${EXPAT_LIBRARY} qgis_core ) diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt index e1f1e0a85266..0317960b3643 100644 --- a/src/quickgui/CMakeLists.txt +++ b/src/quickgui/CMakeLists.txt @@ -31,7 +31,6 @@ include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt index 585e5edf71dc..5570fbd8ac2d 100644 --- a/src/quickgui/plugin/CMakeLists.txt +++ b/src/quickgui/plugin/CMakeLists.txt @@ -33,7 +33,6 @@ include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index ea16e3f8a109..3e871fbbaddc 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -21,7 +21,6 @@ include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt index dd2c90a8258f..ec9fa6b2eba6 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -26,7 +26,6 @@ include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) From a42c02b4de73c826a5a7552fb3ff5925684e319d Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sun, 17 Sep 2023 08:39:07 +0200 Subject: [PATCH 012/151] Use GDAL cmake config and switch to target. Perserve fallback GDAL discovery logic since GDALConfig is quite recent. --- CMakeLists.txt | 6 +- cmake/FindGDAL.cmake | 382 ++++++++++++----------- src/core/CMakeLists.txt | 3 +- src/crssync/CMakeLists.txt | 1 - src/process/CMakeLists.txt | 1 - src/providers/grass/CMakeLists.txt | 17 +- src/providers/mdal/CMakeLists.txt | 5 - src/providers/pdal/CMakeLists.txt | 3 +- src/providers/wms/CMakeLists.txt | 1 - src/server/CMakeLists.txt | 1 - tests/src/3d/sandbox/CMakeLists.txt | 2 - tests/src/providers/grass/CMakeLists.txt | 2 - 12 files changed, 208 insertions(+), 216 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 359434bf5a3d..638abd242954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -360,7 +360,7 @@ if(WITH_CORE) # required find_package(Proj) find_package(GEOS) - find_package(GDAL) + find_package(GDAL REQUIRED) find_package(Expat REQUIRED) find_package(Spatialindex REQUIRED) find_package(LibZip REQUIRED) @@ -403,8 +403,8 @@ if(WITH_CORE) set (HAVE_SPATIALITE TRUE) endif() - if (NOT PROJ_FOUND OR NOT GEOS_FOUND OR NOT GDAL_FOUND) - message (SEND_ERROR "Some dependencies were not found! Proj: ${PROJ_FOUND}, Geos: ${GEOS_FOUND}, GDAL: ${GDAL_FOUND}") + if (NOT PROJ_FOUND OR NOT GEOS_FOUND) + message (SEND_ERROR "Some dependencies were not found! Proj: ${PROJ_FOUND}, Geos: ${GEOS_FOUND}") endif() if (POSTGRES_FOUND) diff --git a/cmake/FindGDAL.cmake b/cmake/FindGDAL.cmake index 56e4c3a7381b..5878e218440b 100644 --- a/cmake/FindGDAL.cmake +++ b/cmake/FindGDAL.cmake @@ -13,195 +13,201 @@ # # GDAL_INCLUDE_DIR = where to find headers -INCLUDE (${CMAKE_SOURCE_DIR}/cmake/MacPlistMacros.cmake) +find_package(GDAL CONFIG) +if(NOT GDAL_FOUND) + # Fallback logic for GDAL < 3.5, as soon as we switch to GDAL>=3.5 this file (Find_GDAL.cmake) can be deleted + INCLUDE (${CMAKE_SOURCE_DIR}/cmake/MacPlistMacros.cmake) -IF(WIN32) - - IF (MINGW) - FIND_PATH(GDAL_INCLUDE_DIR gdal.h /usr/local/include /usr/include c:/msys/local/include PATH_SUFFIXES gdal) - FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS /usr/local/lib /usr/lib c:/msys/local/lib) - ENDIF (MINGW) - - IF (MSVC) - FIND_PATH(GDAL_INCLUDE_DIR gdal.h "$ENV{LIB_DIR}/include/gdal" $ENV{INCLUDE}) - FIND_LIBRARY(GDAL_LIBRARY NAMES gdal gdal_i PATHS - "$ENV{LIB_DIR}/lib" $ENV{LIB} /usr/lib c:/msys/local/lib) - IF (GDAL_LIBRARY) - SET ( - GDAL_LIBRARY;odbc32;odbccp32 - CACHE STRING INTERNAL) - ENDIF (GDAL_LIBRARY) - ENDIF (MSVC) - -ELSEIF(APPLE AND QGIS_MAC_DEPS_DIR) - - FIND_PATH(GDAL_INCLUDE_DIR gdal.h "$ENV{LIB_DIR}/include") - FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS "$ENV{LIB_DIR}/lib") - -ELSE(WIN32) - - IF(UNIX) - - # try to use framework on mac - # want clean framework path, not unix compatibility path - IF (APPLE) - IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" - OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" - OR NOT CMAKE_FIND_FRAMEWORK) - SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) - SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) - FIND_LIBRARY(GDAL_LIBRARY GDAL) - IF (GDAL_LIBRARY) - # they're all the same in a framework - SET (GDAL_INCLUDE_DIR ${GDAL_LIBRARY}/Headers CACHE PATH "Path to a file.") - # set GDAL_CONFIG to make later test happy, not used here, may not exist - SET (GDAL_CONFIG ${GDAL_LIBRARY}/unix/bin/gdal-config CACHE FILEPATH "Path to a program.") - # version in info.plist - GET_VERSION_PLIST (${GDAL_LIBRARY}/Resources/Info.plist GDAL_VERSION) - IF (NOT GDAL_VERSION) - MESSAGE (FATAL_ERROR "Could not determine GDAL version from framework.") - ENDIF (NOT GDAL_VERSION) + IF(WIN32) + + IF (MINGW) + FIND_PATH(GDAL_INCLUDE_DIR gdal.h /usr/local/include /usr/include c:/msys/local/include PATH_SUFFIXES gdal) + FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS /usr/local/lib /usr/lib c:/msys/local/lib) + ENDIF (MINGW) + + IF (MSVC) + FIND_PATH(GDAL_INCLUDE_DIR gdal.h "$ENV{LIB_DIR}/include/gdal" $ENV{INCLUDE}) + FIND_LIBRARY(GDAL_LIBRARY NAMES gdal gdal_i PATHS + "$ENV{LIB_DIR}/lib" $ENV{LIB} /usr/lib c:/msys/local/lib) + IF (GDAL_LIBRARY) + SET ( + GDAL_LIBRARY;odbc32;odbccp32 + CACHE STRING INTERNAL) + ENDIF (GDAL_LIBRARY) + ENDIF (MSVC) + + ELSEIF(APPLE AND QGIS_MAC_DEPS_DIR) + + FIND_PATH(GDAL_INCLUDE_DIR gdal.h "$ENV{LIB_DIR}/include") + FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS "$ENV{LIB_DIR}/lib") + + ELSE(WIN32) + + IF(UNIX) + + # try to use framework on mac + # want clean framework path, not unix compatibility path + IF (APPLE) + IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" + OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" + OR NOT CMAKE_FIND_FRAMEWORK) + SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) + SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) + FIND_LIBRARY(GDAL_LIBRARY GDAL) + IF (GDAL_LIBRARY) + # they're all the same in a framework + SET (GDAL_INCLUDE_DIR ${GDAL_LIBRARY}/Headers CACHE PATH "Path to a file.") + # set GDAL_CONFIG to make later test happy, not used here, may not exist + SET (GDAL_CONFIG ${GDAL_LIBRARY}/unix/bin/gdal-config CACHE FILEPATH "Path to a program.") + # version in info.plist + GET_VERSION_PLIST (${GDAL_LIBRARY}/Resources/Info.plist GDAL_VERSION) + IF (NOT GDAL_VERSION) + MESSAGE (FATAL_ERROR "Could not determine GDAL version from framework.") + ENDIF (NOT GDAL_VERSION) + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GDAL_VERSION_MAJOR "${GDAL_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GDAL_VERSION_MINOR "${GDAL_VERSION}") + IF (GDAL_VERSION_MAJOR LESS 3) + MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.2 or higher.") + ENDIF (GDAL_VERSION_MAJOR LESS 3) + IF ( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR LESS 2) ) + MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.2 or higher.") + ENDIF( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR LESS 2) ) + + ENDIF (GDAL_LIBRARY) + SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) + ENDIF () + ENDIF (APPLE) + + IF(CYGWIN) + FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS /usr/lib /usr/local/lib) + ENDIF(CYGWIN) + + IF (NOT GDAL_INCLUDE_DIR OR NOT GDAL_LIBRARY OR NOT GDAL_CONFIG) + # didn't find OS X framework, and was not set by user + SET(GDAL_CONFIG_PREFER_PATH "$ENV{GDAL_HOME}/bin" CACHE STRING "preferred path to GDAL (gdal-config)") + SET(GDAL_CONFIG_PREFER_FWTOOLS_PATH "$ENV{FWTOOLS_HOME}/bin_safe" CACHE STRING "preferred path to GDAL (gdal-config) from FWTools") + FIND_PROGRAM(GDAL_CONFIG gdal-config + ${GDAL_CONFIG_PREFER_PATH} + ${GDAL_CONFIG_PREFER_FWTOOLS_PATH} + $ENV{LIB_DIR}/bin + /usr/local/bin/ + /usr/bin/ + ) + # MESSAGE("DBG GDAL_CONFIG ${GDAL_CONFIG}") + + IF (GDAL_CONFIG) + + ## extract gdal version + EXEC_PROGRAM(${GDAL_CONFIG} + ARGS --version + OUTPUT_VARIABLE GDAL_VERSION ) STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GDAL_VERSION_MAJOR "${GDAL_VERSION}") STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GDAL_VERSION_MINOR "${GDAL_VERSION}") - IF (GDAL_VERSION_MAJOR LESS 3) - MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.2 or higher.") - ENDIF (GDAL_VERSION_MAJOR LESS 3) - IF ( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR LESS 2) ) - MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.2 or higher.") - ENDIF( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR LESS 2) ) - - ENDIF (GDAL_LIBRARY) - SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) - ENDIF () - ENDIF (APPLE) - - IF(CYGWIN) - FIND_LIBRARY(GDAL_LIBRARY NAMES gdal PATHS /usr/lib /usr/local/lib) - ENDIF(CYGWIN) - - IF (NOT GDAL_INCLUDE_DIR OR NOT GDAL_LIBRARY OR NOT GDAL_CONFIG) - # didn't find OS X framework, and was not set by user - SET(GDAL_CONFIG_PREFER_PATH "$ENV{GDAL_HOME}/bin" CACHE STRING "preferred path to GDAL (gdal-config)") - SET(GDAL_CONFIG_PREFER_FWTOOLS_PATH "$ENV{FWTOOLS_HOME}/bin_safe" CACHE STRING "preferred path to GDAL (gdal-config) from FWTools") - FIND_PROGRAM(GDAL_CONFIG gdal-config - ${GDAL_CONFIG_PREFER_PATH} - ${GDAL_CONFIG_PREFER_FWTOOLS_PATH} - $ENV{LIB_DIR}/bin - /usr/local/bin/ - /usr/bin/ - ) - # MESSAGE("DBG GDAL_CONFIG ${GDAL_CONFIG}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3" GDAL_VERSION_MICRO "${GDAL_VERSION}") - IF (GDAL_CONFIG) - - ## extract gdal version - EXEC_PROGRAM(${GDAL_CONFIG} - ARGS --version - OUTPUT_VARIABLE GDAL_VERSION ) - STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GDAL_VERSION_MAJOR "${GDAL_VERSION}") - STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GDAL_VERSION_MINOR "${GDAL_VERSION}") - STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3" GDAL_VERSION_MICRO "${GDAL_VERSION}") - - # MESSAGE("DBG GDAL_VERSION ${GDAL_VERSION}") - # MESSAGE("DBG GDAL_VERSION_MAJOR ${GDAL_VERSION_MAJOR}") - # MESSAGE("DBG GDAL_VERSION_MINOR ${GDAL_VERSION_MINOR}") - - # check for gdal version - # version 1.2.5 is known NOT to be supported (missing CPL_STDCALL macro) - # According to INSTALL, 2.1+ is required - IF (GDAL_VERSION_MAJOR LESS 3) - MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.0 or higher.") - ENDIF (GDAL_VERSION_MAJOR LESS 3) - #IF ( (GDAL_VERSION_MAJOR EQUAL 2) AND (GDAL_VERSION_MINOR LESS 1) ) - # MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 2.1 or higher.") - #ENDIF( (GDAL_VERSION_MAJOR EQUAL 2) AND (GDAL_VERSION_MINOR LESS 1) ) - IF ( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR EQUAL 0) AND (GDAL_VERSION_MICRO LESS 3) ) - MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.0.3 or higher.") - ENDIF( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR EQUAL 0) AND (GDAL_VERSION_MICRO LESS 3) ) - - # set INCLUDE_DIR to prefix+include - EXEC_PROGRAM(${GDAL_CONFIG} - ARGS --prefix - OUTPUT_VARIABLE GDAL_PREFIX) - #SET(GDAL_INCLUDE_DIR ${GDAL_PREFIX}/include CACHE STRING INTERNAL) - FIND_PATH(GDAL_INCLUDE_DIR - gdal.h - ${GDAL_PREFIX}/include/gdal - ${GDAL_PREFIX}/include - /usr/local/include - /usr/include - ) - - ## extract link dirs for rpath - EXEC_PROGRAM(${GDAL_CONFIG} - ARGS --libs - OUTPUT_VARIABLE GDAL_CONFIG_LIBS ) - - ## split off the link dirs (for rpath) - ## use regular expression to match wildcard equivalent "-L*" - ## with is a space or a semicolon - STRING(REGEX MATCHALL "[-][L]([^ ;])+" - GDAL_LINK_DIRECTORIES_WITH_PREFIX - "${GDAL_CONFIG_LIBS}" ) - # MESSAGE("DBG GDAL_LINK_DIRECTORIES_WITH_PREFIX=${GDAL_LINK_DIRECTORIES_WITH_PREFIX}") - - ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES - - IF (GDAL_LINK_DIRECTORIES_WITH_PREFIX) - STRING(REGEX REPLACE "[-][L]" "" GDAL_LINK_DIRECTORIES ${GDAL_LINK_DIRECTORIES_WITH_PREFIX} ) - ENDIF (GDAL_LINK_DIRECTORIES_WITH_PREFIX) - - ## split off the name - ## use regular expression to match wildcard equivalent "-l*" - ## with is a space or a semicolon - STRING(REGEX MATCHALL "[-][l]([^ ;])+" - GDAL_LIB_NAME_WITH_PREFIX - "${GDAL_CONFIG_LIBS}" ) - # MESSAGE("DBG GDAL_LIB_NAME_WITH_PREFIX=${GDAL_LIB_NAME_WITH_PREFIX}") - - - ## remove prefix -l because we need the pure name - - IF (GDAL_LIB_NAME_WITH_PREFIX) - STRING(REGEX REPLACE "[-][l]" "" GDAL_LIB_NAME ${GDAL_LIB_NAME_WITH_PREFIX} ) - ENDIF (GDAL_LIB_NAME_WITH_PREFIX) - - IF (APPLE) - IF (NOT GDAL_LIBRARY) - # work around empty GDAL_LIBRARY left by framework check - # while still preserving user setting if given - # ***FIXME*** need to improve framework check so below not needed - SET(GDAL_LIBRARY ${GDAL_LINK_DIRECTORIES}/lib${GDAL_LIB_NAME}.dylib CACHE STRING INTERNAL FORCE) - ENDIF (NOT GDAL_LIBRARY) - ELSE (APPLE) - FIND_LIBRARY(GDAL_LIBRARY NAMES ${GDAL_LIB_NAME} gdal PATHS ${GDAL_LINK_DIRECTORIES}/lib ${GDAL_LINK_DIRECTORIES}) - ENDIF (APPLE) - - ELSE(GDAL_CONFIG) - MESSAGE("FindGDAL.cmake: gdal-config not found. Please set it manually. GDAL_CONFIG=${GDAL_CONFIG}") - ENDIF(GDAL_CONFIG) - ENDIF (NOT GDAL_INCLUDE_DIR OR NOT GDAL_LIBRARY OR NOT GDAL_CONFIG) - ENDIF(UNIX) -ENDIF(WIN32) - - -IF (GDAL_INCLUDE_DIR AND GDAL_LIBRARY) - SET(GDAL_FOUND TRUE) -ENDIF (GDAL_INCLUDE_DIR AND GDAL_LIBRARY) - -IF (GDAL_FOUND) - - IF (NOT GDAL_FIND_QUIETLY) - FILE(READ ${GDAL_INCLUDE_DIR}/gdal_version.h gdal_version) - STRING(REGEX REPLACE "^.*GDAL_RELEASE_NAME +\"([^\"]+)\".*$" "\\1" GDAL_RELEASE_NAME "${gdal_version}") - - MESSAGE(STATUS "Found GDAL: ${GDAL_LIBRARY} (${GDAL_RELEASE_NAME})") - ENDIF (NOT GDAL_FIND_QUIETLY) - -ELSE (GDAL_FOUND) - - MESSAGE(GDAL_INCLUDE_DIR=${GDAL_INCLUDE_DIR}) - MESSAGE(GDAL_LIBRARY=${GDAL_LIBRARY}) - MESSAGE(FATAL_ERROR "Could not find GDAL") - -ENDIF (GDAL_FOUND) + # MESSAGE("DBG GDAL_VERSION ${GDAL_VERSION}") + # MESSAGE("DBG GDAL_VERSION_MAJOR ${GDAL_VERSION_MAJOR}") + # MESSAGE("DBG GDAL_VERSION_MINOR ${GDAL_VERSION_MINOR}") + + # check for gdal version + # version 1.2.5 is known NOT to be supported (missing CPL_STDCALL macro) + # According to INSTALL, 2.1+ is required + IF (GDAL_VERSION_MAJOR LESS 3) + MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.0 or higher.") + ENDIF (GDAL_VERSION_MAJOR LESS 3) + #IF ( (GDAL_VERSION_MAJOR EQUAL 2) AND (GDAL_VERSION_MINOR LESS 1) ) + # MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 2.1 or higher.") + #ENDIF( (GDAL_VERSION_MAJOR EQUAL 2) AND (GDAL_VERSION_MINOR LESS 1) ) + IF ( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR EQUAL 0) AND (GDAL_VERSION_MICRO LESS 3) ) + MESSAGE (FATAL_ERROR "GDAL version is too old (${GDAL_VERSION}). Use 3.0.3 or higher.") + ENDIF( (GDAL_VERSION_MAJOR EQUAL 3) AND (GDAL_VERSION_MINOR EQUAL 0) AND (GDAL_VERSION_MICRO LESS 3) ) + + # set INCLUDE_DIR to prefix+include + EXEC_PROGRAM(${GDAL_CONFIG} + ARGS --prefix + OUTPUT_VARIABLE GDAL_PREFIX) + #SET(GDAL_INCLUDE_DIR ${GDAL_PREFIX}/include CACHE STRING INTERNAL) + FIND_PATH(GDAL_INCLUDE_DIR + gdal.h + ${GDAL_PREFIX}/include/gdal + ${GDAL_PREFIX}/include + /usr/local/include + /usr/include + ) + + ## extract link dirs for rpath + EXEC_PROGRAM(${GDAL_CONFIG} + ARGS --libs + OUTPUT_VARIABLE GDAL_CONFIG_LIBS ) + + ## split off the link dirs (for rpath) + ## use regular expression to match wildcard equivalent "-L*" + ## with is a space or a semicolon + STRING(REGEX MATCHALL "[-][L]([^ ;])+" + GDAL_LINK_DIRECTORIES_WITH_PREFIX + "${GDAL_CONFIG_LIBS}" ) + # MESSAGE("DBG GDAL_LINK_DIRECTORIES_WITH_PREFIX=${GDAL_LINK_DIRECTORIES_WITH_PREFIX}") + + ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES + + IF (GDAL_LINK_DIRECTORIES_WITH_PREFIX) + STRING(REGEX REPLACE "[-][L]" "" GDAL_LINK_DIRECTORIES ${GDAL_LINK_DIRECTORIES_WITH_PREFIX} ) + ENDIF (GDAL_LINK_DIRECTORIES_WITH_PREFIX) + + ## split off the name + ## use regular expression to match wildcard equivalent "-l*" + ## with is a space or a semicolon + STRING(REGEX MATCHALL "[-][l]([^ ;])+" + GDAL_LIB_NAME_WITH_PREFIX + "${GDAL_CONFIG_LIBS}" ) + # MESSAGE("DBG GDAL_LIB_NAME_WITH_PREFIX=${GDAL_LIB_NAME_WITH_PREFIX}") + + + ## remove prefix -l because we need the pure name + + IF (GDAL_LIB_NAME_WITH_PREFIX) + STRING(REGEX REPLACE "[-][l]" "" GDAL_LIB_NAME ${GDAL_LIB_NAME_WITH_PREFIX} ) + ENDIF (GDAL_LIB_NAME_WITH_PREFIX) + + IF (APPLE) + IF (NOT GDAL_LIBRARY) + # work around empty GDAL_LIBRARY left by framework check + # while still preserving user setting if given + # ***FIXME*** need to improve framework check so below not needed + SET(GDAL_LIBRARY ${GDAL_LINK_DIRECTORIES}/lib${GDAL_LIB_NAME}.dylib CACHE STRING INTERNAL FORCE) + ENDIF (NOT GDAL_LIBRARY) + ELSE (APPLE) + FIND_LIBRARY(GDAL_LIBRARY NAMES ${GDAL_LIB_NAME} gdal PATHS ${GDAL_LINK_DIRECTORIES}/lib ${GDAL_LINK_DIRECTORIES}) + ENDIF (APPLE) + + ELSE(GDAL_CONFIG) + MESSAGE("FindGDAL.cmake: gdal-config not found. Please set it manually. GDAL_CONFIG=${GDAL_CONFIG}") + ENDIF(GDAL_CONFIG) + ENDIF (NOT GDAL_INCLUDE_DIR OR NOT GDAL_LIBRARY OR NOT GDAL_CONFIG) + ENDIF(UNIX) + ENDIF(WIN32) + + + IF (GDAL_INCLUDE_DIR AND GDAL_LIBRARY) + SET(GDAL_FOUND TRUE) + ENDIF (GDAL_INCLUDE_DIR AND GDAL_LIBRARY) + + IF (GDAL_FOUND) + IF (NOT GDAL_FIND_QUIETLY) + FILE(READ ${GDAL_INCLUDE_DIR}/gdal_version.h gdal_version) + STRING(REGEX REPLACE "^.*GDAL_RELEASE_NAME +\"([^\"]+)\".*$" "\\1" GDAL_RELEASE_NAME "${gdal_version}") + + MESSAGE(STATUS "Found GDAL: ${GDAL_LIBRARY} (${GDAL_RELEASE_NAME})") + ENDIF (NOT GDAL_FIND_QUIETLY) + add_library(GDAL::GDAL UNKNOWN IMPORTED) + target_link_libraries(GDAL::GDAL INTERFACE ${GDAL_LIBRARY}) + target_include_directories(GDAL::GDAL INTERFACE ${GDAL_INCLUDE_DIR}) + set_target_properties(GDAL::GDAL PROPERTIES IMPORTED_LOCATION ${GDAL_LIBRARY}) + ELSE (GDAL_FOUND) + + MESSAGE(GDAL_INCLUDE_DIR=${GDAL_INCLUDE_DIR}) + MESSAGE(GDAL_LIBRARY=${GDAL_LIBRARY}) + MESSAGE(FATAL_ERROR "Could not find GDAL") + + ENDIF (GDAL_FOUND) +endif() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 79fb3e24394e..6f5c2ee77b0d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2248,7 +2248,6 @@ target_include_directories(qgis_core SYSTEM PUBLIC ${${QT_VERSION_BASE}Sql_INCLUDE_DIRS} ${${QT_VERSION_BASE}Concurrent_INCLUDE_DIRS} ${PROJ_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} # before GEOS for case-insensitive filesystems ${GEOS_INCLUDE_DIR} @@ -2419,7 +2418,7 @@ target_link_libraries(qgis_core ${QTKEYCHAIN_LIBRARY} ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} + GDAL::GDAL ${SPATIALINDEX_LIBRARY} ${EXPAT_LIBRARY} ${SQLITE3_LIBRARY} diff --git a/src/crssync/CMakeLists.txt b/src/crssync/CMakeLists.txt index 94d2fc9d7c4a..3ff013abb801 100644 --- a/src/crssync/CMakeLists.txt +++ b/src/crssync/CMakeLists.txt @@ -11,7 +11,6 @@ else () target_link_libraries(crssync qgis_core ${PROJ_LIBRARY} - ${GDAL_LIBRARY} ) if(MSVC AND NOT USING_NMAKE) diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index c4691d843172..01ab9bf9e83a 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -44,7 +44,6 @@ target_link_libraries(qgis_process ${QT_VERSION_BASE}::Core ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} ) if (WITH_3D) diff --git a/src/providers/grass/CMakeLists.txt b/src/providers/grass/CMakeLists.txt index d4902fcf0a80..5234605f9893 100644 --- a/src/providers/grass/CMakeLists.txt +++ b/src/providers/grass/CMakeLists.txt @@ -8,7 +8,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories (SYSTEM - ${GDAL_INCLUDE_DIR} ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} @@ -128,6 +127,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmibase} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmiclient} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gproj} + GDAL::GDAL ) else() set(GRASS_TARGET_LINK_LIBRARIES${GRASS_BUILD_VERSION} @@ -139,6 +139,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmibase} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmiclient} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gproj} + GDAL::GDAL ) endif() @@ -214,14 +215,14 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) target_link_libraries(qgis.d.rast${GRASS_BUILD_VERSION} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gis} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} - ${GDAL_LIBRARY} + GDAL::GDAL ) else() target_link_libraries(qgis.d.rast${GRASS_BUILD_VERSION} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gis} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_raster} - ${GDAL_LIBRARY} + GDAL::GDAL ) endif() @@ -234,7 +235,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gis} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gproj} - ${GDAL_LIBRARY} + GDAL::GDAL ) else() target_link_libraries(qgis.g.info${GRASS_BUILD_VERSION} @@ -242,7 +243,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gproj} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_raster} - ${GDAL_LIBRARY} + GDAL::GDAL ) endif() if (UNIX) @@ -261,7 +262,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) qgisgrass${GRASS_BUILD_VERSION} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gis} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} - ${GDAL_LIBRARY} + GDAL::GDAL qgis_core ) else() @@ -270,7 +271,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_gis} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_datetime} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_raster} - ${GDAL_LIBRARY} + GDAL::GDAL qgis_core ) endif() @@ -289,7 +290,7 @@ macro(ADD_GRASSLIB GRASS_BUILD_VERSION) ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_vect} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmibase} ${GRASS_LIBRARY${GRASS_BUILD_VERSION}_dbmiclient} - ${GDAL_LIBRARY} + GDAL::GDAL qgis_core ) diff --git a/src/providers/mdal/CMakeLists.txt b/src/providers/mdal/CMakeLists.txt index 77828ef26239..ad87a66d8c37 100644 --- a/src/providers/mdal/CMakeLists.txt +++ b/src/providers/mdal/CMakeLists.txt @@ -208,11 +208,6 @@ if (HDF5_FOUND) target_compile_definitions(provider_mdal PRIVATE ${HDF5_DEFINITIONS}) endif() -if (GDAL_FOUND) - target_include_directories(provider_mdal PRIVATE ${GDAL_INCLUDE_DIR}) - target_link_libraries(provider_mdal ${GDAL_LIBRARY} ) -endif() - if (NETCDF_FOUND) target_include_directories(provider_mdal PRIVATE ${NETCDF_INCLUDE_DIR}) target_link_libraries(provider_mdal ${NETCDF_LIBRARY} ) diff --git a/src/providers/pdal/CMakeLists.txt b/src/providers/pdal/CMakeLists.txt index 257511d748b5..38509d1c235b 100644 --- a/src/providers/pdal/CMakeLists.txt +++ b/src/providers/pdal/CMakeLists.txt @@ -183,13 +183,12 @@ if (PDAL_2_5_OR_HIGHER) target_include_directories(pdal_wrench PRIVATE ${PDAL_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/external ) target_link_libraries (pdal_wrench PRIVATE ${PDAL_LIBRARIES} - ${GDAL_LIBRARY} + GDAL::GDAL Threads::Threads ) diff --git a/src/providers/wms/CMakeLists.txt b/src/providers/wms/CMakeLists.txt index 73f442236e53..267ab85b486c 100644 --- a/src/providers/wms/CMakeLists.txt +++ b/src/providers/wms/CMakeLists.txt @@ -80,7 +80,6 @@ else() target_link_libraries(provider_wms qgis_core - ${GDAL_LIBRARY} # for OGR_G_CreateGeometryFromJson() ) if (WITH_GUI) diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index be5855c56d0f..644dae453213 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -116,7 +116,6 @@ target_link_libraries(qgis_server ${PROJ_LIBRARY} ${FCGI_LIBRARY} ${POSTGRES_LIBRARY} - ${GDAL_LIBRARY} ${QCA_LIBRARY} ) diff --git a/tests/src/3d/sandbox/CMakeLists.txt b/tests/src/3d/sandbox/CMakeLists.txt index 72afff6cdb5b..af0fef2280b5 100644 --- a/tests/src/3d/sandbox/CMakeLists.txt +++ b/tests/src/3d/sandbox/CMakeLists.txt @@ -18,7 +18,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} include_directories(SYSTEM ${QT_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${QT5_3DEXTRA_INCLUDE_DIR} ) @@ -37,7 +36,6 @@ target_link_libraries(qgis_3d_sandbox ${QT_VERSION_BASE}::Test ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} ${QWT_LIBRARY} qgis_core qgis_3d diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 7546f3c8c428..1d321891b966 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -3,7 +3,6 @@ include_directories( ) include_directories(BEFORE SYSTEM ${PROJ_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ) include_directories(SYSTEM ${POSTGRES_INCLUDE_DIR} @@ -31,7 +30,6 @@ macro (ADD_QGIS_GRASS_TEST grass_build_version testname testsrc) ${QT_VERSION_BASE}::Test ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} qgis_core qgis_test qgisgrass${grass_build_version} From 57fe9e0945e80f6f5c95670dab135c50d10d798b Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 18 Sep 2023 10:15:29 +0200 Subject: [PATCH 013/151] SERVER: Fix server utils service URL wrong MAP replace Fixes #54533 --- src/server/qgsserverprojectutils.cpp | 12 +++++++++++- tests/src/python/test_qgsserver_projectutils.py | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/server/qgsserverprojectutils.cpp b/src/server/qgsserverprojectutils.cpp index 742fa26bbe2f..82473c0ba13c 100644 --- a/src/server/qgsserverprojectutils.cpp +++ b/src/server/qgsserverprojectutils.cpp @@ -414,7 +414,17 @@ QString QgsServerProjectUtils::serviceUrl( const QString &service, const QgsServ } // https://docs.qgis.org/3.16/en/docs/server_manual/services.html#wms-map - const QString map = QUrlQuery( request.originalUrl().query().replace( QLatin1String( "MAP" ), QStringLiteral( "MAP" ), Qt::CaseInsensitive ) ).queryItemValue( QStringLiteral( "MAP" ) ); + const QUrlQuery query { request.originalUrl().query() }; + const QList> constItems { query.queryItems( ) }; + QString map; + for ( const QPair &item : std::as_const( constItems ) ) + { + if ( 0 == item.first.compare( QLatin1String( "MAP" ), Qt::CaseSensitivity::CaseInsensitive ) ) + { + map = item.second; + break; + } + } if ( ! map.isEmpty() ) { diff --git a/tests/src/python/test_qgsserver_projectutils.py b/tests/src/python/test_qgsserver_projectutils.py index dd07ad64d462..7159bbf34ce5 100644 --- a/tests/src/python/test_qgsserver_projectutils.py +++ b/tests/src/python/test_qgsserver_projectutils.py @@ -15,9 +15,9 @@ import os from qgis.core import QgsProject -from qgis.server import QgsServerProjectUtils +from qgis.server import QgsServerProjectUtils, QgsServerSettings, QgsBufferServerRequest from qgis.testing import unittest, start_app - +from unittest import mock from utilities import unitTestDataPath start_app() @@ -82,6 +82,16 @@ def test_wcslayersids(self): self.assertEqual(expected, result) + @mock.patch.dict(os.environ, {"QGIS_SERVER_WFS_SERVICE_URL": "http://localhost:8080"}) + def test_map_uppercase_replace(self): + """Test issue GH #54533 MAP replacementin URL arg""" + + settings = QgsServerSettings() + self.assertIsNotNone(settings.serviceUrl('WFS')) + request = QgsBufferServerRequest('http://localhost:8080/?MaP=/mAp.qgs&SERVICE=WMS&REQUEST=GetMap') + service_url = QgsServerProjectUtils.serviceUrl('WFS', request, settings) + self.assertEqual(service_url, 'http://localhost:8080/?MAP=/mAp.qgs') + if __name__ == '__main__': unittest.main() From 1b5794302dc90b1b41daa6f1f92b9021432aefcb Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 10:23:09 +0200 Subject: [PATCH 014/151] Do not require pkgconfig --- cmake/FindSpatiaLite.cmake | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmake/FindSpatiaLite.cmake b/cmake/FindSpatiaLite.cmake index acb3b0ed4ecc..0cb5579ad5df 100644 --- a/cmake/FindSpatiaLite.cmake +++ b/cmake/FindSpatiaLite.cmake @@ -7,15 +7,18 @@ # CMake module to search for SpatiaLite library # # If it's found it sets SPATIALITE_FOUND to TRUE -# and following variables are set: -# SPATIALITE_INCLUDE_DIR -# SPATIALITE_LIBRARY +# and adds the following target +# +# spatialite::spatialite -find_package(PkgConfig REQUIRED) -pkg_search_module(PC_SPATIALITE IMPORTED_TARGET spatialite) +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_search_module(PC_SPATIALITE IMPORTED_TARGET spatialite) +endif() if(PC_SPATIALITE_FOUND) add_library(spatialite::spatialite ALIAS PkgConfig::PC_SPATIALITE) + set(SPATIALITE_FOUND TRUE) else() # This macro checks if the symbol exists include(CheckLibraryExists) From 789e92b05436f3d1f6d3ca8950a74265892090d3 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 10:35:20 +0200 Subject: [PATCH 015/151] [cmake] Use proj cmake configuration --- cmake/FindProj.cmake | 158 ++++++++++++----------- src/core/CMakeLists.txt | 3 +- src/plugins/grass/CMakeLists.txt | 1 - src/providers/grass/CMakeLists.txt | 1 - src/providers/hana/CMakeLists.txt | 1 - src/quickgui/CMakeLists.txt | 1 - src/quickgui/plugin/CMakeLists.txt | 1 - tests/src/providers/grass/CMakeLists.txt | 1 - tests/src/quickgui/CMakeLists.txt | 1 - tests/src/quickgui/app/CMakeLists.txt | 1 - 10 files changed, 85 insertions(+), 84 deletions(-) diff --git a/cmake/FindProj.cmake b/cmake/FindProj.cmake index d164a439e9a4..3feb785fa6be 100644 --- a/cmake/FindProj.cmake +++ b/cmake/FindProj.cmake @@ -7,9 +7,9 @@ # CMake module to search for Proj library # # If it's found it sets PROJ_FOUND to TRUE -# and following variables are set: -# PROJ_INCLUDE_DIR -# PROJ_LIBRARY +# and adds the following target +# +# PROJ::proj # FIND_PATH and FIND_LIBRARY normally search standard locations # before the specified paths. To search non-standard paths first, @@ -18,79 +18,89 @@ # locations. When an earlier FIND_* succeeds, subsequent FIND_*s # searching for the same item do nothing. -# try to use framework on mac -# want clean framework path, not unix compatibility path -IF (APPLE) - IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" - OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" - OR NOT CMAKE_FIND_FRAMEWORK) - SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) - SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) - #FIND_PATH(PROJ_INCLUDE_DIR PROJ/proj_api.h) - FIND_LIBRARY(PROJ_LIBRARY PROJ) - IF (PROJ_LIBRARY) - # FIND_PATH doesn't add "Headers" for a framework - SET (PROJ_INCLUDE_DIR ${PROJ_LIBRARY}/Headers CACHE PATH "Path to a file.") - ENDIF (PROJ_LIBRARY) - SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) - ENDIF () -ENDIF (APPLE) +find_package(PROJ CONFIG) -FIND_PATH(PROJ_INCLUDE_DIR proj_api.h - "$ENV{INCLUDE}" - "$ENV{LIB_DIR}/include" - ) -IF (NOT PROJ_INCLUDE_DIR) - FIND_PATH(PROJ_INCLUDE_DIR proj.h +# Fallback for systems where proj-targets.cmake is not present. +if(NOT PROJ_FOUND) + # try to use framework on mac + # want clean framework path, not unix compatibility path + IF (APPLE) + IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" + OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" + OR NOT CMAKE_FIND_FRAMEWORK) + SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) + SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) + #FIND_PATH(PROJ_INCLUDE_DIR PROJ/proj_api.h) + FIND_LIBRARY(PROJ_LIBRARY PROJ) + IF (PROJ_LIBRARY) + # FIND_PATH doesn't add "Headers" for a framework + SET (PROJ_INCLUDE_DIR ${PROJ_LIBRARY}/Headers CACHE PATH "Path to a file.") + ENDIF (PROJ_LIBRARY) + SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) + ENDIF () + ENDIF (APPLE) + + FIND_PATH(PROJ_INCLUDE_DIR proj_api.h "$ENV{INCLUDE}" "$ENV{LIB_DIR}/include" ) -ENDIF (NOT PROJ_INCLUDE_DIR) - -FIND_LIBRARY(PROJ_LIBRARY NAMES proj_i proj PATHS - "$ENV{LIB}" - "$ENV{LIB_DIR}/lib" - ) - -IF (PROJ_INCLUDE_DIR AND PROJ_LIBRARY) - SET(PROJ_FOUND TRUE) -ENDIF (PROJ_INCLUDE_DIR AND PROJ_LIBRARY) - -IF (PROJ_FOUND) - IF (EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) - FILE(READ ${PROJ_INCLUDE_DIR}/proj.h proj_version) - STRING(REGEX REPLACE "^.*PROJ_VERSION_MAJOR +([0-9]+).*$" "\\1" PROJ_VERSION_MAJOR "${proj_version}") - STRING(REGEX REPLACE "^.*PROJ_VERSION_MINOR +([0-9]+).*$" "\\1" PROJ_VERSION_MINOR "${proj_version}") - STRING(REGEX REPLACE "^.*PROJ_VERSION_PATCH +([0-9]+).*$" "\\1" PROJ_VERSION_PATCH "${proj_version}") - STRING(CONCAT PROJ_VERSION_STR "(" ${PROJ_VERSION_MAJOR} "." ${PROJ_VERSION_MINOR} "." ${PROJ_VERSION_PATCH} ")") - IF ((PROJ_VERSION_MAJOR EQUAL 7) AND ((PROJ_VERSION_MINOR LESS 2) OR (PROJ_VERSION_MAJOR LESS 7))) - MESSAGE (FATAL_ERROR "Cannot build QGIS using Proj ${PROJ_VERSION_MAJOR}.${PROJ_VERSION_MINOR}.${PROJ_VERSION_PATCH} Use 7.2.0 or higher.") - ENDIF ((PROJ_VERSION_MAJOR EQUAL 7) AND ((PROJ_VERSION_MINOR LESS 2) OR (PROJ_VERSION_MAJOR LESS 7))) - ELSE(EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) - FILE(READ ${PROJ_INCLUDE_DIR}/proj_api.h proj_version) - STRING(REGEX REPLACE "^.*PJ_VERSION ([0-9]+).*$" "\\1" PJ_VERSION "${proj_version}") - - # This will break if 4.10.0 ever will be released (highly unlikely) - STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\1" PROJ_VERSION_MAJOR "${PJ_VERSION}") - STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\2" PROJ_VERSION_MINOR "${PJ_VERSION}") - STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\3" PROJ_VERSION_PATCH "${PJ_VERSION}") - STRING(CONCAT PROJ_VERSION_STR "(" ${PROJ_VERSION_MAJOR} "." ${PROJ_VERSION_MINOR} "." ${PROJ_VERSION_PATCH} ")") - - # Minimum Proj version required is 4.9.3 - IF ((PROJ_VERSION_MAJOR EQUAL 4) AND ((PROJ_VERSION_MINOR LESS 9) OR ((PROJ_VERSION_MINOR EQUAL 9) AND (PROJ_VERSION_PATCH LESS 3)))) - MESSAGE(FATAL_ERROR "Found Proj: ${PROJ_VERSION_MAJOR}.${PROJ_VERSION_MINOR}.${PROJ_VERSION_PATCH}. Cannot build QGIS using Proj older than 4.9.3.") - ENDIF((PROJ_VERSION_MAJOR EQUAL 4) AND ((PROJ_VERSION_MINOR LESS 9) OR ((PROJ_VERSION_MINOR EQUAL 9) AND (PROJ_VERSION_PATCH LESS 3)))) - ENDIF(EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) - IF (NOT PROJ_FIND_QUIETLY) - MESSAGE(STATUS "Found Proj: ${PROJ_LIBRARY} version ${PROJ_VERSION_MAJOR} ${PROJ_VERSION_STR}") - ENDIF (NOT PROJ_FIND_QUIETLY) - - INCLUDE_DIRECTORIES(BEFORE SYSTEM ${PROJ_INCLUDE_DIR}) - -ELSE (PROJ_FOUND) - - IF (PROJ_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find Proj") - ENDIF (PROJ_FIND_REQUIRED) + IF (NOT PROJ_INCLUDE_DIR) + FIND_PATH(PROJ_INCLUDE_DIR proj.h + "$ENV{INCLUDE}" + "$ENV{LIB_DIR}/include" + ) + ENDIF (NOT PROJ_INCLUDE_DIR) + + FIND_LIBRARY(PROJ_LIBRARY NAMES proj_i proj PATHS + "$ENV{LIB}" + "$ENV{LIB_DIR}/lib" + ) + + IF (PROJ_INCLUDE_DIR AND PROJ_LIBRARY) + SET(PROJ_FOUND TRUE) + ENDIF (PROJ_INCLUDE_DIR AND PROJ_LIBRARY) + + IF (PROJ_FOUND) + IF (EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) + FILE(READ ${PROJ_INCLUDE_DIR}/proj.h proj_version) + STRING(REGEX REPLACE "^.*PROJ_VERSION_MAJOR +([0-9]+).*$" "\\1" PROJ_VERSION_MAJOR "${proj_version}") + STRING(REGEX REPLACE "^.*PROJ_VERSION_MINOR +([0-9]+).*$" "\\1" PROJ_VERSION_MINOR "${proj_version}") + STRING(REGEX REPLACE "^.*PROJ_VERSION_PATCH +([0-9]+).*$" "\\1" PROJ_VERSION_PATCH "${proj_version}") + STRING(CONCAT PROJ_VERSION_STR "(" ${PROJ_VERSION_MAJOR} "." ${PROJ_VERSION_MINOR} "." ${PROJ_VERSION_PATCH} ")") + IF ((PROJ_VERSION_MAJOR EQUAL 7) AND ((PROJ_VERSION_MINOR LESS 2) OR (PROJ_VERSION_MAJOR LESS 7))) + MESSAGE (FATAL_ERROR "Cannot build QGIS using Proj ${PROJ_VERSION_MAJOR}.${PROJ_VERSION_MINOR}.${PROJ_VERSION_PATCH} Use 7.2.0 or higher.") + ENDIF ((PROJ_VERSION_MAJOR EQUAL 7) AND ((PROJ_VERSION_MINOR LESS 2) OR (PROJ_VERSION_MAJOR LESS 7))) + ELSE(EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) + FILE(READ ${PROJ_INCLUDE_DIR}/proj_api.h proj_version) + STRING(REGEX REPLACE "^.*PJ_VERSION ([0-9]+).*$" "\\1" PJ_VERSION "${proj_version}") + + # This will break if 4.10.0 ever will be released (highly unlikely) + STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\1" PROJ_VERSION_MAJOR "${PJ_VERSION}") + STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\2" PROJ_VERSION_MINOR "${PJ_VERSION}") + STRING(REGEX REPLACE "([0-9])([0-9])([0-9])" "\\3" PROJ_VERSION_PATCH "${PJ_VERSION}") + STRING(CONCAT PROJ_VERSION_STR "(" ${PROJ_VERSION_MAJOR} "." ${PROJ_VERSION_MINOR} "." ${PROJ_VERSION_PATCH} ")") + + # Minimum Proj version required is 4.9.3 + IF ((PROJ_VERSION_MAJOR EQUAL 4) AND ((PROJ_VERSION_MINOR LESS 9) OR ((PROJ_VERSION_MINOR EQUAL 9) AND (PROJ_VERSION_PATCH LESS 3)))) + MESSAGE(FATAL_ERROR "Found Proj: ${PROJ_VERSION_MAJOR}.${PROJ_VERSION_MINOR}.${PROJ_VERSION_PATCH}. Cannot build QGIS using Proj older than 4.9.3.") + ENDIF((PROJ_VERSION_MAJOR EQUAL 4) AND ((PROJ_VERSION_MINOR LESS 9) OR ((PROJ_VERSION_MINOR EQUAL 9) AND (PROJ_VERSION_PATCH LESS 3)))) + ENDIF(EXISTS ${PROJ_INCLUDE_DIR}/proj.h AND EXISTS ${PROJ_INCLUDE_DIR}/proj_experimental.h) + IF (NOT PROJ_FIND_QUIETLY) + MESSAGE(STATUS "Found Proj: ${PROJ_LIBRARY} version ${PROJ_VERSION_MAJOR} ${PROJ_VERSION_STR}") + ENDIF (NOT PROJ_FIND_QUIETLY) + + INCLUDE_DIRECTORIES(BEFORE SYSTEM ${PROJ_INCLUDE_DIR}) -ENDIF (PROJ_FOUND) + add_library(PROJ::proj UNKNOWN IMPORTED) + target_link_libraries(PROJ::proj INTERFACE ${PROJ_LIBRARY}) + target_include_directories(PROJ::proj INTERFACE ${PROJ_INCLUDE_DIR}) + set_target_properties(PROJ::proj PROPERTIES IMPORTED_LOCATION ${PROJ_LIBRARY}) + + ELSE (PROJ_FOUND) + + IF (PROJ_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find Proj") + ENDIF (PROJ_FIND_REQUIRED) + + ENDIF (PROJ_FOUND) +endif() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 79fb3e24394e..b7da68deb8a0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2247,7 +2247,6 @@ target_include_directories(qgis_core SYSTEM PUBLIC ${${QT_VERSION_BASE}Network_INCLUDE_DIRS} ${${QT_VERSION_BASE}Sql_INCLUDE_DIRS} ${${QT_VERSION_BASE}Concurrent_INCLUDE_DIRS} - ${PROJ_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} # before GEOS for case-insensitive filesystems @@ -2417,7 +2416,6 @@ target_link_libraries(qgis_core ${OPTIONAL_QTWEBKIT} ${QCA_LIBRARY} ${QTKEYCHAIN_LIBRARY} - ${PROJ_LIBRARY} ${GEOS_LIBRARY} ${GDAL_LIBRARY} ${SPATIALINDEX_LIBRARY} @@ -2427,6 +2425,7 @@ target_link_libraries(qgis_core ${Protobuf_LITE_LIBRARY} ${ZLIB_LIBRARIES} ${EXIV2_LIBRARY} + PROJ::proj ) if (WITH_DRACO) diff --git a/src/plugins/grass/CMakeLists.txt b/src/plugins/grass/CMakeLists.txt index a19a21c87919..9bfeccab3fd3 100644 --- a/src/plugins/grass/CMakeLists.txt +++ b/src/plugins/grass/CMakeLists.txt @@ -82,7 +82,6 @@ include_directories( ) include_directories(SYSTEM ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/providers/grass/CMakeLists.txt b/src/providers/grass/CMakeLists.txt index d4902fcf0a80..056615152139 100644 --- a/src/providers/grass/CMakeLists.txt +++ b/src/providers/grass/CMakeLists.txt @@ -9,7 +9,6 @@ include_directories( ) include_directories (SYSTEM ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/providers/hana/CMakeLists.txt b/src/providers/hana/CMakeLists.txt index 9295c55fd538..431b8c098527 100644 --- a/src/providers/hana/CMakeLists.txt +++ b/src/providers/hana/CMakeLists.txt @@ -67,7 +67,6 @@ include_directories( ) include_directories (SYSTEM - ${PROJ_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} ${ODBC_INCLUDE_DIRS} ${QCA_INCLUDE_DIR} diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt index e1f1e0a85266..b7168b891f16 100644 --- a/src/quickgui/CMakeLists.txt +++ b/src/quickgui/CMakeLists.txt @@ -27,7 +27,6 @@ include_directories( include_directories(SYSTEM ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt index 585e5edf71dc..a1438d19e598 100644 --- a/src/quickgui/plugin/CMakeLists.txt +++ b/src/quickgui/plugin/CMakeLists.txt @@ -29,7 +29,6 @@ include_directories( include_directories(SYSTEM ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 7546f3c8c428..dc1c4058018b 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -2,7 +2,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/src/providers/grass ) include_directories(BEFORE SYSTEM - ${PROJ_INCLUDE_DIR} ${GDAL_INCLUDE_DIR} ) include_directories(SYSTEM diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index ea16e3f8a109..f55811fbc887 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -16,7 +16,6 @@ include_directories( ) include_directories(SYSTEM - ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt index dd2c90a8258f..d91d69a27a72 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -22,7 +22,6 @@ include_directories( include_directories(SYSTEM ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} From 381afa8b7060be29b3ad49b4c01c200d8b06cfc7 Mon Sep 17 00:00:00 2001 From: Paul Blottiere Date: Mon, 18 Sep 2023 10:59:04 +0200 Subject: [PATCH 016/151] Update src/core/proj/qgscoordinatetransform.cpp Co-authored-by: Even Rouault --- src/core/proj/qgscoordinatetransform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/proj/qgscoordinatetransform.cpp b/src/core/proj/qgscoordinatetransform.cpp index b78bb2a63494..2bca58ad5798 100644 --- a/src/core/proj/qgscoordinatetransform.cpp +++ b/src/core/proj/qgscoordinatetransform.cpp @@ -666,7 +666,7 @@ QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &r // check if result bbox is geographic and is crossing 180/-180 line: ie. min X is before the 180° and max X is after the -180° bool doHandle180Crossover = false; - if ( nXPoints > 0 && x.size() >= nXPoints ) + if ( nXPoints > 0 ) { const double xMin = std::fmod( x[0], 180.0 ); const double xMax = std::fmod( x[nXPoints - 1], 180.0 ); From c9d33df379d77290306cb8e436608f5a24f2049b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 17 Sep 2023 17:01:47 +0200 Subject: [PATCH 017/151] [OAPIF] Handle features with complex attribute of QMap type (fixes #54275) --- .../qgsbackgroundcachedfeatureiterator.cpp | 5 +- .../wfs/qgsbackgroundcachedshareddata.cpp | 11 +++++ tests/src/python/test_provider_oapif.py | 48 ++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp b/src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp index 08b0f2d5a530..b5dbeea8a294 100644 --- a/src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp +++ b/src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -944,7 +945,9 @@ void QgsBackgroundCachedFeatureIterator::copyFeature( const QgsFeature &srcFeatu else if ( QgsWFSUtils::isCompatibleType( v.type(), fieldType ) ) dstFeature.setAttribute( i, v ); else if ( fieldType == QVariant::DateTime && !QgsVariantUtils::isNull( v ) ) - dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) ); + dstFeature.setAttribute( i, QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ); + else if ( fieldType == QVariant::Map && !QgsVariantUtils::isNull( v ) ) + dstFeature.setAttribute( i, QJsonDocument::fromJson( v.toString().toUtf8() ).toVariant() ); else dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fieldType, v.toString() ) ); } diff --git a/src/providers/wfs/qgsbackgroundcachedshareddata.cpp b/src/providers/wfs/qgsbackgroundcachedshareddata.cpp index eb1753626389..5b25f23c413e 100644 --- a/src/providers/wfs/qgsbackgroundcachedshareddata.cpp +++ b/src/providers/wfs/qgsbackgroundcachedshareddata.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -628,6 +629,16 @@ void QgsBackgroundCachedSharedData::serializeFeatures( QVector Date: Fri, 15 Sep 2023 16:13:15 +0200 Subject: [PATCH 018/151] qgsgeos.cpp: fix a -Wunused-variable warning --- 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 015c8d75e330..92b4b8641fd3 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -1032,7 +1032,7 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co else if ( qgsgeometry_cast( splitGeom.get() ) ) { splitPoints = std::make_unique< QgsMultiPoint >(); - if ( QgsPoint *point = qgsgeometry_cast( splitGeom.get() ) ) + if ( qgsgeometry_cast( splitGeom.get() ) ) { splitPoints->addGeometry( qgsgeometry_cast( splitGeom.release() ) ); } From 1103a371ee14443e5ba72d6159c0b9d067e3813f Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 18 Sep 2023 12:18:43 +0200 Subject: [PATCH 019/151] RASTER properties dlg sync opacity Fixes #54496 --- src/gui/raster/qgsrasterlayerproperties.cpp | 2 ++ .../python/test_qgsrasterlayerproperties.py | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/gui/raster/qgsrasterlayerproperties.cpp b/src/gui/raster/qgsrasterlayerproperties.cpp index f8d95af54d77..76bc5c82b23d 100644 --- a/src/gui/raster/qgsrasterlayerproperties.cpp +++ b/src/gui/raster/qgsrasterlayerproperties.cpp @@ -753,6 +753,8 @@ void QgsRasterLayerProperties::sync() if ( !provider ) return; + mRasterTransparencyWidget->syncToLayer(); + if ( provider->dataType( 1 ) == Qgis::DataType::ARGB32 || provider->dataType( 1 ) == Qgis::DataType::ARGB32_Premultiplied ) { diff --git a/tests/src/python/test_qgsrasterlayerproperties.py b/tests/src/python/test_qgsrasterlayerproperties.py index 1f50a97e28c7..13ae02f37ad3 100644 --- a/tests/src/python/test_qgsrasterlayerproperties.py +++ b/tests/src/python/test_qgsrasterlayerproperties.py @@ -25,6 +25,7 @@ ) import unittest from qgis.testing import start_app, QgisTestCase +import tempfile from utilities import unitTestDataPath @@ -98,6 +99,29 @@ def createWidget(self, self.assertEqual(MyFactory.COUNT, 1, msg='Custom QgsMapLayerConfigWidget::createWidget(...) not called') self.assertEqual(MyWidget.COUNT, 1, msg='Custom QgsMapLayerConfigWidget::apply() not called') + def test_transparency_load(self): + """Test issue GH #54496""" + + myCanvas = QgsMapCanvas() + myPath = pathlib.Path(unitTestDataPath('raster')) / 'band1_float32_noct_epsg4326.tif' + myRasterLayer = QgsRasterLayer(myPath.as_posix(), myPath.name) + + assert myRasterLayer.isValid(), f'Raster not loaded {myPath}' + + dialog = QgsRasterLayerProperties(myRasterLayer, myCanvas) + + with tempfile.NamedTemporaryFile(suffix='.qml') as qml_file_object: + renderer = myRasterLayer.renderer() + renderer.setOpacity(0.5) + self.assertTrue(myRasterLayer.saveNamedStyle(qml_file_object.name)[1]) + myRasterLayer.loadNamedStyle(qml_file_object.name) + dialog.syncToLayer() + renderer = myRasterLayer.renderer() + self.assertEqual(renderer.opacity(), 0.5) + dialog.apply() + renderer = myRasterLayer.renderer() + self.assertEqual(renderer.opacity(), 0.5) + if __name__ == '__main__': unittest.main() From 654bcacb2952010da6355df2cd92db63cc27fa2a Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 13:44:46 +0200 Subject: [PATCH 020/151] Add doc --- cmake/FindSpatiaLite.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/FindSpatiaLite.cmake b/cmake/FindSpatiaLite.cmake index 0cb5579ad5df..74f46e3dde91 100644 --- a/cmake/FindSpatiaLite.cmake +++ b/cmake/FindSpatiaLite.cmake @@ -20,6 +20,8 @@ if(PC_SPATIALITE_FOUND) add_library(spatialite::spatialite ALIAS PkgConfig::PC_SPATIALITE) set(SPATIALITE_FOUND TRUE) else() + # Fallback for systems without PkgConfig, e.g. OSGeo4W + # This macro checks if the symbol exists include(CheckLibraryExists) From 8d71057294f6e13184aa83e1bd598f3851e53708 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 18 Sep 2023 16:14:40 +0200 Subject: [PATCH 021/151] SERVER: fix getpring atlas with DD follow theme Fix #54475 when an atlas map has a data-defined follow theme with an expression which depends on the atlas feature. --- src/server/services/wms/qgswmsrenderer.cpp | 18 +- src/server/services/wms/qgswmsrenderer.h | 5 +- .../test_qgsserver_wms_getprint_maptheme.py | 46 + .../qgis_server/test_project_mapthemes.qgs | 4891 ++++++++--------- 4 files changed, 2437 insertions(+), 2523 deletions(-) diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index 21708feff237..6877f2045229 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -449,15 +449,19 @@ namespace QgsWms } filterString.append( " )" ); + } atlas->setFilterFeatures( true ); + QString errorString; atlas->setFilterExpression( filterString, errorString ); + if ( !errorString.isEmpty() ) { throw QgsException( QStringLiteral( "An error occurred during the Atlas print: %1" ).arg( errorString ) ); } + } } @@ -691,7 +695,7 @@ namespace QgsWms return tempOutputFile.readAll(); } - bool QgsRenderer::configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings, bool atlasPrint ) + bool QgsRenderer::configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings, QgsLayoutAtlas *atlas ) { c->renderContext().setSelectionColor( mapSettings.selectionColor() ); @@ -713,7 +717,7 @@ namespace QgsWms cMapParams.mLayers = mWmsParameters.composerMapParameters( -1 ).mLayers; } - if ( !atlasPrint || !map->atlasDriven() ) //No need to extent, scal, rotation set with atlas feature + if ( !atlas || !map->atlasDriven() ) //No need to extent, scale, rotation set with atlas feature { //map extent is mandatory if ( !cMapParams.mHasExtent ) @@ -751,6 +755,7 @@ namespace QgsWms if ( !map->keepLayerSet() ) { + QList layerSet; for ( const auto &layer : std::as_const( cMapParams.mLayers ) ) @@ -798,6 +803,15 @@ namespace QgsWms QMap layersStyle; if ( map->followVisibilityPreset() ) { + + if ( atlas ) + { + // Possibly triggers a refresh of the DD visibility preset (theme) name + // see issue GH #54475 + atlas->updateFeatures(); + atlas->first(); + } + const QString presetName = map->followVisibilityPresetName(); if ( layerSet.isEmpty() ) { diff --git a/src/server/services/wms/qgswmsrenderer.h b/src/server/services/wms/qgswmsrenderer.h index fa8d9277957f..28fcb345c812 100644 --- a/src/server/services/wms/qgswmsrenderer.h +++ b/src/server/services/wms/qgswmsrenderer.h @@ -20,6 +20,7 @@ #ifndef QGSWMSRENDERER_H #define QGSWMSRENDERER_H +#include "qgslayoutatlas.h" #include "qgsserversettings.h" #include "qgswmsparameters.h" #include "qgswmsrendercontext.h" @@ -324,10 +325,10 @@ namespace QgsWms * Configures the print layout for the GetPrint request *\param c the print layout *\param mapSettings the map settings - *\param atlasPrint true if atlas is used for printing + *\param atlas atlas used for printing, maybe NULL *\returns true in case of success */ - bool configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings, bool atlasPrint = false ); + bool configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings, QgsLayoutAtlas *atlas ); void removeTemporaryLayers(); diff --git a/tests/src/python/test_qgsserver_wms_getprint_maptheme.py b/tests/src/python/test_qgsserver_wms_getprint_maptheme.py index 74bdfe893c24..a220cef2f4c7 100644 --- a/tests/src/python/test_qgsserver_wms_getprint_maptheme.py +++ b/tests/src/python/test_qgsserver_wms_getprint_maptheme.py @@ -306,6 +306,52 @@ def test_wms_getprint_maptheme_highlight(self): image = QImage.fromData(response.body(), "PNG") self._assertBlue(image.pixelColor(100, 100)) + def test_wms_getprint_atlas_dd_theme_(self): + """Test a template with atlas DD theme""" + + project = self.project + + # No LAYERS specified + params = { + 'SERVICE': 'WMS', + 'VERSION': '1.3.0', + 'REQUEST': 'GetPrint', + 'TEMPLATE': 'data_defined_theme', + 'FORMAT': 'png', + 'CRS': 'EPSG:4326', + 'DPI': '72', + } + + def _test_red(): + params['ATLAS_PK'] = '2' + + response = QgsBufferServerResponse() + request = QgsBufferServerRequest('?' + '&'.join(["%s=%s" % i for i in params.items()])) + self.server.handleRequest(request, response, project) + + image = QImage.fromData(response.body(), "PNG") + # Expected: white and red + self._assertRed(image.pixelColor(325, 184)) + self._assertWhite(image.pixelColor(685, 150)) + + def _test_green(): + params['ATLAS_PK'] = '4' + + response = QgsBufferServerResponse() + request = QgsBufferServerRequest('?' + '&'.join(["%s=%s" % i for i in params.items()])) + self.server.handleRequest(request, response, project) + + image = QImage.fromData(response.body(), "PNG") + # Expected: green and white + self._assertGreen(image.pixelColor(325, 184)) + self._assertWhite(image.pixelColor(685, 150)) + + # Alternate test to make sure nothing is cached + _test_red() + _test_green() + _test_red() + _test_green() + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/qgis_server/test_project_mapthemes.qgs b/tests/testdata/qgis_server/test_project_mapthemes.qgs index b70cedb35616..5638e35edd4f 100644 --- a/tests/testdata/qgis_server/test_project_mapthemes.qgs +++ b/tests/testdata/qgis_server/test_project_mapthemes.qgs @@ -1,12 +1,12 @@ - + - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -17,43 +17,51 @@ true + - + + + + + - + - + - + - - + - + @@ -64,16 +72,18 @@ points_f8287dca_eb31_4636_bfe2_dc6900639433 points_43fc4229_2098_4413_bb87_52755161fcb9 points_c27022e1_5845_4026_ad1f_6be2c25d03b2 + points_6e958a3a_6884_4fa9_9374_16352136a3bc - + - - - - - - + + + + + + + @@ -89,7 +99,7 @@ 0 - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -105,39 +115,69 @@ - + + + + - + - + - + - + - + - + + degrees + + 0 + 0 + 0 + 0 + + 0 + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + 0 + + + Annotations_50979f9e_3931_47a0_8fe6_02eb3c518de9 @@ -165,6 +205,7 @@ + @@ -183,12 +224,21 @@ + + 1 + 1 + 1 + 0 + + + 1 0 - + 7.09705908663948293 45.0539864893108799 @@ -209,7 +259,7 @@ points_red - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -237,12 +287,13 @@ + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -253,7 +304,7 @@ - + @@ -278,446 +329,319 @@ 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - - - - + + + + - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - + + + + @@ -726,80 +650,53 @@ 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -807,30 +704,30 @@ - + - + - + - + - + - - + + + + + + - - + + - - + + - - + + - + - @@ -894,12 +795,12 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - + + - - + + @@ -908,9 +809,21 @@ def my_form_open(dialog, layer, feature): "fid" - + - + + + 7.11061910251475382 + 44.92642189552130105 + 7.11061910251475382 + 44.92642189552130105 + + + 7.11061910251475382 + 44.92642189552130105 + 7.11061910251475382 + 44.92642189552130105 + points_43fc4229_2098_4413_bb87_52755161fcb9 ./test_project_mapthemes.gpkg|layername=points|subset="style" = 3 @@ -919,7 +832,7 @@ def my_form_open(dialog, layer, feature): points_blue - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -947,12 +860,13 @@ def my_form_open(dialog, layer, feature): + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -963,7 +877,7 @@ def my_form_open(dialog, layer, feature): - + @@ -988,446 +902,316 @@ def my_form_open(dialog, layer, feature): 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - - - - + + + + - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - + + + @@ -1436,80 +1220,53 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1517,61 +1274,557 @@ def my_form_open(dialog, layer, feature): - + - + - + - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + + + + "fid" + + + + + 7.09705829620360973 + 44.92641830444340201 + 7.34967803955078036 + 45.05399322509759941 + + + 7.09705829620360973 + 44.92641830444340201 + 7.34967803955078036 + 45.05399322509759941 + + points_6e958a3a_6884_4fa9_9374_16352136a3bc + ./test_project_mapthemes.gpkg|layername=points + + + + points + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + dataset + + + + + + + + + + + + + + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + - - + + - - + + - - + + - + - @@ -1604,12 +1857,12 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - + + - - + + @@ -1618,9 +1871,21 @@ def my_form_open(dialog, layer, feature): "fid" - + - + + + 7.34314826363512729 + 45.05197759807009561 + 7.34314826363512729 + 45.05197759807009561 + + + 7.34314826363512729 + 45.05197759807009561 + 7.34314826363512729 + 45.05197759807009561 + points_c27022e1_5845_4026_ad1f_6be2c25d03b2 ./test_project_mapthemes.gpkg|layername=points|subset="style" = 2 @@ -1629,7 +1894,7 @@ def my_form_open(dialog, layer, feature): points_green - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -1657,12 +1922,13 @@ def my_form_open(dialog, layer, feature): + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -1673,7 +1939,7 @@ def my_form_open(dialog, layer, feature): - + @@ -1698,446 +1964,316 @@ def my_form_open(dialog, layer, feature): 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - - - - + + + + - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - + + + @@ -2146,80 +2282,53 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2227,30 +2336,30 @@ def my_form_open(dialog, layer, feature): - + - + - + - + - + - - + + + + + + - - + + - - + + - - + + - + - @@ -2314,12 +2427,12 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - + + - - + + @@ -2328,9 +2441,21 @@ def my_form_open(dialog, layer, feature): "fid" - + - + + + 7.3496771601676647 + 44.93244856924363972 + 7.3496771601676647 + 44.93244856924363972 + + + 7.3496771601676647 + 44.93244856924363972 + 7.3496771601676647 + 44.93244856924363972 + points_f8287dca_eb31_4636_bfe2_dc6900639433 ./test_project_mapthemes.gpkg|layername=points|subset="style" = 4 @@ -2339,7 +2464,7 @@ def my_form_open(dialog, layer, feature): points_black - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -2367,12 +2492,13 @@ def my_form_open(dialog, layer, feature): + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -2383,7 +2509,7 @@ def my_form_open(dialog, layer, feature): - + @@ -2408,446 +2534,316 @@ def my_form_open(dialog, layer, feature): 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - - - - + + + + - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - + + + @@ -2856,80 +2852,53 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2937,30 +2906,30 @@ def my_form_open(dialog, layer, feature): - + - + - + - + - + - - + + + + + + - - + + - - + + - - + + - + - @@ -3024,12 +2997,12 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - + + - - + + @@ -3038,9 +3011,9 @@ def my_form_open(dialog, layer, feature): "fid" - + - + 6.99864045786122979 44.86445936400060219 @@ -3061,7 +3034,7 @@ def my_form_open(dialog, layer, feature): red - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -3089,12 +3062,13 @@ def my_form_open(dialog, layer, feature): + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -3105,7 +3079,7 @@ def my_form_open(dialog, layer, feature): - + @@ -3130,241 +3104,173 @@ def my_form_open(dialog, layer, feature): 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - - - - - - - @@ -3373,9 +3279,12 @@ def my_form_open(dialog, layer, feature): + + + @@ -3384,80 +3293,53 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3465,29 +3347,29 @@ def my_form_open(dialog, layer, feature): - + - + - + - + - + + + + - + - + - + - + - @@ -3546,18 +3431,18 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - + - + "fid" - + - + 6.99864045786122979 44.86445936400060219 @@ -3578,7 +3463,7 @@ def my_form_open(dialog, layer, feature): green - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -3606,12 +3491,13 @@ def my_form_open(dialog, layer, feature): + - + +proj=longlat +datum=WGS84 +no_defs 0 0 @@ -3622,7 +3508,7 @@ def my_form_open(dialog, layer, feature): - + @@ -3647,241 +3533,173 @@ def my_form_open(dialog, layer, feature): 1 0 - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - - - - - - - @@ -3890,9 +3708,12 @@ def my_form_open(dialog, layer, feature): + + + @@ -3901,80 +3722,53 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -3982,29 +3776,29 @@ def my_form_open(dialog, layer, feature): - + - + - + - + - + + + + - + - + - + - + - @@ -4063,16 +3860,16 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - + - + "fid" - + @@ -4082,6 +3879,7 @@ def my_form_open(dialog, layer, feature): + @@ -4113,6 +3911,7 @@ def my_form_open(dialog, layer, feature): 5 2.5 + false false false 1 @@ -4199,44 +3998,44 @@ def my_form_open(dialog, layer, feature): - - + + - - + + - - + + - - + + - - + + - - + + @@ -4259,7 +4058,7 @@ def my_form_open(dialog, layer, feature): - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -4287,7 +4086,7 @@ def my_form_open(dialog, layer, feature): - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -4302,7 +4101,7 @@ def my_form_open(dialog, layer, feature): - PROJCRS["WGS 84 / UTM zone 48N",BASEGEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Between 102°E and 108°E, northern hemisphere between equator and 84°N, onshore and offshore. Cambodia. China. Indonesia. Laos. Malaysia - West Malaysia. Mongolia. Russian Federation. Singapore. Thailand. Vietnam."],BBOX[0,102,84,108]],ID["EPSG",32648]] + PROJCRS["WGS 84 / UTM zone 48N",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Between 102°E and 108°E, northern hemisphere between equator and 84°N, onshore and offshore. Cambodia. China. Indonesia. Laos. Malaysia - West Malaysia. Mongolia. Russian Federation. Singapore. Thailand. Vietnam."],BBOX[0,102,84,108]],ID["EPSG",32648]] +proj=utm +zone=48 +datum=WGS84 +units=m +no_defs 3132 32648 @@ -4330,7 +4129,7 @@ def my_form_open(dialog, layer, feature): - PROJCRS["WGS 84 / Pseudo-Mercator",BASEGEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06°S and 85.06°N."],BBOX[-85.06,-180,85.06,180]],ID["EPSG",3857]] + PROJCRS["WGS 84 / Pseudo-Mercator",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06°S and 85.06°N."],BBOX[-85.06,-180,85.06,180]],ID["EPSG",3857]] +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs 3857 3857 @@ -4358,7 +4157,7 @@ def my_form_open(dialog, layer, feature): - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -4401,110 +4200,91 @@ def my_form_open(dialog, layer, feature): + + + Alessandro Pasotti 2021-05-25T14:31:14 - - - + + + - + - + - - - - - - - - - - - - - - + + + - + - + - - - - - - - - - - - @@ -4512,136 +4292,114 @@ def my_form_open(dialog, layer, feature): - - - + + + - + - + - + - + - + - - - + + + - + - + - - - - - - - - - - - - - - + + + - + - + - - - - - - - - - - - @@ -4649,136 +4407,114 @@ def my_form_open(dialog, layer, feature): - - - + + + - + - + - + - + - + - - - + + + - + - + - - - - - - - - - - - - - - + + + - + - + - - - - - - - - - - - @@ -4786,136 +4522,114 @@ def my_form_open(dialog, layer, feature): - - - + + + - + - + - + - + - + - - - + + + - + - + - - - - - - - - - - - - - - + + + - + - + - - - - - - - - - - - @@ -4923,70 +4637,193 @@ def my_form_open(dialog, layer, feature): - - - + + + - + - + - + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + + - + - GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] +proj=longlat +datum=WGS84 +no_defs 3452 4326 @@ -5001,39 +4838,55 @@ def my_form_open(dialog, layer, feature): - + - + - + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + From 92c54d75b82345016b7584027d09ad7179e695c2 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 18 Sep 2023 18:28:57 +0300 Subject: [PATCH 022/151] add object names to annotation actions, so they can be removed with interface customization (fix #54250) --- src/app/qgisapp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index eec638d74a92..0d9070fd5f0f 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -816,6 +816,7 @@ void QgisApp::annotationItemTypeAdded( int id ) action->setCheckable( true ); action->setData( id ); action->setIcon( QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->creationIcon() ); + action->setObjectName( QStringLiteral( "mAction%1" ).arg( name.replace( " ", "" ) ) ); mMapToolGroup->addAction( action ); From cd51bb2535f08528335fa80bbce439fd0252c481 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 17:29:53 +0200 Subject: [PATCH 023/151] Remove leftover --- tests/src/providers/grass/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 3eb1008564dc..2880084c4cde 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -1,8 +1,6 @@ include_directories( ${CMAKE_SOURCE_DIR}/src/providers/grass ) -include_directories(BEFORE SYSTEM -) include_directories(SYSTEM ${POSTGRES_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} From 55593bdbb3d240a21a151f6d106bc30d202aef60 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 19:02:53 +0200 Subject: [PATCH 024/151] Cleanup GDAL_INCLUDE_DIR --- python/CMakeLists.txt | 1 - src/plugins/geometry_checker/CMakeLists.txt | 4 ---- src/plugins/grass/CMakeLists.txt | 1 - src/providers/hana/CMakeLists.txt | 1 - src/quickgui/CMakeLists.txt | 1 - src/quickgui/plugin/CMakeLists.txt | 1 - src/server/services/wcs/CMakeLists.txt | 1 - src/server/services/wfs/CMakeLists.txt | 1 - src/server/services/wfs3/CMakeLists.txt | 1 - src/server/services/wms/CMakeLists.txt | 1 - src/server/services/wmts/CMakeLists.txt | 1 - tests/src/3d/CMakeLists.txt | 1 - tests/src/quickgui/CMakeLists.txt | 1 - tests/src/quickgui/app/CMakeLists.txt | 1 - 14 files changed, 17 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index d9547e05c557..9e3d0106b3ea 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -73,7 +73,6 @@ include_directories(SYSTEM ${QT_QTNETWORK_INCLUDE_DIR} ${QT_QTSVG_INCLUDE_DIR} ${QT_QTXML_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${QWT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} diff --git a/src/plugins/geometry_checker/CMakeLists.txt b/src/plugins/geometry_checker/CMakeLists.txt index c6981dc4decd..5b77d208dc8c 100644 --- a/src/plugins/geometry_checker/CMakeLists.txt +++ b/src/plugins/geometry_checker/CMakeLists.txt @@ -49,10 +49,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) -include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} -) - target_link_libraries(plugin_geometrychecker qgis_core qgis_gui diff --git a/src/plugins/grass/CMakeLists.txt b/src/plugins/grass/CMakeLists.txt index 9bfeccab3fd3..75df156f5f03 100644 --- a/src/plugins/grass/CMakeLists.txt +++ b/src/plugins/grass/CMakeLists.txt @@ -81,7 +81,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/providers/hana/CMakeLists.txt b/src/providers/hana/CMakeLists.txt index 431b8c098527..898386b6eac1 100644 --- a/src/providers/hana/CMakeLists.txt +++ b/src/providers/hana/CMakeLists.txt @@ -67,7 +67,6 @@ include_directories( ) include_directories (SYSTEM - ${GDAL_INCLUDE_DIR} ${ODBC_INCLUDE_DIRS} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt index 5c384f492c0e..4d5c659a57b7 100644 --- a/src/quickgui/CMakeLists.txt +++ b/src/quickgui/CMakeLists.txt @@ -26,7 +26,6 @@ include_directories( ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt index dd744ead6977..4a62b4f62497 100644 --- a/src/quickgui/plugin/CMakeLists.txt +++ b/src/quickgui/plugin/CMakeLists.txt @@ -28,7 +28,6 @@ include_directories( ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} diff --git a/src/server/services/wcs/CMakeLists.txt b/src/server/services/wcs/CMakeLists.txt index d05f87e658de..e728e6fc11ac 100644 --- a/src/server/services/wcs/CMakeLists.txt +++ b/src/server/services/wcs/CMakeLists.txt @@ -19,7 +19,6 @@ add_library (wcs MODULE ${wcs_SRCS}) target_compile_features(wcs PRIVATE cxx_std_17) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/server/services/wfs/CMakeLists.txt b/src/server/services/wfs/CMakeLists.txt index c090e9f4f52d..8a3cb2417136 100644 --- a/src/server/services/wfs/CMakeLists.txt +++ b/src/server/services/wfs/CMakeLists.txt @@ -35,7 +35,6 @@ foreach(_library_type MODULE STATIC) target_compile_features(${_library_name} PRIVATE cxx_std_17) include_directories(${_library_name} SYSTEM PUBLIC - ${GDAL_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/server/services/wfs3/CMakeLists.txt b/src/server/services/wfs3/CMakeLists.txt index fb306005e2e8..d1858fcd5b62 100644 --- a/src/server/services/wfs3/CMakeLists.txt +++ b/src/server/services/wfs3/CMakeLists.txt @@ -17,7 +17,6 @@ add_library (wfs3 MODULE ${WFS3_SRCS}) target_compile_features(wfs3 PRIVATE cxx_std_17) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/server/services/wms/CMakeLists.txt b/src/server/services/wms/CMakeLists.txt index fa34d1e99055..68dfe7a69596 100644 --- a/src/server/services/wms/CMakeLists.txt +++ b/src/server/services/wms/CMakeLists.txt @@ -45,7 +45,6 @@ foreach(_library_type MODULE STATIC) target_compile_features(${_library_name} PRIVATE cxx_std_17) include_directories(${_library_name} SYSTEM PUBLIC - ${GDAL_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/server/services/wmts/CMakeLists.txt b/src/server/services/wmts/CMakeLists.txt index 39af320cb453..23b7fb8b86b5 100644 --- a/src/server/services/wmts/CMakeLists.txt +++ b/src/server/services/wmts/CMakeLists.txt @@ -24,7 +24,6 @@ add_library (wmts MODULE ${WMTS_SRCS} ${WMTS_HDRS}) target_compile_features(wmts PRIVATE cxx_std_17) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/tests/src/3d/CMakeLists.txt b/tests/src/3d/CMakeLists.txt index b4294ba1bf13..ecc3ef6a678c 100644 --- a/tests/src/3d/CMakeLists.txt +++ b/tests/src/3d/CMakeLists.txt @@ -13,7 +13,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ) include_directories(SYSTEM ${QT_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${${QT_VERSION_BASE}_3DEXTRA_INCLUDE_DIR} ) diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index a5aa1da0f1d5..d6250e875ba2 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -19,7 +19,6 @@ include_directories(SYSTEM ${GEOS_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt index 9a7ece8ae927..4fb7b4c389c9 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -21,7 +21,6 @@ include_directories( ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} From 1e1fb478fd031c8db6120fc1925c8cf425705167 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 18 Sep 2023 19:08:58 +0200 Subject: [PATCH 025/151] Remove PROJ_LIBRARY leftovers --- src/crssync/CMakeLists.txt | 1 - src/process/CMakeLists.txt | 1 - src/server/CMakeLists.txt | 1 - tests/src/3d/sandbox/CMakeLists.txt | 1 - tests/src/providers/grass/CMakeLists.txt | 1 - 5 files changed, 5 deletions(-) diff --git a/src/crssync/CMakeLists.txt b/src/crssync/CMakeLists.txt index 3ff013abb801..278272161a98 100644 --- a/src/crssync/CMakeLists.txt +++ b/src/crssync/CMakeLists.txt @@ -10,7 +10,6 @@ else () target_link_libraries(crssync qgis_core - ${PROJ_LIBRARY} ) if(MSVC AND NOT USING_NMAKE) diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index 01ab9bf9e83a..c1947294c69c 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -42,7 +42,6 @@ target_link_libraries(qgis_process qgis_core qgis_analysis ${QT_VERSION_BASE}::Core - ${PROJ_LIBRARY} ${GEOS_LIBRARY} ) diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 644dae453213..77f835996bb6 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -113,7 +113,6 @@ endif() target_link_libraries(qgis_server qgis_core - ${PROJ_LIBRARY} ${FCGI_LIBRARY} ${POSTGRES_LIBRARY} ${QCA_LIBRARY} diff --git a/tests/src/3d/sandbox/CMakeLists.txt b/tests/src/3d/sandbox/CMakeLists.txt index af0fef2280b5..b12558e898db 100644 --- a/tests/src/3d/sandbox/CMakeLists.txt +++ b/tests/src/3d/sandbox/CMakeLists.txt @@ -34,7 +34,6 @@ target_link_libraries(qgis_3d_sandbox ${QT_VERSION_BASE}::Svg ${QT_VERSION_BASE}::Network ${QT_VERSION_BASE}::Test - ${PROJ_LIBRARY} ${GEOS_LIBRARY} ${QWT_LIBRARY} qgis_core diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 2880084c4cde..9d6d833f7f35 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -25,7 +25,6 @@ macro (ADD_QGIS_GRASS_TEST grass_build_version testname testsrc) ${QT_VERSION_BASE}::Core ${QT_VERSION_BASE}::Svg ${QT_VERSION_BASE}::Test - ${PROJ_LIBRARY} ${GEOS_LIBRARY} qgis_core qgis_test From 37f426e88558d6ea8b05f0b59a69d9e552ddff88 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Mon, 18 Sep 2023 17:47:51 +0200 Subject: [PATCH 026/151] Fix dialog name --- src/ui/tiledscene/qgstiledsceneelevationpropertieswidgetbase.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/tiledscene/qgstiledsceneelevationpropertieswidgetbase.ui b/src/ui/tiledscene/qgstiledsceneelevationpropertieswidgetbase.ui index 2a46d5eff2fe..22cf441ef256 100644 --- a/src/ui/tiledscene/qgstiledsceneelevationpropertieswidgetbase.ui +++ b/src/ui/tiledscene/qgstiledsceneelevationpropertieswidgetbase.ui @@ -11,7 +11,7 @@ - Point Cloud Elevation Properties + Tiled Scene Elevation Properties From add14bfbd31e415df716fc494859a4711e2f4ea2 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 19 Sep 2023 13:47:06 +0200 Subject: [PATCH 027/151] PG: fix issue GH #54572 st_geographyfromtext Fix #54572 Error saving edit on PostGIS geometry when table also contains geography --- .../postgres/qgspostgresprovider.cpp | 8 +++---- tests/src/python/test_provider_postgres.py | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index cd3f0fa0481a..3ca8ff05fd99 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -2616,7 +2616,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) } else if ( fieldTypeName == QLatin1String( "geography" ) ) { - values += QStringLiteral( "%1st_geographyfromewkt(%2)" ) + values += QStringLiteral( "%1st_geographyfromtext(%2)" ) .arg( delim, quotedValue( v.toString() ) ); } @@ -2650,7 +2650,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) } else if ( fieldTypeName == QLatin1String( "geography" ) ) { - values += QStringLiteral( "%1st_geographyfromewkt($%2)" ) + values += QStringLiteral( "%1st_geographyfromtext($%2)" ) .arg( delim ) .arg( defaultValues.size() + offset ); } @@ -3223,7 +3223,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & } else if ( fld.typeName() == QLatin1String( "geography" ) ) { - sql += QStringLiteral( "st_geographyfromewkt(%1)" ) + sql += QStringLiteral( "st_geographyfromtext(%1)" ) .arg( quotedValue( siter->toString() ) ); } else if ( fld.typeName() == QLatin1String( "jsonb" ) ) @@ -3588,7 +3588,7 @@ bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_ma } else if ( fld.typeName() == QLatin1String( "geography" ) ) { - sql += QStringLiteral( "st_geographyfromewkt(%1)" ) + sql += QStringLiteral( "st_geographyfromtext(%1)" ) .arg( quotedValue( siter->toString() ) ); } else if ( fld.typeName() == QLatin1String( "jsonb" ) ) diff --git a/tests/src/python/test_provider_postgres.py b/tests/src/python/test_provider_postgres.py index 1e393a140141..fff74b0dab23 100644 --- a/tests/src/python/test_provider_postgres.py +++ b/tests/src/python/test_provider_postgres.py @@ -3280,6 +3280,28 @@ def reportError(self, error, fatalError=False): extracted_fids = [f['id'] for f in vl_result.getFeatures()] self.assertEqual(set(extracted_fids), {1, 2}) # Bug ? + def testGeographyAddFeature(self): + """Test issue GH #54572 Error saving edit on PostGIS geometry when table also contains geography""" + + self.execSQLCommand( + 'DROP TABLE IF EXISTS qgis_test."geom_and_geog" CASCADE') + self.execSQLCommand(""" + CREATE TABLE qgis_test.geom_and_geog ( + pkey SERIAL PRIMARY KEY, + geom geometry(POLYGON, 3857), + geog geography(POLYGON, 4326) + );""") + + vl = QgsVectorLayer(self.dbconn + ' sslmode=disable key=\'pkey\' srid=3857 table="qgis_test"."geom_and_geog" (geom) sql=', 'geom_and_geog', 'postgres') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.featureCount(), 0) + dp = vl.dataProvider() + f = QgsFeature(vl.fields()) + f.setGeometry(QgsGeometry.fromWkt('POLYGON((28.030080546000004 -26.2055410477482,28.030103891999996 -26.20540054874821,28.030532775999998 -26.205458576748192,28.030553322999996 -26.2056050407482,28.030080546000004 -26.2055410477482))')) + f.setAttribute('geog', 'POLYGON((28.030080546000004 -26.2055410477482,28.030103891999996 -26.20540054874821,28.030532775999998 -26.205458576748192,28.030553322999996 -26.2056050407482,28.030080546000004 -26.2055410477482))') + self.assertTrue(dp.addFeature(f)) + self.assertEqual(vl.featureCount(), 1) + class TestPyQgsPostgresProviderCompoundKey(QgisTestCase, ProviderTestCase): From 6b0d61ab7f44345a84e8ed2fd21771ac3ab5d2e6 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 18 Sep 2023 15:45:28 +0200 Subject: [PATCH 028/151] Remove password from project filename log --- src/app/qgisapp.cpp | 7 ++++++- src/core/qgsdatasourceuri.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index eec638d74a92..fb584cd4a633 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -4471,7 +4471,12 @@ void QgisApp::setupConnections() { if ( !mProjectLoadingProxyTask && i < n ) { - const QString name = QgsProject::instance()->title().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->title(); + QString name = QgsProject::instance()->title().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->title(); + if ( QgsProject::instance()->projectStorage() ) + { + name = QgsDataSourceUri::removePassword( name ); + } + mProjectLoadingProxyTask = new QgsProxyProgressTask( tr( "Loading “%1”" ).arg( name ) ); QgsApplication::taskManager()->addTask( mProjectLoadingProxyTask ); } diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index 35ee70408dfc..3c00e3b9420f 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -248,6 +248,17 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) regexp.setPattern( QStringLiteral( "/.*@" ) ); safeName.replace( regexp, QStringLiteral( "/@" ) ); } + else if ( aUri.contains( QLatin1String( "postgresql:" ) ) ) + { + // postgresql://user:pwd@... + regexp.setPattern( QStringLiteral( "/.*@" ) ); + const QString matched = regexp.match(aUri).capturedView().toString(); + + QString anonymised = matched.split( QStringLiteral( ":" ) )[0]; + anonymised.append( QStringLiteral( "@" ) ); + + safeName.replace( regexp, anonymised ); + } else if ( aUri.contains( QLatin1String( "SDE:" ) ) ) { QStringList strlist = aUri.split( ',' ); From 137af237912ca615434f5ee5392ecc2658699884 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 18 Sep 2023 15:58:03 +0200 Subject: [PATCH 029/151] Add unit test --- tests/src/core/testqgsdatasourceuri.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/src/core/testqgsdatasourceuri.cpp b/tests/src/core/testqgsdatasourceuri.cpp index 1ce49617da65..acc57ab3fb26 100644 --- a/tests/src/core/testqgsdatasourceuri.cpp +++ b/tests/src/core/testqgsdatasourceuri.cpp @@ -35,6 +35,7 @@ class TestQgsDataSourceUri: public QObject void checkConnectionInfo_data(); void checkAuthParams(); void checkParameterKeys(); + void checkRemovePassword(); }; void TestQgsDataSourceUri::checkparser_data() @@ -524,5 +525,11 @@ void TestQgsDataSourceUri::checkParameterKeys() QVERIFY( uri.parameterKeys().contains( QLatin1String( "bar" ) ) ); } +void TestQgsDataSourceUri::checkRemovePassword() +{ + const QString uri = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user:password@127.0.0.1:5432?dbname=test" ) ); + QCOMPARE( uri, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test") ); +} + QGSTEST_MAIN( TestQgsDataSourceUri ) #include "testqgsdatasourceuri.moc" From d7fb273aafb926370c2606fcc8011358ca68cbb3 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 18 Sep 2023 16:09:02 +0200 Subject: [PATCH 030/151] layout --- src/core/qgsdatasourceuri.cpp | 2 +- tests/src/core/testqgsdatasourceuri.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index 3c00e3b9420f..b632d9dc1458 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -252,7 +252,7 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) { // postgresql://user:pwd@... regexp.setPattern( QStringLiteral( "/.*@" ) ); - const QString matched = regexp.match(aUri).capturedView().toString(); + const QString matched = regexp.match( aUri ).capturedView().toString(); QString anonymised = matched.split( QStringLiteral( ":" ) )[0]; anonymised.append( QStringLiteral( "@" ) ); diff --git a/tests/src/core/testqgsdatasourceuri.cpp b/tests/src/core/testqgsdatasourceuri.cpp index acc57ab3fb26..a9d59feb866c 100644 --- a/tests/src/core/testqgsdatasourceuri.cpp +++ b/tests/src/core/testqgsdatasourceuri.cpp @@ -528,7 +528,7 @@ void TestQgsDataSourceUri::checkParameterKeys() void TestQgsDataSourceUri::checkRemovePassword() { const QString uri = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user:password@127.0.0.1:5432?dbname=test" ) ); - QCOMPARE( uri, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test") ); + QCOMPARE( uri, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test" ) ); } QGSTEST_MAIN( TestQgsDataSourceUri ) From e1b03c6002befce9160dad145d45b79cdd7cd38c Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 15:27:09 +0200 Subject: [PATCH 031/151] Use captured() --- src/core/qgsdatasourceuri.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index b632d9dc1458..c3b85b74a65b 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -252,7 +252,7 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) { // postgresql://user:pwd@... regexp.setPattern( QStringLiteral( "/.*@" ) ); - const QString matched = regexp.match( aUri ).capturedView().toString(); + const QString matched = regexp.match( aUri ).captured(); QString anonymised = matched.split( QStringLiteral( ":" ) )[0]; anonymised.append( QStringLiteral( "@" ) ); From adc441535bf00e15b4b1e9df7b529893b57b22b8 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 15:30:34 +0200 Subject: [PATCH 032/151] Support postgresql://user@... schema --- src/core/qgsdatasourceuri.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index c3b85b74a65b..12160021a268 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -254,8 +254,12 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) regexp.setPattern( QStringLiteral( "/.*@" ) ); const QString matched = regexp.match( aUri ).captured(); - QString anonymised = matched.split( QStringLiteral( ":" ) )[0]; - anonymised.append( QStringLiteral( "@" ) ); + QString anonymised = matched; + const QStringList items = matched.split( QStringLiteral( ":" ) ); + if (items.size() > 1) { + anonymised = matched.split( QStringLiteral( ":" ) )[0]; + anonymised.append( QStringLiteral( "@" ) ); + } safeName.replace( regexp, anonymised ); } From f55f0ffb96c0c956e461d2eeb74bb281c4870e78 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 16:00:46 +0200 Subject: [PATCH 033/151] Add possibility to hide a password with XXXXXXXX --- src/core/qgsdatasourceuri.cpp | 48 +++++++++++++++++++++++++++++++---- src/core/qgsdatasourceuri.h | 2 +- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index 12160021a268..cb36b7e45f97 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -27,6 +27,8 @@ #include #include +#define HIDING_TOKEN QStringLiteral( "XXXXXXXX" ) + QgsDataSourceUri::QgsDataSourceUri() { // do nothing @@ -222,7 +224,7 @@ QgsDataSourceUri::QgsDataSourceUri( const QString &u ) } } -QString QgsDataSourceUri::removePassword( const QString &aUri ) +QString QgsDataSourceUri::removePassword( const QString &aUri, bool hide ) { QRegularExpression regexp; regexp.setPatternOptions( QRegularExpression::InvertedGreedinessOption ); @@ -230,23 +232,55 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) if ( aUri.contains( QLatin1String( " password=" ) ) ) { regexp.setPattern( QStringLiteral( " password=.* " ) ); - safeName.replace( regexp, QStringLiteral( " " ) ); + + if ( hide ) + { + safeName.replace( regexp, QStringLiteral( " password=%1 " ).arg( HIDING_TOKEN ) ); + } + else + { + safeName.replace( regexp, QStringLiteral( " " ) ); + } } else if ( aUri.contains( QLatin1String( ",password=" ) ) ) { regexp.setPattern( QStringLiteral( ",password=.*," ) ); - safeName.replace( regexp, QStringLiteral( "," ) ); + + if ( hide ) + { + safeName.replace( regexp, QStringLiteral( ",password=%1," ).arg( HIDING_TOKEN ) ); + } + else + { + safeName.replace( regexp, QStringLiteral( "," ) ); + } } else if ( aUri.contains( QLatin1String( "IDB:" ) ) ) { regexp.setPattern( QStringLiteral( " pass=.* " ) ); - safeName.replace( regexp, QStringLiteral( " " ) ); + + if ( hide ) + { + safeName.replace( regexp, QStringLiteral( " pass=%1 " ).arg( HIDING_TOKEN ) ); + } + else + { + safeName.replace( regexp, QStringLiteral( " " ) ); + } } else if ( ( aUri.contains( QLatin1String( "OCI:" ) ) ) || ( aUri.contains( QLatin1String( "ODBC:" ) ) ) ) { regexp.setPattern( QStringLiteral( "/.*@" ) ); - safeName.replace( regexp, QStringLiteral( "/@" ) ); + + if ( hide ) + { + safeName.replace( regexp, QStringLiteral( "/%1@" ).arg( HIDING_TOKEN ) ); + } + else + { + safeName.replace( regexp, QStringLiteral( "/@" ) ); + } } else if ( aUri.contains( QLatin1String( "postgresql:" ) ) ) { @@ -258,6 +292,10 @@ QString QgsDataSourceUri::removePassword( const QString &aUri ) const QStringList items = matched.split( QStringLiteral( ":" ) ); if (items.size() > 1) { anonymised = matched.split( QStringLiteral( ":" ) )[0]; + if ( hide ) + { + anonymised.append( QStringLiteral( ":%1" ).arg( HIDING_TOKEN ) ); + } anonymised.append( QStringLiteral( "@" ) ); } diff --git a/src/core/qgsdatasourceuri.h b/src/core/qgsdatasourceuri.h index b973f7e03ed5..a71911c1c22e 100644 --- a/src/core/qgsdatasourceuri.h +++ b/src/core/qgsdatasourceuri.h @@ -188,7 +188,7 @@ class CORE_EXPORT QgsDataSourceUri /** * Removes the password element from a URI. */ - static QString removePassword( const QString &aUri ); + static QString removePassword( const QString &aUri, bool hide = false ); /** * Returns any associated authentication configuration ID stored in the URI. From f0a2c902284b03c1bd76ca306a548fe61e40ed67 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 16:01:08 +0200 Subject: [PATCH 034/151] Add more tests --- tests/src/core/testqgsdatasourceuri.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/src/core/testqgsdatasourceuri.cpp b/tests/src/core/testqgsdatasourceuri.cpp index a9d59feb866c..5bd578ca43d3 100644 --- a/tests/src/core/testqgsdatasourceuri.cpp +++ b/tests/src/core/testqgsdatasourceuri.cpp @@ -527,8 +527,14 @@ void TestQgsDataSourceUri::checkParameterKeys() void TestQgsDataSourceUri::checkRemovePassword() { - const QString uri = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user:password@127.0.0.1:5432?dbname=test" ) ); - QCOMPARE( uri, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test" ) ); + const QString uri0 = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user:password@127.0.0.1:5432?dbname=test" ) ); + QCOMPARE( uri0, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test" ) ); + + const QString uri1 = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user:password@127.0.0.1:5432?dbname=test" ), true ); + QCOMPARE( uri1, QStringLiteral( "postgresql://user:XXXXXXXX@127.0.0.1:5432?dbname=test" ) ); + + const QString uri2 = QgsDataSourceUri::removePassword( QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test" ) ); + QCOMPARE( uri2, QStringLiteral( "postgresql://user@127.0.0.1:5432?dbname=test" ) ); } QGSTEST_MAIN( TestQgsDataSourceUri ) From 9b9a702039fd877a740ac4ba89754216aa430154 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 16:08:41 +0200 Subject: [PATCH 035/151] Hide password in project properties --- src/app/qgisapp.cpp | 2 +- src/app/qgsprojectproperties.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index fb584cd4a633..2b5ef5393cbb 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -4474,7 +4474,7 @@ void QgisApp::setupConnections() QString name = QgsProject::instance()->title().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->title(); if ( QgsProject::instance()->projectStorage() ) { - name = QgsDataSourceUri::removePassword( name ); + name = QgsDataSourceUri::removePassword( name, true ); } mProjectLoadingProxyTask = new QgsProxyProgressTask( tr( "Loading “%1”" ).arg( name ) ); diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp index 2f445f6a1ba4..7727b40ff0f6 100644 --- a/src/app/qgsprojectproperties.cpp +++ b/src/app/qgsprojectproperties.cpp @@ -305,7 +305,15 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa mEndDateTimeEdit->setDateTime( range.end() ); title( QgsProject::instance()->title() ); - mProjectFileLineEdit->setText( QDir::toNativeSeparators( !QgsProject::instance()->fileName().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->originalPath() ) ); + + QString name = !QgsProject::instance()->fileName().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->originalPath(); + if ( QgsProject::instance()->projectStorage() ) + { + name = QgsDataSourceUri::removePassword( name, true ); + } + + mProjectFileLineEdit->setText( QDir::toNativeSeparators( name ) ); + mProjectHomeLineEdit->setShowClearButton( true ); mProjectHomeLineEdit->setText( QDir::toNativeSeparators( QgsProject::instance()->presetHomePath() ) ); connect( mButtonSetProjectHome, &QToolButton::clicked, this, [ = ] From 0d5d80ed4930e131a4afa438aa567f08d6980fd6 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 16:52:34 +0200 Subject: [PATCH 036/151] Anonymise connection string in welcome page (title and tooltip) --- src/app/qgsprojectlistitemdelegate.cpp | 2 +- src/app/qgsprojectlistitemdelegate.h | 3 ++- src/app/qgsrecentprojectsitemsmodel.cpp | 12 +++++++++++- src/app/qgswelcomepage.cpp | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/app/qgsprojectlistitemdelegate.cpp b/src/app/qgsprojectlistitemdelegate.cpp index 523e0d1d1212..8cfe05786e43 100644 --- a/src/app/qgsprojectlistitemdelegate.cpp +++ b/src/app/qgsprojectlistitemdelegate.cpp @@ -80,7 +80,7 @@ void QgsProjectListItemDelegate::paint( QPainter *painter, const QStyleOptionVie doc.setHtml( QStringLiteral( "
%3%4
%5
%6
" ).arg( textSize ).arg( QString::number( titleSize ), index.data( QgsProjectListItemDelegate::TitleRole ).toString(), index.data( QgsProjectListItemDelegate::PinRole ).toBool() ? QStringLiteral( "" ) : QString(), - mShowPath ? index.data( QgsProjectListItemDelegate::NativePathRole ).toString() : QString(), + mShowPath ? index.data( QgsProjectListItemDelegate::AnonymisedNativePathRole ).toString() : QString(), index.data( QgsProjectListItemDelegate::CrsRole ).toString() ) ); doc.setTextWidth( option.rect.width() - ( !icon.isNull() ? iconSize.width() + 4.375 * mRoundedRectSizePixels : 4.375 * mRoundedRectSizePixels ) ); diff --git a/src/app/qgsprojectlistitemdelegate.h b/src/app/qgsprojectlistitemdelegate.h index 87e7ba2f38ad..becf3209610e 100644 --- a/src/app/qgsprojectlistitemdelegate.h +++ b/src/app/qgsprojectlistitemdelegate.h @@ -46,7 +46,8 @@ class QgsProjectListItemDelegate : public QStyledItemDelegate PathRole = Qt::UserRole + 2, NativePathRole = Qt::UserRole + 3, CrsRole = Qt::UserRole + 4, - PinRole = Qt::UserRole + 5 + PinRole = Qt::UserRole + 5, + AnonymisedNativePathRole = Qt::UserRole + 6 }; explicit QgsProjectListItemDelegate( QObject *parent = nullptr ); diff --git a/src/app/qgsrecentprojectsitemsmodel.cpp b/src/app/qgsrecentprojectsitemsmodel.cpp index c6ca699ac0cb..7861e2329002 100644 --- a/src/app/qgsrecentprojectsitemsmodel.cpp +++ b/src/app/qgsrecentprojectsitemsmodel.cpp @@ -21,6 +21,7 @@ #include "qgsprojectstorageregistry.h" #include "qgsprojectlistitemdelegate.h" #include "qgsprojectstorage.h" +#include "qgsdatasourceuri.h" #include #include @@ -88,7 +89,16 @@ QVariant QgsRecentProjectItemsModel::data( const QModelIndex &index, int role ) } case Qt::ToolTipRole: - return mRecentProjects.at( index.row() ).path; + case QgsProjectListItemDelegate::AnonymisedNativePathRole: + { + QString name = mRecentProjects.at( index.row() ).path; + QgsProjectStorage *storage = QgsApplication::projectStorageRegistry()->projectStorageFromUri( name ); + if ( storage ) + { + name = QgsDataSourceUri::removePassword( name, true ); + } + return name; + } default: return QVariant(); diff --git a/src/app/qgswelcomepage.cpp b/src/app/qgswelcomepage.cpp index 57911e223d4d..02dfc8726aef 100644 --- a/src/app/qgswelcomepage.cpp +++ b/src/app/qgswelcomepage.cpp @@ -207,7 +207,7 @@ QgsRecentProjectItemsModel *QgsWelcomePage::recentProjectsModel() void QgsWelcomePage::recentProjectItemActivated( const QModelIndex &index ) { - QgisApp::instance()->openProject( mRecentProjectsModel->data( index, Qt::ToolTipRole ).toString() ); + QgisApp::instance()->openProject( mRecentProjectsModel->data( index, QgsProjectListItemDelegate::PathRole ).toString() ); } void QgsWelcomePage::templateProjectItemActivated( const QModelIndex &index ) From a95e6b23cad5e32df9680c4fd2b522b2cad13bbf Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 19 Sep 2023 17:17:47 +0200 Subject: [PATCH 037/151] sip --- python/core/auto_generated/qgsdatasourceuri.sip.in | 2 +- src/core/qgsdatasourceuri.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/core/auto_generated/qgsdatasourceuri.sip.in b/python/core/auto_generated/qgsdatasourceuri.sip.in index 7b4a9a199417..900bc63d51ba 100644 --- a/python/core/auto_generated/qgsdatasourceuri.sip.in +++ b/python/core/auto_generated/qgsdatasourceuri.sip.in @@ -167,7 +167,7 @@ Sets the ``username`` for the URI. Sets the ``password`` for the URI. %End - static QString removePassword( const QString &aUri ); + static QString removePassword( const QString &aUri, bool hide = false ); %Docstring Removes the password element from a URI. %End diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index cb36b7e45f97..bc80a4f2cf29 100644 --- a/src/core/qgsdatasourceuri.cpp +++ b/src/core/qgsdatasourceuri.cpp @@ -290,7 +290,8 @@ QString QgsDataSourceUri::removePassword( const QString &aUri, bool hide ) QString anonymised = matched; const QStringList items = matched.split( QStringLiteral( ":" ) ); - if (items.size() > 1) { + if ( items.size() > 1 ) + { anonymised = matched.split( QStringLiteral( ":" ) )[0]; if ( hide ) { From 2c8e784625292992effeb269ccb85086f17acc5d Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Tue, 19 Sep 2023 22:10:15 +0200 Subject: [PATCH 038/151] Use geos cmake configuration (#54659) --- CMakeLists.txt | 8 +- cmake/FindGEOS.cmake | 356 ++++++++++++----------- python/CMakeLists.txt | 1 - src/core/CMakeLists.txt | 3 +- src/plugins/grass/CMakeLists.txt | 1 - src/plugins/topology/CMakeLists.txt | 4 - src/providers/grass/CMakeLists.txt | 1 - src/providers/oracle/CMakeLists.txt | 1 - src/quickgui/CMakeLists.txt | 1 - src/quickgui/plugin/CMakeLists.txt | 1 - tests/src/providers/grass/CMakeLists.txt | 2 - tests/src/quickgui/CMakeLists.txt | 1 - tests/src/quickgui/app/CMakeLists.txt | 1 - 13 files changed, 185 insertions(+), 196 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d4f00b0dc04..efedfa7db5db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,8 +358,8 @@ if(WITH_CORE) endif() # required - find_package(Proj) - find_package(GEOS) + find_package(Proj REQUIRED) + find_package(GEOS REQUIRED) find_package(GDAL REQUIRED) find_package(EXPAT REQUIRED) find_package(Spatialindex REQUIRED) @@ -403,10 +403,6 @@ if(WITH_CORE) set (HAVE_SPATIALITE TRUE) endif() - if (NOT PROJ_FOUND OR NOT GEOS_FOUND) - message (SEND_ERROR "Some dependencies were not found! Proj: ${PROJ_FOUND}, Geos: ${GEOS_FOUND}") - endif() - if (POSTGRES_FOUND) # following variable is used in qgsconfig.h set (HAVE_POSTGRESQL TRUE) diff --git a/cmake/FindGEOS.cmake b/cmake/FindGEOS.cmake index 254b5e348707..f5e2551949c4 100644 --- a/cmake/FindGEOS.cmake +++ b/cmake/FindGEOS.cmake @@ -8,180 +8,188 @@ # CMake module to search for GEOS library # # If it's found it sets GEOS_FOUND to TRUE -# and following variables are set: -# GEOS_INCLUDE_DIR -# GEOS_LIBRARY +# and the following target is added # - -INCLUDE (${CMAKE_SOURCE_DIR}/cmake/MacPlistMacros.cmake) - -IF(WIN32) - - IF (MINGW) - FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" /usr/local/include /usr/include c:/msys/local/include) - FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" /usr/local/lib /usr/lib c:/msys/local/lib) - ENDIF (MINGW) - - IF (MSVC) - FIND_PATH(GEOS_INCLUDE_DIR geos_c.h $ENV{LIB_DIR}/include $ENV{INCLUDE}) - FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c_i geos_c PATHS - "$ENV{LIB_DIR}/lib" - $ENV{LIB} - ) - ENDIF (MSVC) - -ELSEIF(APPLE AND QGIS_MAC_DEPS_DIR) - - FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" ) - FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" ) - -ELSE(WIN32) - - IF(UNIX) - # try to use framework on mac - # want clean framework path, not unix compatibility path - IF (APPLE) - IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" - OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" - OR NOT CMAKE_FIND_FRAMEWORK) - SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) - SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) - FIND_LIBRARY(GEOS_LIBRARY GEOS) - IF (GEOS_LIBRARY) - # they're all the same in a framework - SET (GEOS_INCLUDE_DIR ${GEOS_LIBRARY}/Headers CACHE PATH "Path to a file.") - # set GEOS_CONFIG to make later test happy, not used here, may not exist - SET (GEOS_CONFIG ${GEOS_LIBRARY}/unix/bin/geos-config CACHE FILEPATH "Path to a program.") - # version in info.plist - GET_VERSION_PLIST (${GEOS_LIBRARY}/Resources/Info.plist GEOS_VERSION) - IF (NOT GEOS_VERSION) - MESSAGE (FATAL_ERROR "Could not determine GEOS version from framework.") - ENDIF (NOT GEOS_VERSION) +# GEOS::geos_c + +find_package(GEOS CONFIG) + +if(NOT GEOS_FOUND) + INCLUDE (${CMAKE_SOURCE_DIR}/cmake/MacPlistMacros.cmake) + + IF(WIN32) + + IF (MINGW) + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" /usr/local/include /usr/include c:/msys/local/include) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" /usr/local/lib /usr/lib c:/msys/local/lib) + ENDIF (MINGW) + + IF (MSVC) + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h $ENV{LIB_DIR}/include $ENV{INCLUDE}) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c_i geos_c PATHS + "$ENV{LIB_DIR}/lib" + $ENV{LIB} + ) + ENDIF (MSVC) + + ELSEIF(APPLE AND QGIS_MAC_DEPS_DIR) + + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" ) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" ) + + ELSE(WIN32) + + IF(UNIX) + # try to use framework on mac + # want clean framework path, not unix compatibility path + IF (APPLE) + IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" + OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" + OR NOT CMAKE_FIND_FRAMEWORK) + SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) + SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) + FIND_LIBRARY(GEOS_LIBRARY GEOS) + IF (GEOS_LIBRARY) + # they're all the same in a framework + SET (GEOS_INCLUDE_DIR ${GEOS_LIBRARY}/Headers CACHE PATH "Path to a file.") + # set GEOS_CONFIG to make later test happy, not used here, may not exist + SET (GEOS_CONFIG ${GEOS_LIBRARY}/unix/bin/geos-config CACHE FILEPATH "Path to a program.") + # version in info.plist + GET_VERSION_PLIST (${GEOS_LIBRARY}/Resources/Info.plist GEOS_VERSION) + IF (NOT GEOS_VERSION) + MESSAGE (FATAL_ERROR "Could not determine GEOS version from framework.") + ENDIF (NOT GEOS_VERSION) + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") + IF (GEOS_VERSION_MAJOR LESS 3) + MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.0.0 or higher.") + ENDIF (GEOS_VERSION_MAJOR LESS 3) + ENDIF (GEOS_LIBRARY) + SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) + ENDIF () + ENDIF (APPLE) + + IF(CYGWIN) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS /usr/lib /usr/local/lib) + ENDIF(CYGWIN) + + IF (NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) + # didn't find OS X framework, and was not set by user + SET(GEOS_CONFIG_PREFER_PATH "$ENV{GEOS_HOME}/bin" CACHE STRING "preferred path to GEOS (geos-config)") + FIND_PROGRAM(GEOS_CONFIG geos-config + ${GEOS_CONFIG_PREFER_PATH} + $ENV{LIB_DIR}/bin + /usr/local/bin/ + /usr/bin/ + ) + #MESSAGE("DBG GEOS_CONFIG ${GEOS_CONFIG}") + + IF (GEOS_CONFIG) + + EXEC_PROGRAM(${GEOS_CONFIG} + ARGS --version + OUTPUT_VARIABLE GEOS_VERSION) STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") - IF (GEOS_VERSION_MAJOR LESS 3) - MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.0.0 or higher.") - ENDIF (GEOS_VERSION_MAJOR LESS 3) - ENDIF (GEOS_LIBRARY) - SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) - ENDIF () - ENDIF (APPLE) - - IF(CYGWIN) - FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS /usr/lib /usr/local/lib) - ENDIF(CYGWIN) - - IF (NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) - # didn't find OS X framework, and was not set by user - SET(GEOS_CONFIG_PREFER_PATH "$ENV{GEOS_HOME}/bin" CACHE STRING "preferred path to GEOS (geos-config)") - FIND_PROGRAM(GEOS_CONFIG geos-config - ${GEOS_CONFIG_PREFER_PATH} - $ENV{LIB_DIR}/bin - /usr/local/bin/ - /usr/bin/ - ) - #MESSAGE("DBG GEOS_CONFIG ${GEOS_CONFIG}") - - IF (GEOS_CONFIG) - - EXEC_PROGRAM(${GEOS_CONFIG} - ARGS --version - OUTPUT_VARIABLE GEOS_VERSION) - STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") - STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") - - IF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 9) ) - MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.9.0 or higher.") - ENDIF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 9) ) - - # set INCLUDE_DIR to prefix+include - EXEC_PROGRAM(${GEOS_CONFIG} - ARGS --prefix - OUTPUT_VARIABLE GEOS_PREFIX) - - FIND_PATH(GEOS_INCLUDE_DIR - geos_c.h - ${GEOS_PREFIX}/include - /usr/local/include - /usr/include - ) - - ## extract link dirs for rpath - EXEC_PROGRAM(${GEOS_CONFIG} - ARGS --libs - OUTPUT_VARIABLE GEOS_CONFIG_LIBS ) - - ## split off the link dirs (for rpath) - ## use regular expression to match wildcard equivalent "-L*" - ## with is a space or a semicolon - STRING(REGEX MATCHALL "[-][L]([^ ;])+" - GEOS_LINK_DIRECTORIES_WITH_PREFIX - "${GEOS_CONFIG_LIBS}" ) - #MESSAGE("DBG GEOS_LINK_DIRECTORIES_WITH_PREFIX=${GEOS_LINK_DIRECTORIES_WITH_PREFIX}") - - ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES - - IF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) - STRING(REGEX REPLACE "[-][L]" "" GEOS_LINK_DIRECTORIES ${GEOS_LINK_DIRECTORIES_WITH_PREFIX} ) - ENDIF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) - - ### XXX - mloskot: geos-config --libs does not return -lgeos_c, so set it manually - ## split off the name - ## use regular expression to match wildcard equivalent "-l*" - ## with is a space or a semicolon - #STRING(REGEX MATCHALL "[-][l]([^ ;])+" - # GEOS_LIB_NAME_WITH_PREFIX - # "${GEOS_CONFIG_LIBS}" ) - #MESSAGE("DBG GEOS_CONFIG_LIBS=${GEOS_CONFIG_LIBS}") - #MESSAGE("DBG GEOS_LIB_NAME_WITH_PREFIX=${GEOS_LIB_NAME_WITH_PREFIX}") - SET(GEOS_LIB_NAME_WITH_PREFIX -lgeos_c CACHE STRING INTERNAL) - - ## remove prefix -l because we need the pure name - - IF (GEOS_LIB_NAME_WITH_PREFIX) - STRING(REGEX REPLACE "[-][l]" "" GEOS_LIB_NAME ${GEOS_LIB_NAME_WITH_PREFIX} ) - ENDIF (GEOS_LIB_NAME_WITH_PREFIX) - #MESSAGE("DBG GEOS_LIB_NAME=${GEOS_LIB_NAME}") - - IF (APPLE) - IF (NOT GEOS_LIBRARY) - # work around empty GEOS_LIBRARY left by framework check - # while still preserving user setting if given - # ***FIXME*** need to improve framework check so below not needed - SET(GEOS_LIBRARY ${GEOS_LINK_DIRECTORIES}/lib${GEOS_LIB_NAME}.dylib CACHE STRING INTERNAL FORCE) - ENDIF (NOT GEOS_LIBRARY) - ELSE (APPLE) - FIND_LIBRARY(GEOS_LIBRARY NAMES ${GEOS_LIB_NAME} PATHS ${GEOS_LIB_DIRECTORIES}/lib) - ENDIF (APPLE) - #MESSAGE("DBG GEOS_LIBRARY=${GEOS_LIBRARY}") - - ELSE(GEOS_CONFIG) - MESSAGE("FindGEOS.cmake: geos-config not found. Please set it manually. GEOS_CONFIG=${GEOS_CONFIG}") - ENDIF(GEOS_CONFIG) - ENDIF(NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) - ENDIF(UNIX) -ENDIF(WIN32) - -IF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) - FILE(READ ${GEOS_INCLUDE_DIR}/geos_c.h VERSIONFILE) - STRING(REGEX MATCH "#define GEOS_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+" GEOS_VERSION ${VERSIONFILE}) - STRING(REGEX MATCH "[0-9]+\\.[0-9]\\.[0-9]+" GEOS_VERSION ${GEOS_VERSION}) -ENDIF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) - -IF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) - SET(GEOS_FOUND TRUE) -ENDIF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) - -IF (GEOS_FOUND) - - IF (NOT GEOS_FIND_QUIETLY) - MESSAGE(STATUS "Found GEOS: ${GEOS_LIBRARY} (${GEOS_VERSION})") - ENDIF (NOT GEOS_FIND_QUIETLY) - -ELSE (GEOS_FOUND) - - MESSAGE(GEOS_INCLUDE_DIR=${GEOS_INCLUDE_DIR}) - MESSAGE(GEOS_LIBRARY=${GEOS_LIBRARY}) - MESSAGE(FATAL_ERROR "Could not find GEOS") - -ENDIF (GEOS_FOUND) + + IF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 9) ) + MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.9.0 or higher.") + ENDIF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 9) ) + + # set INCLUDE_DIR to prefix+include + EXEC_PROGRAM(${GEOS_CONFIG} + ARGS --prefix + OUTPUT_VARIABLE GEOS_PREFIX) + + FIND_PATH(GEOS_INCLUDE_DIR + geos_c.h + ${GEOS_PREFIX}/include + /usr/local/include + /usr/include + ) + + ## extract link dirs for rpath + EXEC_PROGRAM(${GEOS_CONFIG} + ARGS --libs + OUTPUT_VARIABLE GEOS_CONFIG_LIBS ) + + ## split off the link dirs (for rpath) + ## use regular expression to match wildcard equivalent "-L*" + ## with is a space or a semicolon + STRING(REGEX MATCHALL "[-][L]([^ ;])+" + GEOS_LINK_DIRECTORIES_WITH_PREFIX + "${GEOS_CONFIG_LIBS}" ) + #MESSAGE("DBG GEOS_LINK_DIRECTORIES_WITH_PREFIX=${GEOS_LINK_DIRECTORIES_WITH_PREFIX}") + + ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES + + IF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) + STRING(REGEX REPLACE "[-][L]" "" GEOS_LINK_DIRECTORIES ${GEOS_LINK_DIRECTORIES_WITH_PREFIX} ) + ENDIF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) + + ### XXX - mloskot: geos-config --libs does not return -lgeos_c, so set it manually + ## split off the name + ## use regular expression to match wildcard equivalent "-l*" + ## with is a space or a semicolon + #STRING(REGEX MATCHALL "[-][l]([^ ;])+" + # GEOS_LIB_NAME_WITH_PREFIX + # "${GEOS_CONFIG_LIBS}" ) + #MESSAGE("DBG GEOS_CONFIG_LIBS=${GEOS_CONFIG_LIBS}") + #MESSAGE("DBG GEOS_LIB_NAME_WITH_PREFIX=${GEOS_LIB_NAME_WITH_PREFIX}") + SET(GEOS_LIB_NAME_WITH_PREFIX -lgeos_c CACHE STRING INTERNAL) + + ## remove prefix -l because we need the pure name + + IF (GEOS_LIB_NAME_WITH_PREFIX) + STRING(REGEX REPLACE "[-][l]" "" GEOS_LIB_NAME ${GEOS_LIB_NAME_WITH_PREFIX} ) + ENDIF (GEOS_LIB_NAME_WITH_PREFIX) + #MESSAGE("DBG GEOS_LIB_NAME=${GEOS_LIB_NAME}") + + IF (APPLE) + IF (NOT GEOS_LIBRARY) + # work around empty GEOS_LIBRARY left by framework check + # while still preserving user setting if given + # ***FIXME*** need to improve framework check so below not needed + SET(GEOS_LIBRARY ${GEOS_LINK_DIRECTORIES}/lib${GEOS_LIB_NAME}.dylib CACHE STRING INTERNAL FORCE) + ENDIF (NOT GEOS_LIBRARY) + ELSE (APPLE) + FIND_LIBRARY(GEOS_LIBRARY NAMES ${GEOS_LIB_NAME} PATHS ${GEOS_LIB_DIRECTORIES}/lib) + ENDIF (APPLE) + #MESSAGE("DBG GEOS_LIBRARY=${GEOS_LIBRARY}") + + ELSE(GEOS_CONFIG) + MESSAGE("FindGEOS.cmake: geos-config not found. Please set it manually. GEOS_CONFIG=${GEOS_CONFIG}") + ENDIF(GEOS_CONFIG) + ENDIF(NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) + ENDIF(UNIX) + ENDIF(WIN32) + + IF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) + FILE(READ ${GEOS_INCLUDE_DIR}/geos_c.h VERSIONFILE) + STRING(REGEX MATCH "#define GEOS_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+" GEOS_VERSION ${VERSIONFILE}) + STRING(REGEX MATCH "[0-9]+\\.[0-9]\\.[0-9]+" GEOS_VERSION ${GEOS_VERSION}) + ENDIF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) + + IF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) + SET(GEOS_FOUND TRUE) + ENDIF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) + + IF (GEOS_FOUND) + + add_library(GEOS::geos_c UNKNOWN IMPORTED) + target_link_libraries(GEOS::geos_c INTERFACE ${GEOS_LIBRARY}) + target_include_directories(GEOS::geos_c INTERFACE ${GEOS_INCLUDE_DIR}) + set_target_properties(GEOS::geos_c PROPERTIES IMPORTED_LOCATION ${GEOS_LIBRARY}) + + IF (NOT GEOS_FIND_QUIETLY) + MESSAGE(STATUS "Found GEOS: ${GEOS_LIBRARY} (${GEOS_VERSION})") + ENDIF (NOT GEOS_FIND_QUIETLY) + + ELSE (GEOS_FOUND) + + MESSAGE(GEOS_INCLUDE_DIR=${GEOS_INCLUDE_DIR}) + MESSAGE(GEOS_LIBRARY=${GEOS_LIBRARY}) + MESSAGE(FATAL_ERROR "Could not find GEOS") + + ENDIF (GEOS_FOUND) +endif() diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 9e3d0106b3ea..443daec68244 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -73,7 +73,6 @@ include_directories(SYSTEM ${QT_QTNETWORK_INCLUDE_DIR} ${QT_QTSVG_INCLUDE_DIR} ${QT_QTXML_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${QWT_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b76cc6ac471b..869428249e98 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2249,7 +2249,6 @@ target_include_directories(qgis_core SYSTEM PUBLIC ${${QT_VERSION_BASE}Concurrent_INCLUDE_DIRS} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} # before GEOS for case-insensitive filesystems - ${GEOS_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} @@ -2414,7 +2413,7 @@ target_link_libraries(qgis_core ${OPTIONAL_QTWEBKIT} ${QCA_LIBRARY} ${QTKEYCHAIN_LIBRARY} - ${GEOS_LIBRARY} + GEOS::geos_c GDAL::GDAL ${SPATIALINDEX_LIBRARY} EXPAT::EXPAT diff --git a/src/plugins/grass/CMakeLists.txt b/src/plugins/grass/CMakeLists.txt index 75df156f5f03..8bfcccd01f78 100644 --- a/src/plugins/grass/CMakeLists.txt +++ b/src/plugins/grass/CMakeLists.txt @@ -81,7 +81,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(SYSTEM - ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/plugins/topology/CMakeLists.txt b/src/plugins/topology/CMakeLists.txt index 3a2b08d00840..30252940bd48 100644 --- a/src/plugins/topology/CMakeLists.txt +++ b/src/plugins/topology/CMakeLists.txt @@ -32,10 +32,6 @@ add_library (plugin_topology MODULE ${topol_SRCS} ${topol_RCCS} ${topol_UIS_H}) # require c++17 target_compile_features(plugin_topology PRIVATE cxx_std_17) -include_directories(SYSTEM - ${GEOS_INCLUDE_DIR} -) - include_directories( ${CMAKE_SOURCE_DIR}/src/plugins diff --git a/src/providers/grass/CMakeLists.txt b/src/providers/grass/CMakeLists.txt index b057be641c21..943a95958cf0 100644 --- a/src/providers/grass/CMakeLists.txt +++ b/src/providers/grass/CMakeLists.txt @@ -8,7 +8,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories (SYSTEM - ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) diff --git a/src/providers/oracle/CMakeLists.txt b/src/providers/oracle/CMakeLists.txt index 2019bc4d6c9a..dd1a552025b3 100644 --- a/src/providers/oracle/CMakeLists.txt +++ b/src/providers/oracle/CMakeLists.txt @@ -39,7 +39,6 @@ include_directories( ${CMAKE_BINARY_DIR}/src/ui ) include_directories(SYSTEM - ${GEOS_INCLUDE_DIR} ${QT_QTSQL_INCLUDEDIR} ) diff --git a/src/quickgui/CMakeLists.txt b/src/quickgui/CMakeLists.txt index 4d5c659a57b7..293e5fd139df 100644 --- a/src/quickgui/CMakeLists.txt +++ b/src/quickgui/CMakeLists.txt @@ -28,7 +28,6 @@ include_directories( include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) diff --git a/src/quickgui/plugin/CMakeLists.txt b/src/quickgui/plugin/CMakeLists.txt index 4a62b4f62497..0060aca22ffc 100644 --- a/src/quickgui/plugin/CMakeLists.txt +++ b/src/quickgui/plugin/CMakeLists.txt @@ -30,7 +30,6 @@ include_directories( include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 9d6d833f7f35..95110d5daeaf 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -3,7 +3,6 @@ include_directories( ) include_directories(SYSTEM ${POSTGRES_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ) @@ -25,7 +24,6 @@ macro (ADD_QGIS_GRASS_TEST grass_build_version testname testsrc) ${QT_VERSION_BASE}::Core ${QT_VERSION_BASE}::Svg ${QT_VERSION_BASE}::Test - ${GEOS_LIBRARY} qgis_core qgis_test qgisgrass${grass_build_version} diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index d6250e875ba2..5b7a2cb651cb 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -16,7 +16,6 @@ include_directories( ) include_directories(SYSTEM - ${GEOS_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} ${QCA_INCLUDE_DIR} diff --git a/tests/src/quickgui/app/CMakeLists.txt b/tests/src/quickgui/app/CMakeLists.txt index 4fb7b4c389c9..d61939d5a48c 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -23,7 +23,6 @@ include_directories( include_directories(SYSTEM ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) From 04a6f41a73f50bf5a901b77a49385537b3d89931 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 20 Sep 2023 11:09:18 +0300 Subject: [PATCH 039/151] make sure that layer is valid before accesing its methods (fix #54648) --- .../processing/qgsprocessingpointcloudexpressionlineedit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/processing/qgsprocessingpointcloudexpressionlineedit.cpp b/src/gui/processing/qgsprocessingpointcloudexpressionlineedit.cpp index 24c9709a310f..47cc9d6a3e99 100644 --- a/src/gui/processing/qgsprocessingpointcloudexpressionlineedit.cpp +++ b/src/gui/processing/qgsprocessingpointcloudexpressionlineedit.cpp @@ -309,7 +309,7 @@ void QgsProcessingPointCloudExpressionDialog::test() int offset; for ( const auto &attribute : attributes ) { - if ( mLayer->dataProvider() && + if ( mLayer && mLayer->dataProvider() && !mLayer->dataProvider()->attributes().find( attribute, offset ) ) { QMessageBox::warning( this, From c448e633ae465dfa71956a3e2aceb7ce79c24ede Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Wed, 20 Sep 2023 21:16:47 +0200 Subject: [PATCH 040/151] Update doc --- python/core/auto_generated/qgsdatasourceuri.sip.in | 5 +++++ src/core/qgsdatasourceuri.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/python/core/auto_generated/qgsdatasourceuri.sip.in b/python/core/auto_generated/qgsdatasourceuri.sip.in index 900bc63d51ba..e0e6387b2716 100644 --- a/python/core/auto_generated/qgsdatasourceuri.sip.in +++ b/python/core/auto_generated/qgsdatasourceuri.sip.in @@ -170,6 +170,11 @@ Sets the ``password`` for the URI. static QString removePassword( const QString &aUri, bool hide = false ); %Docstring Removes the password element from a URI. + +:param aUri: A data source uri +:param hide: ``True`` to replace the password value with 'xxxxxxxx', ``False`` to remove password (key and value) (since QGIS 3.34) + +:return: The data source uri without the password %End QString authConfigId() const; diff --git a/src/core/qgsdatasourceuri.h b/src/core/qgsdatasourceuri.h index a71911c1c22e..a67c215b7707 100644 --- a/src/core/qgsdatasourceuri.h +++ b/src/core/qgsdatasourceuri.h @@ -187,6 +187,11 @@ class CORE_EXPORT QgsDataSourceUri /** * Removes the password element from a URI. + * + * \param aUri A data source uri + * \param hide TRUE to replace the password value with 'xxxxxxxx', FALSE to remove password (key and value) (since QGIS 3.34) + * + * \returns The data source uri without the password */ static QString removePassword( const QString &aUri, bool hide = false ); From d156fbabfcdd5bf632f2643d933a507d6885f96b Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 21 Sep 2023 17:32:42 +0200 Subject: [PATCH 041/151] ATLAS: LEGEND support clipping Fix #8155 --- src/core/layout/qgslayoutitemlegend.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/core/layout/qgslayoutitemlegend.cpp b/src/core/layout/qgslayoutitemlegend.cpp index d1a10b441a7f..f972dc6397a8 100644 --- a/src/core/layout/qgslayoutitemlegend.cpp +++ b/src/core/layout/qgslayoutitemlegend.cpp @@ -1133,14 +1133,29 @@ void QgsLayoutItemLegend::doUpdateFilterByMap() mapSettings.setExpressionContext( createExpressionContext() ); - const QgsGeometry atlasGeometry = mInAtlas ? mLayout->reportContext().currentGeometry( mapSettings.destinationCrs() ) : QgsGeometry(); + const QgsGeometry atlasGeometry { mLayout->reportContext().currentGeometry( mapSettings.destinationCrs() ) }; QgsLayerTreeFilterSettings filterSettings( mapSettings ); + QList layersToClip; + if ( !atlasGeometry.isNull() && mMap->atlasClippingSettings()->enabled() ) + { + layersToClip = mMap->atlasClippingSettings()->layersToClip(); + for ( QgsMapLayer *layer : std::as_const( layersToClip ) ) + { + QList mapLayers { filterSettings.mapSettings().layers( true ) }; + mapLayers.removeAll( layer ); + filterSettings.mapSettings().setLayers( mapLayers ); + filterSettings.addVisibleExtentForLayer( layer, QgsReferencedGeometry( atlasGeometry, mapSettings.destinationCrs() ) ); + } + } + + if ( !linkedFilterMaps.empty() ) { for ( QgsLayoutItemMap *map : std::as_const( linkedFilterMaps ) ) { + if ( map == mMap ) continue; @@ -1160,7 +1175,7 @@ void QgsLayoutItemLegend::doUpdateFilterByMap() const QList< QgsMapLayer * > layersForMap = map->layersToRender(); for ( QgsMapLayer *layer : layersForMap ) { - if ( !atlasGeometry.isNull() ) + if ( mInAtlas && !atlasGeometry.isNull() ) { mapExtent = mapExtent.intersection( atlasGeometry ); } From 311b3ad29dab33c64a5bab18a66aa000430a7106 Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Thu, 21 Sep 2023 16:40:33 +0200 Subject: [PATCH 042/151] [OGR] Allow CTE SQLite subsetstring --- src/core/providers/ogr/qgsogrproviderutils.cpp | 3 ++- tests/src/python/test_provider_ogr_gpkg.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/providers/ogr/qgsogrproviderutils.cpp b/src/core/providers/ogr/qgsogrproviderutils.cpp index 001f9f812543..9d2c9188d2b3 100644 --- a/src/core/providers/ogr/qgsogrproviderutils.cpp +++ b/src/core/providers/ogr/qgsogrproviderutils.cpp @@ -1389,7 +1389,8 @@ OGRLayerH QgsOgrProviderUtils::setSubsetString( OGRLayerH layer, GDALDatasetH ds } } OGRLayerH subsetLayer = nullptr; - if ( cleanedSubsetString.startsWith( QLatin1String( "SELECT " ), Qt::CaseInsensitive ) ) + if ( cleanedSubsetString.startsWith( QLatin1String( "SELECT " ), Qt::CaseInsensitive ) || + cleanedSubsetString.startsWith( QLatin1String( "WITH " ), Qt::CaseInsensitive ) ) { QByteArray sql = encoding->fromUnicode( cleanedSubsetString ); diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 7d374c9955b9..090fe11d7dde 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -563,6 +563,11 @@ def testSelectSubsetString(self): vl.setSubsetString("SELECT fid, foo FROM test WHERE foo = 'baz'") got = [feat for feat in vl.getFeatures()] self.assertEqual(len(got), 1) + + # test SQLite CTE Common Table Expression (issue https://github.com/qgis/QGIS/issues/54677) + vl.setSubsetString("WITH test_cte AS (SELECT fid, foo FROM test WHERE foo = 'baz') SELECT * FROM test_cte") + self.assertEqual(len(got), 1) + del vl testdata_path = unitTestDataPath('provider') From 892146c54833196e626ecc60e2eafeb1f289476e Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 21 Sep 2023 14:04:57 +0200 Subject: [PATCH 043/151] qgscameracontroller: Ensure to keep sane values for camera speed With the current logic in `QgsCameraController::onWheel`, once the speed becomes null, it cannot increase anymore and navigation stops working. On the other hand, there is no upper limit for the speed. This means, that it can become very (too) important, even infinite. These issues are fixed by limiting the values of `mCameraMovementSpeed` in its setter. --- src/3d/qgscameracontroller.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 18c6aabfc6a3..5582053adb9a 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -90,7 +90,9 @@ void QgsCameraController::setCameraMovementSpeed( double movementSpeed ) if ( movementSpeed == mCameraMovementSpeed ) return; - mCameraMovementSpeed = movementSpeed; + // If the speed becomes 0, navigation does not work anymore + // If the speed becomes too important, only one walk can move the view far from the scene. + mCameraMovementSpeed = std::clamp( movementSpeed, 0.05, 150.0 ); emit cameraMovementSpeedChanged( mCameraMovementSpeed ); } From 5c0ec0fdfb327adfb659df6ba59d4aaf2fe48d78 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 19 Sep 2023 16:11:00 +0300 Subject: [PATCH 044/151] mark project dirty after pasting layer style (fix #53693) --- src/app/qgisapp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 2b5ef5393cbb..a21ea5b9509d 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -10617,6 +10617,7 @@ void QgisApp::pasteStyle( QgsMapLayer *destinationLayer, QgsMapLayer::StyleCateg mLayerTreeView->refreshLayerSymbology( selectionLayer->id() ); selectionLayer->triggerRepaint(); + QgsProject::instance()->setDirty( true ); } } } From f22efde645a60b00881d7569163b140901bcc428 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 19 Sep 2023 15:02:44 +0300 Subject: [PATCH 045/151] add binary and boolean types to QgsFieldProxyModel to allow filtering corresponding field types. Also expose these field types to Processing field parameter (fix #53940) --- .../processing/qgsprocessingparameters.sip.in | 4 +- .../auto_generated/qgsfieldproxymodel.sip.in | 2 + .../processing/qgsprocessingparameters.cpp | 26 +++++++++ src/core/processing/qgsprocessingparameters.h | 4 +- src/core/qgsfieldproxymodel.cpp | 4 +- src/core/qgsfieldproxymodel.h | 6 +- .../qgsprocessingwidgetwrapperimpl.cpp | 16 ++++++ tests/src/analysis/testqgsprocessing.cpp | 15 +++++ tests/src/gui/testprocessinggui.cpp | 57 ++++++++++++++++--- .../src/gui/testqgsfieldexpressionwidget.cpp | 14 ++++- 10 files changed, 132 insertions(+), 16 deletions(-) diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in index cea473ec0f35..e3a0097747f1 100644 --- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in @@ -3172,7 +3172,9 @@ A vector layer or feature source field parameter for processing algorithms. Any, Numeric, String, - DateTime + DateTime, + Binary, + Boolean, }; QgsProcessingParameterField( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), diff --git a/python/core/auto_generated/qgsfieldproxymodel.sip.in b/python/core/auto_generated/qgsfieldproxymodel.sip.in index 123555c4e8fd..0a42cfd8d6d7 100644 --- a/python/core/auto_generated/qgsfieldproxymodel.sip.in +++ b/python/core/auto_generated/qgsfieldproxymodel.sip.in @@ -35,6 +35,8 @@ The :py:class:`QgsFieldProxyModel` class provides an easy to use model to displa Time, HideReadOnly, DateTime, + Binary, + Boolean, AllTypes, }; typedef QFlags Filters; diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index ca42c0157bc8..6b1362e1448a 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -5878,6 +5878,14 @@ QString QgsProcessingParameterField::asScriptCode() const code += QLatin1String( "datetime " ); break; + case Binary: + code += QLatin1String( "binary " ); + break; + + case Boolean: + code += QLatin1String( "boolean " ); + break; + case Any: break; } @@ -5923,6 +5931,14 @@ QString QgsProcessingParameterField::asPythonString( const QgsProcessing::Python case DateTime: dataType = QStringLiteral( "QgsProcessingParameterField.DateTime" ); break; + + case Binary: + dataType = QStringLiteral( "QgsProcessingParameterField.Binary" ); + break; + + case Boolean: + dataType = QStringLiteral( "QgsProcessingParameterField.Boolean" ); + break; } code += QStringLiteral( ", type=%1" ).arg( dataType ); @@ -6033,6 +6049,16 @@ QgsProcessingParameterField *QgsProcessingParameterField::fromScriptCode( const type = DateTime; def = def.mid( 9 ); } + else if ( def.startsWith( QLatin1String( "binary " ), Qt::CaseInsensitive ) ) + { + type = Binary; + def = def.mid( 7 ); + } + else if ( def.startsWith( QLatin1String( "boolean " ), Qt::CaseInsensitive ) ) + { + type = Boolean; + def = def.mid( 8 ); + } if ( def.startsWith( QLatin1String( "multiple" ), Qt::CaseInsensitive ) ) { diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index a057e911f9f4..54b01b7eee70 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -3065,7 +3065,9 @@ class CORE_EXPORT QgsProcessingParameterField : public QgsProcessingParameterDef Any = -1, //!< Accepts any field Numeric = 0, //!< Accepts numeric fields String = 1, //!< Accepts string fields - DateTime = 2 //!< Accepts datetime fields + DateTime = 2, //!< Accepts datetime fields + Binary = 3, //!< Accepts binary fields, since QGIS 3.34 + Boolean = 4, //!< Accepts boolean fields, since QGIS 3.34 }; /** diff --git a/src/core/qgsfieldproxymodel.cpp b/src/core/qgsfieldproxymodel.cpp index 3ad9720ff2f4..ed70501f3344 100644 --- a/src/core/qgsfieldproxymodel.cpp +++ b/src/core/qgsfieldproxymodel.cpp @@ -106,7 +106,9 @@ bool QgsFieldProxyModel::filterAcceptsRow( int source_row, const QModelIndex &so ( mFilters.testFlag( Date ) && type == QVariant::Date ) || ( mFilters.testFlag( Date ) && type == QVariant::DateTime ) || ( mFilters.testFlag( DateTime ) && type == QVariant::DateTime ) || - ( mFilters.testFlag( Time ) && type == QVariant::Time ) ) + ( mFilters.testFlag( Time ) && type == QVariant::Time ) || + ( mFilters.testFlag( Binary ) && type == QVariant::ByteArray ) || + ( mFilters.testFlag( Boolean ) && type == QVariant::Bool ) ) return true; return false; diff --git a/src/core/qgsfieldproxymodel.h b/src/core/qgsfieldproxymodel.h index 4ef458902936..769d4ff117b9 100644 --- a/src/core/qgsfieldproxymodel.h +++ b/src/core/qgsfieldproxymodel.h @@ -46,8 +46,10 @@ class CORE_EXPORT QgsFieldProxyModel : public QSortFilterProxyModel Date = 16, //!< Date or datetime fields Time = 32, //!< Time fields HideReadOnly = 64, //!< Hide read-only fields - DateTime = 128, //!< Datetime fieldss - AllTypes = Numeric | Date | String | Time, //!< All field types + DateTime = 128, //!< Datetime fields + Binary = 256, //!< Binary fields, since QGIS 3.34 + Boolean = 512, //!< Boolean fields, since QGIS 3.34 + AllTypes = Numeric | Date | String | Time | DateTime | Binary | Boolean, //!< All field types }; Q_DECLARE_FLAGS( Filters, Filter ) Q_FLAG( Filters ) diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 09342263b71f..b076169eab4f 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -4412,6 +4412,8 @@ QgsProcessingFieldParameterDefinitionWidget::QgsProcessingFieldParameterDefiniti mDataTypeComboBox->addItem( tr( "Number" ), QgsProcessingParameterField::Numeric ); mDataTypeComboBox->addItem( tr( "String" ), QgsProcessingParameterField::String ); mDataTypeComboBox->addItem( tr( "Date/time" ), QgsProcessingParameterField::DateTime ); + mDataTypeComboBox->addItem( tr( "Binary" ), QgsProcessingParameterField::Binary ); + mDataTypeComboBox->addItem( tr( "Boolean" ), QgsProcessingParameterField::Boolean ); if ( const QgsProcessingParameterField *fieldParam = dynamic_cast( definition ) ) mDataTypeComboBox->setCurrentIndex( mDataTypeComboBox->findData( fieldParam->dataType() ) ); @@ -4498,6 +4500,10 @@ QWidget *QgsProcessingFieldWidgetWrapper::createWidget() mComboBox->setFilters( QgsFieldProxyModel::String ); else if ( fieldParam->dataType() == QgsProcessingParameterField::DateTime ) mComboBox->setFilters( QgsFieldProxyModel::Date | QgsFieldProxyModel::Time | QgsFieldProxyModel::DateTime ); + else if ( fieldParam->dataType() == QgsProcessingParameterField::Binary ) + mComboBox->setFilters( QgsFieldProxyModel::Binary ); + else if ( fieldParam->dataType() == QgsProcessingParameterField::Boolean ) + mComboBox->setFilters( QgsFieldProxyModel::Boolean ); mComboBox->setToolTip( parameterDefinition()->toolTip() ); connect( mComboBox, &QgsFieldComboBox::fieldChanged, this, [ = ]( const QString & ) @@ -4796,6 +4802,16 @@ QgsFields QgsProcessingFieldWidgetWrapper::filterFields( const QgsFields &fields if ( f.type() == QVariant::Date || f.type() == QVariant::Time || f.type() == QVariant::DateTime ) res.append( f ); break; + + case QgsProcessingParameterField::Binary: + if ( f.type() == QVariant::ByteArray ) + res.append( f ); + break; + + case QgsProcessingParameterField::Boolean: + if ( f.type() == QVariant::Bool ) + res.append( f ); + break; } } diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 21a470ffc2b6..5ccf8f42a853 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -6861,6 +6861,21 @@ void TestQgsProcessing::parameterField() QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() ); + def->setDataType( QgsProcessingParameterField::Binary ); + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterField('non_optional', '', type=QgsProcessingParameterField.Binary, parentLayerParameterName='my_parent', allowMultiple=False, defaultValue=None)" ) ); + code = def->asScriptCode(); + fromCode.reset( dynamic_cast< QgsProcessingParameterField * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); + QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); + QCOMPARE( fromCode->dataType(), def->dataType() ); + QCOMPARE( fromCode->allowMultiple(), def->allowMultiple() ); + QCOMPARE( fromCode->defaultToAllFields(), def->defaultToAllFields() ); + // multiple def.reset( new QgsProcessingParameterField( "non_optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, true, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index 609ac8ac7a1b..aef0d5300362 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -3326,6 +3326,8 @@ void TestProcessingGui::testFieldWrapper() f.append( QgsField( QStringLiteral( "date" ), QVariant::Date ) ); f.append( QgsField( QStringLiteral( "time" ), QVariant::Time ) ); f.append( QgsField( QStringLiteral( "datetime" ), QVariant::DateTime ) ); + f.append( QgsField( QStringLiteral( "binary" ), QVariant::ByteArray ) ); + f.append( QgsField( QStringLiteral( "boolean" ), QVariant::Bool ) ); QgsFields f2 = wrapper3.filterFields( f ); QCOMPARE( f2, f ); @@ -3426,18 +3428,55 @@ void TestProcessingGui::testFieldWrapper() QCOMPARE( f2.at( 1 ).name(), QStringLiteral( "time" ) ); QCOMPARE( f2.at( 2 ).name(), QStringLiteral( "datetime" ) ); + // binary fields + param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Binary, false, true ); + QgsProcessingFieldWidgetWrapper wrapper7( ¶m, type ); + w = wrapper7.createWrappedWidget( context ); + switch ( type ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper7.wrappedWidget() )->filters(), QgsFieldProxyModel::Binary ); + break; + + case QgsProcessingGui::Modeler: + break; + } + f2 = wrapper7.filterFields( f ); + QCOMPARE( f2.size(), 1 ); + QCOMPARE( f2.at( 0 ).name(), QStringLiteral( "binary" ) ); + delete w; + + // boolean fields + param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Boolean, false, true ); + QgsProcessingFieldWidgetWrapper wrapper8( ¶m, type ); + w = wrapper8.createWrappedWidget( context ); + switch ( type ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Batch: + QCOMPARE( static_cast< QgsFieldComboBox * >( wrapper8.wrappedWidget() )->filters(), QgsFieldProxyModel::Boolean ); + break; + + case QgsProcessingGui::Modeler: + break; + } + f2 = wrapper8.filterFields( f ); + QCOMPARE( f2.size(), 1 ); + QCOMPARE( f2.at( 0 ).name(), QStringLiteral( "boolean" ) ); + delete w; // default to all fields param = QgsProcessingParameterField( QStringLiteral( "field" ), QStringLiteral( "field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ); param.setDefaultToAllFields( true ); - QgsProcessingFieldWidgetWrapper wrapper7( ¶m, type ); - w = wrapper7.createWrappedWidget( context ); - wrapper7.setParentLayerWrapperValue( &layerWrapper ); + QgsProcessingFieldWidgetWrapper wrapper9( ¶m, type ); + w = wrapper9.createWrappedWidget( context ); + wrapper9.setParentLayerWrapperValue( &layerWrapper ); switch ( type ) { case QgsProcessingGui::Standard: case QgsProcessingGui::Batch: - QCOMPARE( wrapper7.widgetValue().toList(), QVariantList() << QStringLiteral( "aaa" ) << QStringLiteral( "bbb" ) ); + QCOMPARE( wrapper9.widgetValue().toList(), QVariantList() << QStringLiteral( "aaa" ) << QStringLiteral( "bbb" ) ); break; case QgsProcessingGui::Modeler: @@ -3449,17 +3488,17 @@ void TestProcessingGui::testFieldWrapper() QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "LineString?field=bbb:string" ), QStringLiteral( "y" ), QStringLiteral( "memory" ) ); p.addMapLayer( vl2 ); - QgsProcessingFieldWidgetWrapper wrapper8( ¶m, type ); - wrapper8.registerProcessingContextGenerator( &generator ); - w = wrapper8.createWrappedWidget( context ); + QgsProcessingFieldWidgetWrapper wrapper10( ¶m, type ); + wrapper10.registerProcessingContextGenerator( &generator ); + w = wrapper10.createWrappedWidget( context ); layerWrapper.setWidgetValue( QVariantList() << vl->id() << vl2->id(), context ); - wrapper8.setParentLayerWrapperValue( &layerWrapper ); + wrapper10.setParentLayerWrapperValue( &layerWrapper ); switch ( type ) { case QgsProcessingGui::Standard: case QgsProcessingGui::Batch: - QCOMPARE( wrapper8.widgetValue().toList(), QVariantList() << QStringLiteral( "bbb" ) ); + QCOMPARE( wrapper10.widgetValue().toList(), QVariantList() << QStringLiteral( "bbb" ) ); break; case QgsProcessingGui::Modeler: diff --git a/tests/src/gui/testqgsfieldexpressionwidget.cpp b/tests/src/gui/testqgsfieldexpressionwidget.cpp index 1a6f4f1952a0..5a4d2370e282 100644 --- a/tests/src/gui/testqgsfieldexpressionwidget.cpp +++ b/tests/src/gui/testqgsfieldexpressionwidget.cpp @@ -286,13 +286,13 @@ void TestQgsFieldExpressionWidget::testIsValid() void TestQgsFieldExpressionWidget::testFilters() { - QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=intfld:int&field=stringfld:string&field=string2fld:string&field=longfld:long&field=doublefld:double&field=datefld:date&field=timefld:time&field=datetimefld:datetime" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ); + QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=intfld:int&field=stringfld:string&field=string2fld:string&field=longfld:long&field=doublefld:double&field=datefld:date&field=timefld:time&field=datetimefld:datetime&field=binaryfld:binary&field=booleanfld:boolean" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ); QgsProject::instance()->addMapLayer( layer ); std::unique_ptr< QgsFieldExpressionWidget > widget( new QgsFieldExpressionWidget() ); widget->setLayer( layer ); - QCOMPARE( widget->mCombo->count(), 8 ); + QCOMPARE( widget->mCombo->count(), 10 ); QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "intfld" ) ); QCOMPARE( widget->mCombo->itemText( 1 ), QStringLiteral( "stringfld" ) ); QCOMPARE( widget->mCombo->itemText( 2 ), QStringLiteral( "string2fld" ) ); @@ -301,6 +301,8 @@ void TestQgsFieldExpressionWidget::testFilters() QCOMPARE( widget->mCombo->itemText( 5 ), QStringLiteral( "datefld" ) ); QCOMPARE( widget->mCombo->itemText( 6 ), QStringLiteral( "timefld" ) ); QCOMPARE( widget->mCombo->itemText( 7 ), QStringLiteral( "datetimefld" ) ); + QCOMPARE( widget->mCombo->itemText( 8 ), QStringLiteral( "binaryfld" ) ); + QCOMPARE( widget->mCombo->itemText( 9 ), QStringLiteral( "booleanfld" ) ); widget->setFilters( QgsFieldProxyModel::String ); QCOMPARE( widget->mCombo->count(), 2 ); @@ -338,6 +340,14 @@ void TestQgsFieldExpressionWidget::testFilters() QCOMPARE( widget->mCombo->count(), 1 ); QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "datetimefld" ) ); + widget->setFilters( QgsFieldProxyModel::Binary ); + QCOMPARE( widget->mCombo->count(), 1 ); + QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "binaryfld" ) ); + + widget->setFilters( QgsFieldProxyModel::Boolean ); + QCOMPARE( widget->mCombo->count(), 1 ); + QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "booleanfld" ) ); + QgsProject::instance()->removeMapLayer( layer ); } From 0e7ab1d29f21a9c673620242f4b5ebeb67a27dcd Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 19 Sep 2023 11:29:03 +0300 Subject: [PATCH 046/151] add tests for GDAL algorithms --- .../tests/GdalAlgorithmsRasterTest.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py index 044510a93952..df15c6c06e37 100644 --- a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py +++ b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py @@ -76,6 +76,9 @@ from processing.algs.gdal.rasterize_over import rasterize_over from processing.algs.gdal.rasterize_over_fixed_value import rasterize_over_fixed_value from processing.algs.gdal.viewshed import viewshed +from processing.algs.gdal.roughness import roughness +from processing.algs.gdal.pct2rgb import pct2rgb +from processing.algs.gdal.rgb2pct import rgb2pct testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') @@ -2864,6 +2867,103 @@ def testBuildVrt(self): '-input_file_list buildvrtInputFiles.txt ' + outdir + '/check.vrt']) + def testPct2Rgb(self): + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + source = os.path.join(testDataPath, 'dem.tif') + alg = pct2rgb() + alg.initAlgorithm() + + with tempfile.TemporaryDirectory() as outdir: + # defaults + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['pct2rgb.py', + source + ' ' + outdir + '/check.tif ' + + '-of GTiff -b 1']) + + # set band + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'BAND': 3, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['pct2rgb.py', + source + ' ' + outdir + '/check.tif ' + + '-of GTiff -b 3']) + + # set RGBA + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'RGBA': True, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['pct2rgb.py', + source + ' ' + outdir + '/check.tif ' + + '-of GTiff -b 1 -rgba']) + + def testRgb2Pct(self): + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + source = os.path.join(testDataPath, 'dem.tif') + alg = rgb2pct() + alg.initAlgorithm() + + with tempfile.TemporaryDirectory() as outdir: + # defaults + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['rgb2pct.py', + '-n 2 -of GTiff ' + source + ' ' + outdir + '/check.tif']) + + # set number of colors + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'NCOLORS': 8, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['rgb2pct.py', + '-n 8 -of GTiff ' + source + ' ' + outdir + '/check.tif']) + + def testRoughness(self): + context = QgsProcessingContext() + feedback = QgsProcessingFeedback() + source = os.path.join(testDataPath, 'dem.tif') + alg = roughness() + alg.initAlgorithm() + + with tempfile.TemporaryDirectory() as outdir: + # defaults + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['gdaldem', + 'roughness ' + source + ' ' + outdir + '/check.tif ' + '-of GTiff -b 1']) + + # set band + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'BAND': 3, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['gdaldem', + 'roughness ' + source + ' ' + outdir + '/check.tif ' + '-of GTiff -b 3']) + + # compute edges + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'COMPUTE_EDGES': True, + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['gdaldem', + 'roughness ' + source + ' ' + outdir + '/check.tif ' + '-of GTiff -b 1 -compute_edges']) + + # creation options + self.assertEqual( + alg.getConsoleCommands({'INPUT': source, + 'OPTIONS': 'COMPRESS=DEFLATE|PREDICTOR=2|ZLEVEL=9', + 'OUTPUT': outdir + '/check.tif'}, context, feedback), + ['gdaldem', + 'roughness ' + source + ' ' + outdir + '/check.tif ' + + '-of GTiff -b 1 -co COMPRESS=DEFLATE -co PREDICTOR=2 -co ZLEVEL=9']) + if __name__ == '__main__': nose2.main() From f6efba543fbbf3767818fa1808322edb782c2d5b Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Mon, 18 Sep 2023 15:55:24 +0200 Subject: [PATCH 047/151] fix(3DSceneExporter): add entity sorting to have idempotent scene export --- src/3d/qgs3dsceneexporter.cpp | 3 +++ src/3d/symbols/qgspolygon3dsymbol.cpp | 9 ++++++++- tests/src/3d/testqgs3drendering.cpp | 8 ++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/3d/qgs3dsceneexporter.cpp b/src/3d/qgs3dsceneexporter.cpp index 42c132bcf5e7..109fba7fc529 100644 --- a/src/3d/qgs3dsceneexporter.cpp +++ b/src/3d/qgs3dsceneexporter.cpp @@ -544,6 +544,9 @@ 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/src/3d/symbols/qgspolygon3dsymbol.cpp b/src/3d/symbols/qgspolygon3dsymbol.cpp index 2f6034f6500e..1c31855901a7 100644 --- a/src/3d/symbols/qgspolygon3dsymbol.cpp +++ b/src/3d/symbols/qgspolygon3dsymbol.cpp @@ -24,6 +24,7 @@ #include "qgs3dsceneexporter.h" #include "qgsvectorlayerelevationproperties.h" #include "qgsvectorlayer.h" +#include "qgstessellatedpolygongeometry.h" QgsPolygon3DSymbol::QgsPolygon3DSymbol() : mMaterialSettings( std::make_unique< QgsPhongMaterialSettings >() ) @@ -170,7 +171,13 @@ void QgsPolygon3DSymbol::setMaterialSettings( QgsAbstractMaterialSettings *mater bool QgsPolygon3DSymbol::exportGeometries( Qgs3DSceneExporter *exporter, Qt3DCore::QEntity *entity, const QString &objectNamePrefix ) const { - const QList subEntities = entity->findChildren( QString(), Qt::FindDirectChildrenOnly ); + QList subEntities = entity->findChildren( QString(), Qt::FindDirectChildrenOnly ); + // sort geometries by their name in order to always export them in the same way: + std::sort( subEntities.begin(), subEntities.end(), []( const Qt3DCore::QEntity * a, const Qt3DCore::QEntity * b ) + { + return a->objectName() < b->objectName(); + } ); + if ( subEntities.isEmpty() ) { const QList renderers = entity->findChildren(); diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 947f4dad0cdb..fef670ea0b80 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -1750,7 +1750,7 @@ void TestQgs3DRendering::do3DSceneExport( int zoomLevelsCount, int expectedObjec QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() ); sum += o->indexes().size(); } - QCOMPARE( maxFaceCount, sum ); + QCOMPARE( sum, maxFaceCount ); exporter.save( QString( "test3DSceneExporter-%1" ).arg( zoomLevelsCount ), "/tmp/" ); QCOMPARE( exporter.mExportedFeatureIds.size(), 3 ); @@ -1803,8 +1803,12 @@ void TestQgs3DRendering::test3DSceneExporter() // =========== check with 1 big tile ==> 1 exported object do3DSceneExport( 1, 1, 165, scene, symbol3d, layerPoly, &engine ); + // =========== 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, 165, scene, symbol3d, layerPoly, &engine ); + do3DSceneExport( 3, 3, 216, scene, symbol3d, layerPoly, &engine ); + // =========== check with 16 tiles ==> 3 exported objects + do3DSceneExport( 4, 3, 132, scene, symbol3d, layerPoly, &engine ); // =========== check with 25 tiles ==> 3 exported objects do3DSceneExport( 5, 3, 165, scene, symbol3d, layerPoly, &engine ); From e0cb6887df5615cca1de703100a8fc3d3506e562 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 18 Sep 2023 12:17:07 +0200 Subject: [PATCH 048/151] QgsVectorLayerSaveAsDialog::createControls(): fix memory leak --- src/gui/ogr/qgsvectorlayersaveasdialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index aac62b2c08f0..a946c51371d5 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp @@ -235,7 +235,6 @@ QList > QgsVectorLayerSaveAsDialog::createControls( c for ( it = options.constBegin(); it != options.constEnd(); ++it ) { QgsVectorFileWriter::Option *option = it.value(); - QLabel *label = new QLabel( it.key() ); QWidget *control = nullptr; switch ( option->type ) { @@ -294,6 +293,8 @@ QList > QgsVectorLayerSaveAsDialog::createControls( c if ( control ) { + QLabel *label = new QLabel( it.key() ); + // Pack the tooltip in some html element, so it gets linebreaks. label->setToolTip( QStringLiteral( "

%1

" ).arg( option->docString.toHtmlEscaped() ) ); control->setToolTip( QStringLiteral( "

%1

" ).arg( option->docString.toHtmlEscaped() ) ); From 48e2faf4709b46fa28acb7214dc29bd39265c204 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 18 Sep 2023 12:52:36 +0200 Subject: [PATCH 049/151] QgsVectorLayerSaveAsDialog: uncheck 'Add saved filed to map' when selecting PGDump driver (refs #54548) --- src/gui/ogr/qgsvectorlayersaveasdialog.cpp | 19 ++++++++++++++++--- src/gui/ogr/qgsvectorlayersaveasdialog.h | 1 + 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index a946c51371d5..d1fdf91d67e1 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp @@ -682,7 +682,18 @@ void QgsVectorLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( int idx ) GDALDriverH hDriver = GDALGetDriverByName( format().toUtf8().constData() ); if ( hDriver ) { - mAddToCanvas->setEnabled( GDALGetMetadataItem( hDriver, GDAL_DCAP_OPEN, nullptr ) != nullptr ); + const bool canReopen = GDALGetMetadataItem( hDriver, GDAL_DCAP_OPEN, nullptr ) != nullptr; + if ( mAddToCanvas->isEnabled() && !canReopen ) + { + mAddToCanvasStateOnOpenCompatibleDriver = mAddToCanvas->isChecked(); + mAddToCanvas->setChecked( false ); + mAddToCanvas->setEnabled( false ); + } + else if ( !mAddToCanvas->isEnabled() && canReopen ) + { + mAddToCanvas->setChecked( mAddToCanvasStateOnOpenCompatibleDriver ); + mAddToCanvas->setEnabled( true ); + } } } @@ -1065,12 +1076,14 @@ QStringList QgsVectorLayerSaveAsDialog::attributesExportNames() const bool QgsVectorLayerSaveAsDialog::addToCanvas() const { - return mAddToCanvas->isChecked() && mAddToCanvas->isEnabled(); + return mAddToCanvas->isChecked(); } void QgsVectorLayerSaveAsDialog::setAddToCanvas( bool enabled ) { - mAddToCanvas->setChecked( enabled ); + mAddToCanvasStateOnOpenCompatibleDriver = enabled; + if ( mAddToCanvas->isEnabled() ) + mAddToCanvas->setChecked( enabled ); } Qgis::FeatureSymbologyExport QgsVectorLayerSaveAsDialog::symbologyExport() const diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.h b/src/gui/ogr/qgsvectorlayersaveasdialog.h index eea4f08ea89d..57d39273eb71 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.h +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.h @@ -287,6 +287,7 @@ class GUI_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec QgsVectorFileWriter::ActionOnExistingFile mActionOnExistingFile; Options mOptions = Option::AllOptions; QString mDefaultOutputLayerNameFromInputLayerName; + bool mAddToCanvasStateOnOpenCompatibleDriver = true; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorLayerSaveAsDialog::Options ) From 5450fa073226cb3f0bb4733529c8b32d53f60093 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 18 Sep 2023 10:12:27 +0200 Subject: [PATCH 050/151] Fixes the wrong reference point for mode fill above and fill below --- .../elevation/qgsabstractprofilesurfacegenerator.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp b/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp index f9b35b9c7170..be435b57103d 100644 --- a/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp +++ b/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp @@ -302,10 +302,6 @@ void QgsAbstractProfileSurfaceResults::renderResults( QgsProfileRenderContext &c double currentPartStartDistance = 0; for ( auto pointIt = mDistanceToHeightMap.constBegin(); pointIt != mDistanceToHeightMap.constEnd(); ++pointIt ) { - if ( std::isnan( prevDistance ) ) - { - currentPartStartDistance = pointIt.key(); - } if ( std::isnan( pointIt.value() ) ) { if ( currentLine.length() > 1 ) @@ -333,7 +329,10 @@ void QgsAbstractProfileSurfaceResults::renderResults( QgsProfileRenderContext &c currentLine.clear(); continue; } - + if ( currentLine.length() < 1 ) + { + currentPartStartDistance = pointIt.key(); + } currentLine.append( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ) ); prevDistance = pointIt.key(); } From 6783df5d59946adcf350d5bb6d744de18163d3fd Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 18 Sep 2023 10:57:07 +0200 Subject: [PATCH 051/151] fix indentation --- src/core/elevation/qgsabstractprofilesurfacegenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp b/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp index be435b57103d..adf5ddeb1594 100644 --- a/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp +++ b/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp @@ -331,7 +331,7 @@ void QgsAbstractProfileSurfaceResults::renderResults( QgsProfileRenderContext &c } if ( currentLine.length() < 1 ) { - currentPartStartDistance = pointIt.key(); + currentPartStartDistance = pointIt.key(); } currentLine.append( context.worldTransform().map( QPointF( pointIt.key(), pointIt.value() ) ) ); prevDistance = pointIt.key(); From d07bd5a5a43906b34d396536ecb1b0fb2429d3af Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 22 Sep 2023 09:29:26 +1000 Subject: [PATCH 052/151] Resync tinygltf --- external/tinygltf/tiny_gltf.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/external/tinygltf/tiny_gltf.h b/external/tinygltf/tiny_gltf.h index c756481f89bf..b53234a0173f 100644 --- a/external/tinygltf/tiny_gltf.h +++ b/external/tinygltf/tiny_gltf.h @@ -4926,13 +4926,13 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model, } if (supposedComponentType > model->accessors[primitive->indices].componentType) { - model->accessors[primitive->indices].componentType = supposedComponentType; if (warn) { (*warn) += "GLTF component type " + std::to_string(model->accessors[primitive->indices].componentType) + " is not sufficient for number of stored points," " treating as " + std::to_string(supposedComponentType) + "\n"; } + model->accessors[primitive->indices].componentType = supposedComponentType; } } @@ -6768,10 +6768,6 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, bin_size_ = size_t(chunk1_length); } - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - chunk0_length); - is_binary_ = true; bool ret = LoadFromString(model, err, warn, From bf22a165b3f6f2f10156b1674a1c4f485bc8874b Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Tue, 5 Sep 2023 10:50:33 +0200 Subject: [PATCH 053/151] vectorlayerchunkloader: Do not apply terrain offset if clamping is absolute If a terrain offset is applied, all the shapes are vertically translated by the size of the offset. However, if the clamping mode of the layer is set to `Absolute` (`Qgis::AltitudeClamping::Absolute`), only the elevation of the shape is taken into account. This means that the terrain offset should not be taken into account. This issue is fixed by checking the altitude clamping mode of the symbol before applying the offset. If the clamping is `Absolute`, the offset is not applied. --- src/3d/qgsvectorlayerchunkloader_p.cpp | 52 +++++++++++++++++++++++++- src/3d/qgsvectorlayerchunkloader_p.h | 2 + 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/3d/qgsvectorlayerchunkloader_p.cpp b/src/3d/qgsvectorlayerchunkloader_p.cpp index cdea34ee0177..430a0c80274a 100644 --- a/src/3d/qgsvectorlayerchunkloader_p.cpp +++ b/src/3d/qgsvectorlayerchunkloader_p.cpp @@ -15,6 +15,9 @@ #include "qgsvectorlayerchunkloader_p.h" #include "qgs3dutils.h" +#include "qgsline3dsymbol.h" +#include "qgspoint3dsymbol.h" +#include "qgspolygon3dsymbol.h" #include "qgsraycastingutils_p.h" #include "qgsabstractvectorlayer3drenderer.h" #include "qgstessellatedpolygongeometry.h" @@ -186,7 +189,10 @@ QgsVectorLayerChunkedEntity::QgsVectorLayerChunkedEntity( QgsVectorLayer *vl, do new QgsVectorLayerChunkLoaderFactory( map, vl, symbol, tilingSettings.zoomLevelsCount() - 1, zMin, zMax ), true ) { mTransform = new Qt3DCore::QTransform; - mTransform->setTranslation( QVector3D( 0.0f, map.terrainElevationOffset(), 0.0f ) ); + if ( applyTerrainOffset() ) + { + mTransform->setTranslation( QVector3D( 0.0f, map.terrainElevationOffset(), 0.0f ) ); + } this->addComponent( mTransform ); connect( &map, &Qgs3DMapSettings::terrainElevationOffsetChanged, this, &QgsVectorLayerChunkedEntity::onTerrainElevationOffsetChanged ); @@ -200,9 +206,53 @@ QgsVectorLayerChunkedEntity::~QgsVectorLayerChunkedEntity() cancelActiveJobs(); } +// if the AltitudeClamping is `Absolute`, do not apply the offset +bool QgsVectorLayerChunkedEntity::applyTerrainOffset() const +{ + QgsVectorLayerChunkLoaderFactory *loaderFactory = static_cast( mChunkLoaderFactory ); + if ( loaderFactory ) + { + QString symbolType = loaderFactory->mSymbol.get()->type(); + if ( symbolType == "line" ) + { + QgsLine3DSymbol *lineSymbol = static_cast( loaderFactory->mSymbol.get() ); + if ( lineSymbol && lineSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute ) + { + return false; + } + } + else if ( symbolType == "point" ) + { + QgsPoint3DSymbol *pointSymbol = static_cast( loaderFactory->mSymbol.get() ); + if ( pointSymbol && pointSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute ) + { + return false; + } + } + else if ( symbolType == "polygon" ) + { + QgsPolygon3DSymbol *polygonSymbol = static_cast( loaderFactory->mSymbol.get() ); + if ( polygonSymbol && polygonSymbol->altitudeClamping() == Qgis::AltitudeClamping::Absolute ) + { + return false; + } + } + else + { + QgsDebugMsgLevel( QStringLiteral( "QgsVectorLayerChunkedEntity::applyTerrainOffset, unhandled symbol type %1" ).arg( symbolType ), 2 ); + } + } + + return true; +} + void QgsVectorLayerChunkedEntity::onTerrainElevationOffsetChanged( float newOffset ) { QgsDebugMsgLevel( QStringLiteral( "QgsVectorLayerChunkedEntity::onTerrainElevationOffsetChanged" ), 2 ); + if ( !applyTerrainOffset() ) + { + newOffset = 0.0; + } mTransform->setTranslation( QVector3D( 0.0f, newOffset, 0.0f ) ); } diff --git a/src/3d/qgsvectorlayerchunkloader_p.h b/src/3d/qgsvectorlayerchunkloader_p.h index 910ecd23cde8..38bdeeb0cb98 100644 --- a/src/3d/qgsvectorlayerchunkloader_p.h +++ b/src/3d/qgsvectorlayerchunkloader_p.h @@ -133,6 +133,8 @@ class QgsVectorLayerChunkedEntity : public QgsChunkedEntity static QVector rayIntersection( const QList &activeNodes, const QMatrix4x4 &transformMatrix, const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ); Qt3DCore::QTransform *mTransform = nullptr; + + bool applyTerrainOffset() const; }; /// @endcond From 5045a9b3feaaf50d782f9398f6aae36813142bc9 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Wed, 20 Sep 2023 21:02:08 +0200 Subject: [PATCH 054/151] Fix session_role setting --- src/providers/postgres/qgspgnewconnection.cpp | 3 +-- src/providers/postgres/qgspostgresproviderconnection.cpp | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/postgres/qgspgnewconnection.cpp b/src/providers/postgres/qgspgnewconnection.cpp index 3d906b001455..cebb3ccd168c 100644 --- a/src/providers/postgres/qgspgnewconnection.cpp +++ b/src/providers/postgres/qgspgnewconnection.cpp @@ -163,7 +163,6 @@ void QgsPgNewConnection::accept() settings.setValue( baseKey + "/host", txtHost->text() ); settings.setValue( baseKey + "/port", txtPort->text() ); settings.setValue( baseKey + "/database", txtDatabase->text() ); - settings.setValue( baseKey + "/session_role", txtSessionRole->text() ); settings.setValue( baseKey + "/username", mAuthSettings->storeUsernameIsChecked( ) ? mAuthSettings->username() : QString() ); settings.setValue( baseKey + "/password", mAuthSettings->storePasswordIsChecked( ) && !hasAuthConfigID ? mAuthSettings->password() : QString() ); settings.setValue( baseKey + "/authcfg", mAuthSettings->configId() ); @@ -192,7 +191,7 @@ void QgsPgNewConnection::accept() configuration.insert( "estimatedMetadata", cb_useEstimatedMetadata->isChecked() ); configuration.insert( "projectsInDatabase", cb_projectsInDatabase->isChecked() ); configuration.insert( "metadataInDatabase", cb_metadataInDatabase->isChecked() ); - + configuration.insert( "session_role", txtSessionRole->text() ); QgsProviderMetadata *providerMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "postgres" ) ); std::unique_ptr< QgsPostgresProviderConnection > providerConnection( qgis::down_cast( providerMetadata->createConnection( txtName->text() ) ) ); diff --git a/src/providers/postgres/qgspostgresproviderconnection.cpp b/src/providers/postgres/qgspostgresproviderconnection.cpp index a205996cc93f..21d2c339d982 100644 --- a/src/providers/postgres/qgspostgresproviderconnection.cpp +++ b/src/providers/postgres/qgspostgresproviderconnection.cpp @@ -45,6 +45,7 @@ const QStringList QgsPostgresProviderConnection::CONFIGURATION_PARAMETERS = QStringLiteral( "estimatedMetadata" ), QStringLiteral( "projectsInDatabase" ), QStringLiteral( "metadataInDatabase" ), + QStringLiteral( "session_role" ), }; const QString QgsPostgresProviderConnection::SETTINGS_BASE_KEY = QStringLiteral( "/PostgreSQL/connections/" ); From c0ae76b3d99cce258b6a2755fbf34e5539d980b6 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 22 Sep 2023 10:52:05 +0200 Subject: [PATCH 055/151] Remove unnecessary settings --- src/providers/postgres/qgspgnewconnection.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/providers/postgres/qgspgnewconnection.cpp b/src/providers/postgres/qgspgnewconnection.cpp index cebb3ccd168c..76fa0600721f 100644 --- a/src/providers/postgres/qgspgnewconnection.cpp +++ b/src/providers/postgres/qgspgnewconnection.cpp @@ -166,16 +166,7 @@ void QgsPgNewConnection::accept() settings.setValue( baseKey + "/username", mAuthSettings->storeUsernameIsChecked( ) ? mAuthSettings->username() : QString() ); settings.setValue( baseKey + "/password", mAuthSettings->storePasswordIsChecked( ) && !hasAuthConfigID ? mAuthSettings->password() : QString() ); settings.setValue( baseKey + "/authcfg", mAuthSettings->configId() ); - settings.setValue( baseKey + "/publicOnly", cb_publicSchemaOnly->isChecked() ); - settings.setValue( baseKey + "/geometryColumnsOnly", cb_geometryColumnsOnly->isChecked() ); - settings.setValue( baseKey + "/dontResolveType", cb_dontResolveType->isChecked() ); - settings.setValue( baseKey + "/allowGeometrylessTables", cb_allowGeometrylessTables->isChecked() ); settings.setValue( baseKey + "/sslmode", cbxSSLmode->currentData().toInt() ); - settings.setValue( baseKey + "/saveUsername", mAuthSettings->storeUsernameIsChecked( ) ? "true" : "false" ); - settings.setValue( baseKey + "/savePassword", mAuthSettings->storePasswordIsChecked( ) && !hasAuthConfigID ? "true" : "false" ); - settings.setValue( baseKey + "/estimatedMetadata", cb_useEstimatedMetadata->isChecked() ); - settings.setValue( baseKey + "/projectsInDatabase", cb_projectsInDatabase->isChecked() ); - settings.setValue( baseKey + "/metadataInDatabase", cb_metadataInDatabase->isChecked() ); // remove old save setting settings.remove( baseKey + "/save" ); From bbfefcfc81ef81f70df5b2e8d7ca1c79395859f0 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 22 Sep 2023 11:35:58 +0200 Subject: [PATCH 056/151] Add tests for atlas legend clipping --- tests/src/python/test_qgslayoutlegend.py | 33 +- .../expected_atlas_legend_clipping.png | Bin 0 -> 16320 bytes .../layouts/atlas_legend_clipping.qgs | 1428 +++++++++++++++++ 3 files changed, 1460 insertions(+), 1 deletion(-) create mode 100644 tests/testdata/control_images/composer_legend/expected_atlas_legend_clipping/expected_atlas_legend_clipping.png create mode 100644 tests/testdata/layouts/atlas_legend_clipping.qgs diff --git a/tests/src/python/test_qgslayoutlegend.py b/tests/src/python/test_qgslayoutlegend.py index 2add17e8d09c..5e802c177c3b 100644 --- a/tests/src/python/test_qgslayoutlegend.py +++ b/tests/src/python/test_qgslayoutlegend.py @@ -49,7 +49,8 @@ QgsSymbol, QgsVectorLayer, QgsReadWriteContext, - QgsTextFormat + QgsTextFormat, + QgsFeatureRequest, ) import unittest from qgis.testing import start_app, QgisTestCase @@ -1131,6 +1132,36 @@ def test_filter_by_map_content_rendering_different_layers(self): ) ) + def test_atlas_legend_clipping(self): + """Test issue GH #54654""" + + p = QgsProject() + self.assertTrue(p.read(os.path.join(TEST_DATA_DIR, 'layouts', 'atlas_legend_clipping.qgs'))) + + layout = p.layoutManager().layoutByName('layout1') + + layer = list(p.mapLayers().values())[0] + feature = layer.getFeature(3) + req = QgsFeatureRequest() + req.setFilterExpression('value = 11') + feature = next(layer.getFeatures(req)) + + layout.reportContext().setFeature(feature) + legend = layout.items()[0] + legend.setStyleFont(QgsLegendStyle.Title, QgsFontUtils.getStandardTestFont('Bold', 20)) + legend.setStyleFont(QgsLegendStyle.Group, QgsFontUtils.getStandardTestFont('Bold', 20)) + legend.setStyleFont(QgsLegendStyle.Subgroup, QgsFontUtils.getStandardTestFont('Bold', 20)) + legend.setStyleFont(QgsLegendStyle.Symbol, QgsFontUtils.getStandardTestFont('Bold', 20)) + legend.setStyleFont(QgsLegendStyle.SymbolLabel, + QgsFontUtils.getStandardTestFont('Bold', 20)) + legend.refresh() + + self.assertTrue( + self.render_layout_check( + 'atlas_legend_clipping', layout + ) + ) + def test_filter_by_map_content_rendering_different_layers_in_atlas(self): point_path = os.path.join(TEST_DATA_DIR, 'points.shp') point_layer = QgsVectorLayer(point_path, 'points', 'ogr') diff --git a/tests/testdata/control_images/composer_legend/expected_atlas_legend_clipping/expected_atlas_legend_clipping.png b/tests/testdata/control_images/composer_legend/expected_atlas_legend_clipping/expected_atlas_legend_clipping.png new file mode 100644 index 0000000000000000000000000000000000000000..f91ce4e232794ba548a2d3fb934d664aeb1bf057 GIT binary patch literal 16320 zcmeIZ2UApA*DbsWiYUS{0g8Z15CnmP5+n#7RB}!#5=62j1#D8&2r3E!hnzu?AX##z z;UKhR1p&z*$tGuPLie59_j%s>{=ipt>(;RpsoiVuwf0&wj5+4$*IF8iM-QJlj3CHS zWu==s2tsR)AT(zVQNxwL0x)y%AHB2ET^9slY()Q25u83)AjmmH`Q{Bh_r!$}kH{np z_WOj_Y-io0w9~=js?m8(MN{hI>>_6Ne!&jTe(VWm#}BF7oq1xzrg6l!<$OSL)x=e9 zs^j&V6|%FLv;9pQUZb>jp=B`FA#Gs0(uh*=qZg|Qt;?$TPgt8z9`_s-{GY)bM~fhTT!=uIZE+ky0!k;d12u5&)f*&`^Ue7|J{v$ZR1~0`F}7Jq-!%F&YdCb($Q+X)*bQsckiCw6h!J7 zxFdXRCs!sKrJpW*FMO!z8$|xNc`Ihv>Q!Y5Mo2oHvBYmbKQkhPrUqCxXj&@80$3QJ2WOQ<(ZT z0w#@6qt5snySwEuTH~=iT6+;68Qk>pk@coT*_K#YMqB9e@E@T~D&dXO{iA3@A`!<6eSq#J3JvI67tqnohBELJMV4^ZkZ3Jdi1 z_F@+vatd7Dq<``|d+{nuCHd@c_LtiaJb@#NFlEnnm`nDWkXLXKXHKa&{UdH8*I)X~ z&{I0Y{OYBuoPs*fA9Y-a2zv{+**`}a8SCJjgljjtvpD7L68lGrEGjY{bkD@OITLal zrCkW=>FG=|EAkEwi)mO4Vm1foMl01uqtp@P8PAg0dUze_{@(Pkuu|IR?ueJS_I$Xi zP~~*A5wFKV#Ox9be$L9u>P{xjntCmkMwqG?=wZqQ+i$lg;gkmnvyqqF7z;rH2e|FEZl$}nsyXB7H z)1P17NsPKbB&|(rGaW$G=tb%I-soK_eLUH$o*-^18h65Qa`oBE==Vp2m17KVNepJC zpRi0?kLei2%VKu^IU+1*T=i{c9FcEV$H?DU7%E>LuufTATI$40>WnyV`1LmSZfdK$ z8LZkroyh5;OG&eEaYoK5@>0!CM;eJ)s2*oukHJU?hcvy%9VE7AdxexO?ddO7l$2~t za#GyY)zwS5sE{z&ILRc(oR{R6(GP)!f$dZ4?CXuaIZ&LUAw{o-HBMmd-+oN8~!<9V?zT8+9>{ZiD1^>z@)c(usJWUs9tQXnz@{?-OlCyXo(#`dPL``i*x7 z;F*U$bC0X(WPN)WD!bKZ8lD2a*ds9`T>wb^FL zX!b;|6mxFwlE-;Wjni12O#l^=YA!}EnMXs%$iT>$ENSeq-|m=pM^IcxKPES~gPl|Q zslAO2-=GKLs|0H-8Filw9{=_8hgc6*y86#ExsMKqFrAhBV}cuz*Rwe8Y!|0y)t_%n z(EPDVetG(rNH_n{-VVkP#YJN(?Ao^dgl!dcyn`qH#&m7!JADgk9?&TVvMatK10R6H$7}7pb+A zLWD_WL~9D>K77lIU>rh-K;C`^pDnq7pA=#aN^iU5H~Ekd!V}SQ2ws*qMUDElz?$^Z zliS&v-hVq@iVAc-j+m|e_lAp66Ui0_4q z5x#$SBuLN^uuYx>_WzmqFC!zP72E8%@K5!*eW;VYdJeDi{o?}#=i#0r?j*ioXI8}b z18iSKSy`FHnD-S-BEPb0so-nZvxx6!^t!gHa$NTsy24j~t5KW(sR}^b1Hxj^T@`BN zGgkzHUI5_zF_IRAy*89#gMbFojDM*zhF&5ilH3rr%$)AGxdsFK3uh#jk`TKdzmxe zww>KtG|p|l&qIkGvAPsT+r&c&scxvs08{l*Q5h7f5l!OjNZQ=kaR1){auF}>tYKBI zk%9#>RlkbHzbU97BDyfk1r9YS98IuGwUJC3EpjCh_UlJOn%~bmAI(3bW8OCv57>XZ zf<#!X&vbOjQM{KbuoJC&`o}tziJ!;+>Gv@RS2<`HEo3|xn^{&S&SCft5iyRs4Y9$4 zkwb>{EK~1y{DVE6X?=$v;YTB-{1t062~IP(8yCWCY-|o?XT5v(pfC6CAU%?C1U8tv z0`-$%z24iBEes-GzI2OWY$wAcG)Z<+NlQP`KcDfjV;U|q?Y8j8&hEGdB60xULTA-| zn7w>9%cWsW&%?6uE7p>WrciAr*A(|eIx0H4Y)V@l5xMo5+xKy6^AFjMAg_29!oz`2 zpS1>@aRXoE(Vr{mPvVOFo;?#TLnq6hj_II;LUdG=n1n=mNlAN>ba{HZB`h_Y9=UKn z0_ijqXV#1|OmEFOxL$TAlu(q;rC`~(pRTZP34gi#EBykK>XO1+((I^xv%q!LUfByK z3l^wE8#c|&Oc{4X2Qx%7ZALL|=`n0XZewD212nc7;n}HB=kjXHBeFuXL(%&ELNBc>C@eCSHlrkO6(F z-UddWVT=2Ar=B_;<5@}Pad{)7q21B#x|VnrB+yTdI`*$_%TqL#9W0=T3T^cT%U`6m)%|6AjTQZb zy|xCdSp@HBX=@+*8jFbZL_%Uf9!6cd%xPmoOvJ2Cq#G_){)pDQYq{i!!%;MGJ3UH@ zG`^>s?2gx$MDi<_T(0ycha9c!={NBLcxUNYOOui7VIg{qk;ZyDL)zCi7uyi1 zcgtb_P*;Gf%c$hdD=+vx(%5 zOgRT9r%>?6SSpzWT9HLJ!8_VLF1fB1a?C*x!wHIja+1zgS$dZ!B_$-MIFyON~00Z(`BZ;w&f z@T+WKLn-?U)^v1qnxwY=JfrP=Ozk6B7-WQX+3jJMG3@q;=X@UgdDNee{WTFl_{C+@ zlc8?uQMXlEkhCXOKKw*=;edqB-JvoFO0MwA^Zf;|<7S>M z7NP((>SyIBq|wpQ@gJW*f5z<>P&PZIm(QL)eR{Kr>H4p6hw_oFVVAC?M1=$ir#cFG zS8gpVl!1vUWplS~f6)oQR5kC{6dQGj-IKh>565JVS&$f^;oS{ zNBqvtPNBE1qyWT63n-$3SU7Qga>*KEC5w==4;MhpYuLHgK-ATg?fv@o*n~{|Rh_$c zNqJ7#oV+}T?WGaQjwxk+FNWNPBMF0zR8GRu$+MKb6#tmxuRobmHcc_EuC5pi1-2KT zos;7qLnbd%d<5GDF<=FIq;>z_dq^rEr%={$ev=7Kb=S5t?_7!+L{Y{5>x;?m(@}=W z*e%qmE32!$pv*JfpEGo-B4!L%op=*U*_MA?xi$F6|KSmpE1KfWaz#^t;}MbJEmKUm zUyX&s+te#8xK$SXwimvCc(}%*B^>`N2>)v(K<;eKRS}Vu?+-(#Lns8wFHX{S<$g=b zUQ4FjKJjj`6%j5z^OPe`=8l z4f~_Z`zA~00F_?+@&U^$R59)+xAR_Zr$y7J$d%a5`X&ZYNB7{M-3?o-w#aC96Hk3! zC+UETDc;>r8SCSCj=vuFDNEG!{&9ko*--1;CA)o}yg5eMAM29Cjz6PYTdrf9v^K%b z>84x~7VctaVql;xr^xR7eBTgS(A?Y%g`5E9zKMy?Y7+|`es5>K0Kd_#Do29pH*-v} zP;ad}HTypXjNmHB*Wu2k??#AD{NDKhPO!`c>9lX~J0K<|MlxKJ=4K9JhiVD)5l4B= z?yI7zDh#i(CyXAqgM%!|Flhin%X01Z==jx&Wg!3uyUTcdQIV)5cBO%CeeA*R*5cSs zUpe_#DCJkuvbQxByH9t0g-UK)Qd5*ZP~*{~`EVZ9Cj{n5tL(bwzWlx~PZ_J9E(U^I z2ErV&>rwCCy&JwkCXd7ZzrOi{0q^YY?*1lcfW&qUyulKp1SA~yi5Dz>K@IGfYpAEP z)UCYGwvCDCuu5_N@fu#8d=cLKPMug{xSF52Ylm1r?eFB%aVgc~NZDAw`-r*R)-PSZ9jEI`4Ei`CNfl zhH@YEvUbW&czC!lmZ(~dStb*3djvX8@0IvR`KLFZ>xd^n1eBAGb~>8L9r2bm_@0*C z==zhiNs;qKh3-Qy47uBKo@BOItl`LAl!3lJ=0&~wZ{J2nYCYCx_5|K@2;3{R(_b0~ zWs~xFaB@{r_Zkm+^+!KAq~0J*P4l<tN1@#>qy7tMu zaM^oS8vmZeTN_CgynD;SR+ z#gf-kx^SCiLyl~_2ua$1dvg;~EzfUr1RoFH`4IqXg`7n)R8djM2Odazse-E_T#eTs z+dgr>$pJ@uYn~3G{)gIH*$SV{Uc&-Y{PptW2$gh$ynCfk6^-|% ztHz8zCa_OWPtW(|4OO{4a;^o;HQSlw=H^z}qv^92ftszKAtbH!a*NNTbveqq+x2-OT1xGwyeU`SLwI9i25eB^;^Elrg$vwmr^e>hrOb zo570_%TJx1yrZ@nX$ z1)lje^%fTw7l{Q2k$wy^t2HaxmAocQS-X-#YLCMo)KD~0Sk2DMt6eM`^21r>ur99S8%_@s+di}(R(UPQ6`TT?BT!>W*MkbNM0VZ-+*SK?2d7k zV*i=z&5>{%{qU%fx!`NZv%Fd|%s?qfdQr;uEYojZSS<|2=GN6+|EIxM zX-fEWu5tC2A$~{S%WHRRe`HLSG!+rR#OY{f2k}3F!+WLS#Sxa|wHWK-0qbOsnNEu3-@%%Vq`s*)STVjh-HK4lk+C>MF=~g

WLuU3pN0(D(tfwbVtxYN2kbn;-9`ggA{sIAT<7X0-P>85X?X`7v2%% zXe(P9baifWYt(ns?m8g8 zv1+39ta%w3Gq4*>j_!PHr{lMqJ=1CHB^iz$-JDkDjX1Gt47^jg3}t3)FX8nsp_5Tr zMc)sr@dI+MhRBlaz1A{(QEOI(_+{;p?zFyk=)roAfohD?gA^42$u;Di`N>sXr%*Jb zuMLfnpE~b+<_aD5TCP2?xAd&mYx^JpXq|*tPB*lHtjz+7`a(j-_04a(D)eT}-yuPu zaKyE$KIKh+e?O+}a(QBYq?yzc;K}X@80NQA9(IjjuIS@%xNlrg506y29dH763V=<) z;&5g0xYLni;mY-KeCctQD4f`#!NHdS0bFMf#$G+~Rq4cj=O6SEBUILfPo`Ue(VAPT zE=zv|6!d$J;MF$n*6=7XO9euI7@3$tCPJ7Fddh7k3M?j_!H9dZlMc?+X>vm1h-eod z5Qr%A3mJ5DU|^uPS4>9czNsnR=4)zBe$`xdPQFxs_3lABI@CG#1Y%4)ZH-4>cUApd zSn!5~jK(M+oY&UYz$fb_*_ZDWTLljt3WCzAb7@NF-pXJX@CT_tkbh8om{?SJXLs*Z zo|9u|5@7gsFhA5=-zOc8y_&Vws&{vqRM+BxZ{#0=T44+-OO5ThCKl7Zr~>cl5a}h> z$!cgR413HbQ8qLwC-0M>p4#s%@Dald4-Al&Df@WJF8qyP=`E<7i7x;U)PLWl<61}; z!$gL(ncD3UEK5++y{Gu^HM7(1Ql}>L5<{OgR~lbtIhYC1YdDNc@j@cqxlHH=FchH6 zgUVnJP4I;^y)D5Tq%It}fIL_5gwwMSDj0!+nl&fNmXoH(z(i~^c)u$TaKoT_lGYe&4;0rKLX z(=FWNXLF&Ul9fdSv<+w3Zp_cd5lh+G>GWX1bXD`QxiD>CjR+*<+1&vWhxo|J$m&Em+j62s1o9~Avz5%0RNOD>g@Feh5W*6l7yk0iXYOM?wSlS9hR zw@kkUWs%1S!fAzS0fc`wOVq7GE~m>(q3q4#H`0Zt>n5QOHM&(+4m=a2p^1qJVeg3} zz!=||@WH7uIabkhs=K(XgJr}}mhD#Wlo7v#csSXY(2Tl9UMi46IjF1Kf9pJ7N4$4> zQtDQl*)&J5Pi*5T+v}QWqE237zb&Q%DF9N~;@<7RCUa8x;^liKKfA~aU4F|I05ox{ z!sJ^kbCaQR9D9@Jni}nnJ9k1l8(UA_7Q96q<0<6UR{DX) z!{ZFVi&|e_k1N3K&sWy!cb)r%sk*X@qSfp$^TO|6-z%&Av8E*Pz*Z1_p*VlSWta zNV6v6H9%ZI=&=KQv*i}M6h0m0pepp6=_>+Ubd*i}K2!ow&6*Thybirf%iDlXGhR_t zv{p#q@Pe=rvrFn385#L5nGviHId>Ou+s4*bvw5Mb6c!xad)aHO%~aP85+h{?)-wS` zOK8{1OF${p*Y<^Csi60SRASkay_PEB8V`o2ygaJmgvQdr!NEfpX|2lCcq8z;!zgAma;)T$908P(<@1$4*Q9nw zz?9UwJ4PW72YWz0uvEQFo?ybU))+zM#1nIWZ7!!^Mq_(!x$`-DTIb4SPojfn> zu~0|=D#oQXhCGemivjd|^ypCt{FMSwhMhTmnqOErw8CtkhF{qwXWH!DikkTVNtW4o-P!rqV z-$$vB&P`0KD~nve(G{xBT&QMnrU8kg83*88O zD^w<&qWux57<7S9tVc*|r0YPBsX-N`e&qHV3K%w zqY%d)dg~#vhna;<5_fZO5A+QTnCEn9>!1|7zPnI-<9n_t9_)RyauVwNdCESXg3(ys z8p7`n!8xHdV5~GdE9O( z0iOB=i6sT71}vVu+F_w`ytbHyA~%2HrP5jE$5Yw z#(T9B0|oNkfuC50oUpflZ#^n95|YMI2$Nlsb;3PK2}{28kw7Q{1$ZD+QTR@ewDp7M z>(0q4qQv-41vkRhtX!p@k#1u1ekvc92llr+a?_dhR=6jb~O+>^jT zWwI`@357%Jn29%pRUm>iO}$r}U<=iFdjQn zv017pw;=v-ZxH#m+FL&T*Rf_L9WE-e!411yOJnG^3i_+&2m;1|noB;1dBkAj(GQe z@>B3ruBcLY*3CzzC3YXm_%VrUyeSY(p%dr(_-{}(*f`16F}aI#XzG8vDa~-=5?c^^ z^>{aw5NJU#hL;-*L&oo;(uDLLqFGO|#VvJlN7a?nj%h_@nbV*0Fet~hFpbo2T!`?c z@9*tBWj`?!kQ^F5m1!^7=lK7vatpQm7S7 zJGsPU=azOD7ZUAjC5;Up?|nC6MAS~ehTcJAsW_%FIy_L5S9`F|+0q%64V=C1E%}cJ zVkQVHD_q-l`q*LoxOJ@UJaZ6QXB(;@YLC&I-#IKTjA=y{6@D)~BGiF0@eqN*0QjLa zJ0U>1d$Rf&OP+=0;=7=+3@{cUc-k{hFE7R0x6v-(-R@%LTlOEyl=-QCuanwgsO)k= z$4rQqweYKfnfwZVp5MJOwMamo@Y#3?rZe?Wz=J?7nEwdO&j(Fm;{1FY^a-yH;-8&` zg6bP|hF=#!5FD?t?mPfTtM?uz{s9x8hl#fr1|p<8xm&>qZ%Z-ihUifLNA*$ap|Dc^ z;1IY&hF~1+$>a_;Rxb2upfCET*n{RFrd4O|=g(J9FCl@6YSf3jW{xICM5q$8GLxaR z|5f9F{GSoZ^3Mipo<05?rQ_^5^*Ic>(`aJ_Em99`L6#hZ&seh^q@FgUHxPF==SMv0 z&{n;c8Bmf(LU!Y4pdHYov7C71;JnprNDmQ0rQBklG}>|V{YxcXr$OA3SQHpG_tIwe zfoDszIoiQ7ILE|!y~eUF+D4K3tz--L+rJq(uP16`x)A)`ii^K$G+qV3X=nOTRC2#b z*k=NshBn?06CvUq4hgVJxRCgqm;)jo?eftBn$=Wjb6EcTv~~AeHFNJDs}CoPmZ25K zv3T=$C;nvn2f7HID6y}djT=#d1#^jbLCKMd$L~=ChVqt=m5Ende&n)hI9_NmIXM}E zn;6Iy_%8kTqk%g$E~8BiSqFH~CO7rrW`E_A29#V6u2amB$SYsC(g;`XgAAtVEv@`X z6hCmNnP1qD=ukE2n+dthM>U~eW8fReu4nq z>NjwX{HT{(qNP0}TvX0+;A( zWo;uPREW^-jE*GbD04-Lf&DWucUcMShAf}d(bw<&_U$By0NUOEGonQ$M_KLF+OvFj z-Xv+03g-tUjffV=z?@C&T?pJC=47=ek1z|q0%-=Q38uKpM43pejQWxF&emBzo>!TG(vI!EPeD~L;|e&evjR}!D!=(6R+ zq#+A8qyd8=IWQCQS~vWR?I^w$WN+lAHlb?4Y%n)*AX+lNuw%+9-#gn?F?J1-%uEs#JVVQTh9_d98t!`WCmyhsWrBZAd2$ zv4-wBEQRa4l~qnM_+8DOdsOgqIWUDc*u3x#h#HG5+kh|b?d}HE7j-?PPgW)--p2;% zT>fr^QK?5~ntY5Ep*fx)hx3u)NGYMG8h3>a=YX?Hkk&9582ZzxzX0TQr|UBE^BsxZ z>2rsC`IUI7CLd4^c8)Uy>lqrBuk_E&&tJreBdj1vc%%4JA;RLgNA`}?y~bd`Hr0S; zZv`6Y{^6fp42JxWNsAQ&q2Xs{PG z?6qFrrZ#H^I?~xB{8X=YsQf1l$21fjT<2u=we0|#nam7BV#7ctKbZ!<05Cx&-@z{p z@XKs&c7XyHm5BeqKUBUhy090R{(`H3I%3tR$It3*J#9y4a}}lcEzF*qw>c`&9dLc) zd@yZ&`{V#O(wvrE@ZN3q=k2xAMZb}ZZ!ru%c?xioGxNpe{XCdu0H_pa-ZS*R7f{dW zK}Vhh>p>FZZ&SauY2ijRZg5d)ft(Y0&lBDV#8MwT;c^^py^R|?-6T;D)zs<12zzuy zuPgiEkg?5tj4d}37_3HpF3sNJV=7)6GyLVtM`+BV@TBEp1ac^gD0B9#uu+*H#FBFm zX(fe=$2`TNLF#(oMI?@pr1m>4j3@T?_s^zu4sGUyyNYH%;LFv<40~Ln_x%Cs&82T} zunIV|@sjM>?|%LLF-MthN*;fq)8k>zZws=+V;a;5O76LPulRG;{7en<>nVCycc6e$ zQ&i6NPZ-~;*18iEE)&3^|2q=M(2=J@4v+!doYT3Z67en%_Y3!rX*7|7GJ6^x>+0%0 zk3i(1LKM+}?k^PV)qqk@y#BPRI=q}Fe3FO5b)P~2M0Z&-zP31L1$1MMdXQjw8~zpm*HxuJ*&X$F@%!N(Db1KH~l zV5OO=mmSY2r|W2kBFI+=b)W1&{bOs}!XCrORI%(Bs2XE2MA6ztGChP z04ySM+93B%n9^B%lt6Sx=UAaOzxv(xOVz5p5nnrLey7xkcd!xBMvkuup>i>itf%!< zbPY1N|NQQ}S68if$&!l>2?X67Z_CEL7TD z2wvMui$vn;BlI; z2hiNz*xnxcOy!G$G5#BHNw!#1kgpafVf1Jsz@~Ix=)}h#v?@Kwb`4e8f*8UQutq&M zocPJq$c(pYHEB+ruY=F}Ks&MkUeUsW`=2nxEcHJcv*$HH1ELZZ{}c`4TDE^%K4_p6 zrS<<*YDDm$RZvd|5A1TV3(l9Olz40Td5UKg`woVptcxc z98{)8CMFzTwGff>UQWN^V%i`KDW}s$#|g3EPgBl`r}W1P2FmLNPz=OINRk zPT+7UMn{mDEh~RHvE2UcJIlaMi9gW~-vAQNas=x|>r}df8B0DGcu-~y)iTgr_p6^e z+&n=ge;#V5GfJ5~fQfV`X7#0#Mxm9uwK(JjMMCY>`}$*I_zH8|Z%5rvojR5B>(j;7 z*4EUd!$>Ex8@(MM?->pWeKVA4&mvSvgyv;OHHOm4ukIN zjqZi83g~NVgrvKRU%~We{NPWwALisi+OLxBX(WYj2#&b+s5dm-=Lf;rO$io6#7C6= zEdt__X&}NW``d>4`qTNJisPCUs?mkBa>mhWiGYZYgi1UT4KSR_Q&yukI8_ zS;ZEGJp)kaOC6z%yokZS)$_8yEzD;rI!$v?d_$G{__0oE0zaV44w+I-39?}`+c}2^|_pUR<->{i9wIUpV2`HFi3tP_$=dXNoA_2G+mc1hoc&od8kCz zLHkG_Nug3HU3604X3`3@?mp_^61FqegGnF zi*$2?+mQ1U_p;@w-+up8+~bY8=WF`__$9QX0TR8W43##ff(r2+ot@1!ZT0>zT}Oja z$hsh0IrKm9B$gY1?o6o9#(RDp6viI z`4Hq6nlh_5AE;)frKJ@W6=i47O;OtA@;-dv8e-@Q{DMx%vO^_gE?TeJN&` x0%4tUs`X;D3`4j4&A&hX-GhJa;QtagP|IN((6%cg#G`Jid`shI{$FN({|CXwHxmE= literal 0 HcmV?d00001 diff --git a/tests/testdata/layouts/atlas_legend_clipping.qgs b/tests/testdata/layouts/atlas_legend_clipping.qgs new file mode 100644 index 000000000000..f4c204344181 --- /dev/null +++ b/tests/testdata/layouts/atlas_legend_clipping.qgs @@ -0,0 +1,1428 @@ + + + + + + + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + + + + + + + + + + + polys_c551ad5f_e1cd_414e_8889_0bfaf7f2f023 + + + + + + + + + + + degrees + + -149.54156478309073464 + 9.20011247797592802 + -37.76716404725016929 + 55.85854863535562487 + + 0 + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + 0 + + + + + + + + + + + Annotations_fb2bafb6_1139_492d_90a4_725919aaaa6d + + + + + Annotations + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + + 1 + 1 + 1 + 0 + + + + 1 + 0 + + + + + + -118.92286230599032137 + 24.50786971868489061 + -83.79001199101509201 + 46.72617265077044379 + + + -118.92286230599032137 + 24.50786971868489061 + -83.79001199101493569 + 46.72617265077047932 + + polys_c551ad5f_e1cd_414e_8889_0bfaf7f2f023 + ../polys.shp + + + + polys + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + + + + + dataset + + + + + + + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 0 + generatedlayout + + + + + + "Name" + + + + + + + + + 0 + + + 255 + 255 + 255 + 255 + 0 + 255 + 255 + + + false + + + PARAMETER:6378137:6356752.31424517929553986 + + + m2 + meters + + + 5 + 2.5 + false + false + false + 1 + 0 + false + false + true + 0 + 255,0,0,255 + + + false + + + true + 2 + + + 1 + + + + + + + + + + + PROJCRS["NAD27 / UTM zone 11N",BASEGEOGCRS["NAD27",DATUM["North American Datum 1927",ELLIPSOID["Clarke 1866",6378206.4,294.978698213898,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4267]],CONVERSION["UTM zone 11N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",-117,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["North America - between 120°W and 114°W - onshore. Canada - Alberta; British Columbia; Northwest Territories; Nunavut. Mexico. United States (USA) - California; Idaho; Nevada; Oregon; Washington."],BBOX[26.93,-120,78.13,-114]],ID["EPSG",26711]] + +proj=utm +zone=11 +datum=NAD27 +units=m +no_defs + 2140 + 26711 + EPSG:26711 + NAD27 / UTM zone 11N + utm + EPSG:7008 + false + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + PROJCRS["Amersfoort / RD New",BASEGEOGCRS["Amersfoort",DATUM["Amersfoort",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4289]],CONVERSION["RD New",METHOD["Oblique Stereographic",ID["EPSG",9809]],PARAMETER["Latitude of natural origin",52.1561605555556,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",5.38763888888889,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9999079,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",155000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",463000,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Netherlands - onshore, including Waddenzee, Dutch Wadden Islands and 12-mile offshore coastal zone."],BBOX[50.75,3.2,53.7,7.22]],ID["EPSG",28992]] + +proj=sterea +lat_0=52.1561605555556 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +units=m +no_defs + 2517 + 28992 + EPSG:28992 + Amersfoort / RD New + sterea + EPSG:7004 + false + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + PROJCRS["WGS 84 / UTM zone 48N",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Between 102°E and 108°E, northern hemisphere between equator and 84°N, onshore and offshore. Cambodia. China. Indonesia. Laos. Malaysia - West Malaysia. Mongolia. Russian Federation. Singapore. Thailand. Vietnam."],BBOX[0,102,84,108]],ID["EPSG",32648]] + +proj=utm +zone=48 +datum=WGS84 +units=m +no_defs + 3132 + 32648 + EPSG:32648 + WGS 84 / UTM zone 48N + utm + EPSG:7030 + false + + + + + PROJCRS["Indian 1960 / UTM zone 48N",BASEGEOGCRS["Indian 1960",DATUM["Indian 1960",ELLIPSOID["Everest 1830 (1937 Adjustment)",6377276.345,300.8017,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4131]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Cambodia; Vietnam west of 108°E."],BBOX[8.33,102.14,23.4,108]],ID["EPSG",3148]] + +proj=utm +zone=48 +ellps=evrst30 +units=m +no_defs + 1108 + 3148 + EPSG:3148 + Indian 1960 / UTM zone 48N + utm + EPSG:7015 + false + + + + + + + PROJCRS["WGS 84 / Pseudo-Mercator",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06°S and 85.06°N."],BBOX[-85.06,-180,85.06,180]],ID["EPSG",3857]] + +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs + 3857 + 3857 + EPSG:3857 + WGS 84 / Pseudo-Mercator + merc + EPSG:7030 + false + + + + + PROJCRS["Indian 1960 / UTM zone 48N",BASEGEOGCRS["Indian 1960",DATUM["Indian 1960",ELLIPSOID["Everest 1830 (1937 Adjustment)",6377276.345,300.8017,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4131]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Cambodia; Vietnam west of 108°E."],BBOX[8.33,102.14,23.4,108]],ID["EPSG",3148]] + +proj=utm +zone=48 +ellps=evrst30 +units=m +no_defs + 1108 + 3148 + EPSG:3148 + Indian 1960 / UTM zone 48N + utm + EPSG:7015 + false + + + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + PROJCRS["Indian 1960 / UTM zone 48N",BASEGEOGCRS["Indian 1960",DATUM["Indian 1960",ELLIPSOID["Everest 1830 (1937 Adjustment)",6377276.345,300.8017,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4131]],CONVERSION["UTM zone 48N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",105,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Cambodia; Vietnam west of 108°E."],BBOX[8.33,102.14,23.4,108]],ID["EPSG",3148]] + +proj=utm +zone=48 +ellps=evrst30 +units=m +no_defs + 1108 + 3148 + EPSG:3148 + Indian 1960 / UTM zone 48N + utm + EPSG:7015 + false + + + + + + + + + + + + + + + + Alessandro Pasotti + 2023-09-22T10:32:48 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + polys_c551ad5f_e1cd_414e_8889_0bfaf7f2f023 + + + + + + + + + + + + + + + + + + GEOGCRS["WGS 84",DATUM["unknown",ELLIPSOID["WGS84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]]] + +proj=longlat +ellps=WGS84 +no_defs +type=crs + 0 + 0 + + WGS 84 + + PARAMETER:6378137:6356752.31424517929553986 + false + + + + + + + + + + + + + + + + + + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + + From 8a23c9ad5efa622cd9f4507d135c8d9f63f15ddc Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Sep 2023 19:21:44 +0200 Subject: [PATCH 057/151] qgs3dmapscene: Fix near far computation failed check The update failed check assumes that the initial fnear and ffar values have not changed. However, if the fnear/ffar swap occurs before this check, this condition can never be true. --- src/3d/qgs3dmapscene.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 8ad686f25f50..a5c9ab28e764 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -404,10 +404,6 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() if ( fnear < 1 ) fnear = 1; // does not really make sense to use negative far plane (behind camera) - // when zooming in a lot, fnear can become smaller than ffar. This should not happen - if ( fnear > ffar ) - std::swap( fnear, ffar ); - if ( fnear == 1e9 && ffar == 0 ) { // the update didn't work out... this should not happen @@ -417,6 +413,10 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() ffar = 1e9; } + // when zooming in a lot, fnear can become smaller than ffar. This should not happen + if ( fnear > ffar ) + std::swap( fnear, ffar ); + // set near/far plane - with some tolerance in front/behind expected near/far planes float newFar = ffar * 2; float newNear = fnear / 2; From 28df05420db62696080a71536cc6b4eb932e38d6 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Sep 2023 20:24:50 +0200 Subject: [PATCH 058/151] qgs3dutils: Factor out function to compute near/far planes of a bbox --- src/3d/chunks/qgschunkedentity_p.cpp | 17 +++++------------ src/3d/qgs3dutils.cpp | 19 +++++++++++++++++++ src/3d/qgs3dutils.h | 14 ++++++++++++++ src/3d/qgsvirtualpointcloudentity_p.cpp | 17 +++++------------ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 5ff5043fe7cb..3e4f770a3382 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -286,18 +286,11 @@ QgsRange QgsChunkedEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMa // project each corner of bbox to camera coordinates // and determine closest and farthest point. QgsAABB bbox = node->bbox(); - for ( int i = 0; i < 8; ++i ) - { - const QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax, - ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax, - ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 ); - - const QVector4D pc = viewMatrix * p; - - const float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back - fnear = std::min( fnear, dst ); - ffar = std::max( ffar, dst ); - } + float bboxfnear; + float bboxffar; + Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar ); + fnear = std::min( fnear, bboxfnear ); + ffar = std::max( ffar, bboxffar ); } return QgsRange( fnear, ffar ); } diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 6cdd41e14518..522a7d8f0f79 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -846,3 +846,22 @@ float Qgs3DUtils::screenSpaceError( float epsilon, float distance, float screenS float phi = epsilon * screenSize / ( 2 * distance * tan( fov * M_PI / ( 2 * 180 ) ) ); return phi; } + +void Qgs3DUtils::computeBoundingBoxNearFarPlanes( const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar ) +{ + fnear = 1e9; + ffar = 0; + + for ( int i = 0; i < 8; ++i ) + { + const QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax, + ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax, + ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 ); + + const QVector4D pc = viewMatrix * p; + + const float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back + fnear = std::min( fnear, dst ); + ffar = std::max( ffar, dst ); + } +} diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index 9b0d68e35645..a492bbc4c7ae 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -274,6 +274,20 @@ class _3D_EXPORT Qgs3DUtils * \since QGIS 3.32 */ static float screenSpaceError( float epsilon, float distance, float screenSize, float fov ); + + /** + * This routine computes \a nearPlane \a farPlane from the closest and farthest corners point + * of bounding box \a bbox. + * In case of error, fnear will equal 1e9 and ffar 0. + * + * \param bbox in world coordinates + * \param viewMatrix camera view matrix + * \param fnear near plane + * \param ffar far plane + * + * \since QGIS 3.34 + */ + static void computeBoundingBoxNearFarPlanes( const QgsAABB &bbox, const QMatrix4x4 &viewMatrix, float &fnear, float &ffar ); }; #endif // QGS3DUTILS_H diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index 789e5a78324b..a7bebcdafbf7 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -146,18 +146,11 @@ QgsRange QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4 { for ( const QgsAABB &bbox : mBboxes ) { - for ( int i = 0; i < 8; ++i ) - { - const QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax, - ( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax, - ( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 ); - - const QVector4D pc = viewMatrix * p; - - const float dst = -pc.z(); // in camera coordinates, x grows right, y grows down, z grows to the back - fnear = std::min( fnear, dst ); - ffar = std::max( ffar, dst ); - } + float bboxfnear; + float bboxffar; + Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar ); + fnear = std::min( fnear, bboxfnear ); + ffar = std::max( ffar, bboxffar ); } } From 6b371430693113503fe90d1a68245e1f9c49352c Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Sep 2023 20:53:29 +0200 Subject: [PATCH 059/151] qgs3dmapscene: Use scene extent as a fallback for near/far plane computation This can happen if the scene does not contain any Qgs3DMapSceneEntity. For example, if the scene only containes meshes. --- src/3d/qgs3dmapscene.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index a5c9ab28e764..573abed36d3b 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -404,13 +404,15 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() if ( fnear < 1 ) fnear = 1; // does not really make sense to use negative far plane (behind camera) + // the update didn't work out... this can happen if the scene does not contain + // any Qgs3DMapSceneEntity. Use the scene extent to compute near and far planes + // as a fallback. if ( fnear == 1e9 && ffar == 0 ) { - // the update didn't work out... this should not happen - // well at least temporarily use some conservative starting values - qWarning() << "oops... this should not happen! couldn't determine near/far plane. defaulting to 1...1e9"; - fnear = 1; - ffar = 1e9; + QgsDoubleRange sceneYRange = elevationRange(); + sceneYRange = sceneYRange.isInfinite() ? QgsDoubleRange( 0.0, 0.0 ) : sceneYRange; + const QgsAABB sceneBbox = Qgs3DUtils::mapToWorldExtent( mMap.extent(), sceneYRange.lower(), sceneYRange.upper(), mMap.origin() ); + Qgs3DUtils::computeBoundingBoxNearFarPlanes( sceneBbox, viewMatrix, fnear, ffar ); } // when zooming in a lot, fnear can become smaller than ffar. This should not happen From f80784e0ddeba22a081126721e8836c5ab7f04b2 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 22 Sep 2023 15:18:10 +0200 Subject: [PATCH 060/151] Layout legend wdgt: bump max value for spinboxes Fix #54682 --- src/ui/layout/qgslayoutlegendwidgetbase.ui | 48 ++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/ui/layout/qgslayoutlegendwidgetbase.ui b/src/ui/layout/qgslayoutlegendwidgetbase.ui index afae894fff74..717e898d73ce 100644 --- a/src/ui/layout/qgslayoutlegendwidgetbase.ui +++ b/src/ui/layout/qgslayoutlegendwidgetbase.ui @@ -63,9 +63,9 @@ 0 - 0 - 359 - 2256 + -2299 + 517 + 3237 @@ -1000,6 +1000,9 @@ mm + + 9999.989999999999782 + @@ -1010,6 +1013,9 @@ mm + + 9999.989999999999782 + @@ -1047,6 +1053,9 @@ mm + + 9999.989999999999782 + @@ -1064,6 +1073,9 @@ mm + + 9999.989999999999782 + @@ -1084,6 +1096,9 @@ mm + + 9999.989999999999782 + @@ -1104,6 +1119,9 @@ mm + + 9999.989999999999782 + @@ -1114,6 +1132,9 @@ mm + + 9999.989999999999782 + @@ -1131,6 +1152,9 @@ mm + + 9999.989999999999782 + @@ -1148,6 +1172,9 @@ mm + + 9999.989999999999782 + @@ -1168,6 +1195,9 @@ mm + + 9999.989999999999782 + @@ -1198,6 +1228,9 @@ mm + + 9999.989999999999782 + @@ -1225,6 +1258,9 @@ mm + + 9999.989999999999782 + @@ -1235,6 +1271,9 @@ mm + + 9999.989999999999782 + @@ -1296,6 +1335,9 @@ mm + + 9999.989999999999782 + From 3c6298f292f61f8327c09cea4297d49d25f23651 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 22 Sep 2023 16:34:01 +0200 Subject: [PATCH 061/151] Fix read of vector settings for mesh layers --- src/core/mesh/qgsmeshlayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index e9fb0f04dd35..c2ac29220a16 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -1390,7 +1390,7 @@ QgsMeshRendererSettings QgsMeshLayer::accordSymbologyWithGroupName( const QgsMes QString activeVectorName; QgsMeshRendererSettings consistentSettings = settings; int activeScalar = consistentSettings.activeScalarDatasetGroup(); - int activeVector = consistentSettings.activeScalarDatasetGroup(); + int activeVector = consistentSettings.activeVectorDatasetGroup(); for ( auto it = nameToIndex.constBegin(); it != nameToIndex.constEnd(); ++it ) { From 0ce9b6377dd4e9637732462527051061f9d2a027 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 22 Sep 2023 14:07:46 +0200 Subject: [PATCH 062/151] Fix crash on WCS bad layers Fix #54702 --- src/core/raster/qgsrasterinterface.cpp | 3 ++- src/providers/wcs/qgswcsprovider.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/raster/qgsrasterinterface.cpp b/src/core/raster/qgsrasterinterface.cpp index e83a25a07ae6..05852ac6c8a1 100644 --- a/src/core/raster/qgsrasterinterface.cpp +++ b/src/core/raster/qgsrasterinterface.cpp @@ -622,7 +622,8 @@ QString QgsRasterInterface::generateBandName( int bandNumber ) const if ( mInput ) return mInput->generateBandName( bandNumber ); - return tr( "Band" ) + QStringLiteral( " %1" ) .arg( bandNumber, 1 + static_cast< int >( std::log10( static_cast< double >( bandCount() ) ) ), 10, QChar( '0' ) ); + // For bad layers bandCount is 0, no log! + return tr( "Band" ) + QStringLiteral( " %1" ) .arg( bandNumber, 1 + ( bandCount() > 0 ? static_cast< int >( std::log10( static_cast< double >( bandCount() ) ) ) : 0 ), 10, QChar( '0' ) ); } QString QgsRasterInterface::colorInterpretationName( int bandNo ) const diff --git a/src/providers/wcs/qgswcsprovider.cpp b/src/providers/wcs/qgswcsprovider.cpp index 1513dc54e131..dc74f0687c01 100644 --- a/src/providers/wcs/qgswcsprovider.cpp +++ b/src/providers/wcs/qgswcsprovider.cpp @@ -957,6 +957,10 @@ QList QgsWcsProvider::colorTable( int bandNum Qgis::RasterColorInterpretation QgsWcsProvider::colorInterpretation( int bandNo ) const { + if ( !mCachedGdalDataset.get() ) + { + return Qgis::RasterColorInterpretation::Undefined; + } GDALRasterBandH myGdalBand = GDALGetRasterBand( mCachedGdalDataset.get(), bandNo ); return colorInterpretationFromGdal( GDALGetRasterColorInterpretation( myGdalBand ) ); } From 42c9d5a06f046edfe8a50e29d1c4242366e73a08 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 22 Sep 2023 14:15:51 +0200 Subject: [PATCH 063/151] Test for isse #54702 --- tests/src/python/test_provider_gdal.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/src/python/test_provider_gdal.py b/tests/src/python/test_provider_gdal.py index 14a11fa95000..ce56df4df6bf 100644 --- a/tests/src/python/test_provider_gdal.py +++ b/tests/src/python/test_provider_gdal.py @@ -345,6 +345,16 @@ def testBandDescription(self): rl = QgsRasterLayer(tmpfile) self.assertEqual(rl.dataProvider().bandDescription(1), 'my metadata description') + def testDisplayBandNameBadLayer(self): + """Test crash from issue GH #54702""" + + rl = QgsRasterLayer("https://FAKESERVER/ImageServer/WCSServer", "BadWCS", "wcs") + self.assertFalse(rl.isValid()) + # This was triggering a std::bad_alloc exception: + self.assertEqual(rl.dataProvider().displayBandName(1), 'Band 1') + # This was triggering another crash: + self.assertEqual(rl.dataProvider().colorInterpretationName(1), 'Undefined') + if __name__ == '__main__': unittest.main() From 9fc2c3d6a29b05b38e57091cd2a64ccb61739cab Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Fri, 22 Sep 2023 05:24:03 +0000 Subject: [PATCH 064/151] There is no feature display name for raster --- src/ui/qgsrasterlayerpropertiesbase.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/qgsrasterlayerpropertiesbase.ui b/src/ui/qgsrasterlayerpropertiesbase.ui index 374924a4e010..99dbb47ad576 100644 --- a/src/ui/qgsrasterlayerpropertiesbase.ui +++ b/src/ui/qgsrasterlayerpropertiesbase.ui @@ -1522,7 +1522,7 @@ li.checked::marker { content: "\2612"; } - The HTML map tips are shown when moving mouse over features of the currently selected layer when the 'Show Map Tips' action is toggled on. If no HTML code is set, the feature display name is used. + The HTML map tips are shown when moving mouse over features of the currently selected layer when the 'Show Map Tips' action is toggled on. true From a3ca16e3283a6dfffc3cf180562ece900f647f56 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 19 Sep 2023 16:59:54 +0300 Subject: [PATCH 065/151] [processing] replace illegal characters with underscore when generating output filename in Split Vector Layer algorithm (fix #53856) Also ignore invalid geometries, as we only want to split vector layer --- src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp b/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp index ab11f535ba91..47e29130d1dd 100644 --- a/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp +++ b/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp @@ -81,7 +81,7 @@ void QgsSplitVectorLayerAlgorithm::initAlgorithm( const QVariantMap & ) QVariantMap QgsSplitVectorLayerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); if ( !source ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); @@ -139,13 +139,15 @@ QVariantMap QgsSplitVectorLayerAlgorithm::processAlgorithm( const QVariantMap &p } else { - fileName = QStringLiteral( "%1%2.%3" ).arg( baseName ).arg( ( *it ).toString() ).arg( outputFormat ); + QString value = ( *it ).toString(); + value.replace( QRegularExpression( "<|>|\\:|\\\\|\\/|\\||\\?|\\*|\"" ), QStringLiteral( "_" ) ); + fileName = QStringLiteral( "%1%2.%3" ).arg( baseName ).arg( value ).arg( outputFormat ); } feedback->pushInfo( QObject::tr( "Creating layer: %1" ).arg( fileName ) ); sink.reset( QgsProcessingUtils::createFeatureSink( fileName, context, fields, geometryType, crs ) ); const QString expr = QgsExpression::createFieldEqualityExpression( fieldName, *it ); - QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setFilterExpression( expr ) ); + QgsFeatureIterator features = source->getFeatures( QgsFeatureRequest().setFilterExpression( expr ), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks ); count = 0; while ( features.nextFeature( feat ) ) { From 6daf48e231a652e4e81e9e3a5e22473175d25b7d Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 22 Sep 2023 10:32:31 +0300 Subject: [PATCH 066/151] use stringToSafeFilename() --- src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp b/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp index 47e29130d1dd..e46d5ba54117 100644 --- a/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp +++ b/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp @@ -18,6 +18,7 @@ #include "qgsalgorithmsplitvectorlayer.h" #include "qgsvectorfilewriter.h" #include "qgsvariantutils.h" +#include "qgsfileutils.h" ///@cond PRIVATE @@ -139,9 +140,7 @@ QVariantMap QgsSplitVectorLayerAlgorithm::processAlgorithm( const QVariantMap &p } else { - QString value = ( *it ).toString(); - value.replace( QRegularExpression( "<|>|\\:|\\\\|\\/|\\||\\?|\\*|\"" ), QStringLiteral( "_" ) ); - fileName = QStringLiteral( "%1%2.%3" ).arg( baseName ).arg( value ).arg( outputFormat ); + fileName = QStringLiteral( "%1%2.%3" ).arg( baseName ).arg( QgsFileUtils::stringToSafeFilename( ( *it ).toString() ) ).arg( outputFormat ); } feedback->pushInfo( QObject::tr( "Creating layer: %1" ).arg( fileName ) ); From 4c15213e246834be38eebad92865702f856d97b1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 17 Sep 2023 21:37:53 +0200 Subject: [PATCH 067/151] [OGR provider] GPKG/SQLite: issue a 'UPDATE ... SET col_name = constant' when updating all features (fixes #53043) On the test case provided in #53043: Before: ``` Start layer.getFeatures() without update layer.getFeatures() found 591904 features and took 0:00:06.561681 Start loop to update features Loop took 0:00:07.583642 Commitchanges took 0:00:33.213708 ``` After: ``` Start layer.getFeatures() without update layer.getFeatures() found 591904 features and took 0:00:06.572171 Start loop to update features Loop took 0:00:07.807320 Commitchanges took 0:00:03.322783 ``` So a 10x times improvement --- src/core/providers/ogr/qgsogrprovider.cpp | 86 +++++++++++++++++++++- tests/src/python/test_provider_ogr_gpkg.py | 81 ++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index d2cac79987c4..468c07f15212 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -2235,7 +2235,91 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ mayNeedResetReadingAfterGetFeature = false; } - for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it ) + /* Optimization to update a single field of all layer's feature with a + * constant value */ + bool useUpdate = false; + // If changing the below value, update it into test_provider_ogr_gpkg.py + // as well + constexpr size_t THRESHOLD_UPDATE_OPTIM = 100; + if ( static_cast( attr_map.size() ) >= THRESHOLD_UPDATE_OPTIM && + ( mGDALDriverName == QLatin1String( "GPKG" ) || + mGDALDriverName == QLatin1String( "SQLite" ) ) && + mOgrLayer->TestCapability( OLCFastFeatureCount ) && + attr_map.size() == mOgrLayer->GetFeatureCount() ) + { + std::set fids; + OGRFieldDefnH fd = nullptr; + int fieldIdx = -1; + QVariant val; + OGRFieldType type = OFTMaxType; + useUpdate = true; + for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it ) + { + QgsFeatureId fid = it.key(); + fids.insert( fid ); + const QgsAttributeMap &attr = it.value(); + if ( attr.size() != 1 ) + { + useUpdate = false; + break; + } + QgsAttributeMap::const_iterator it2 = attr.begin(); + if ( fieldIdx < 0 ) + { + fieldIdx = it2.key(); + if ( fieldIdx == 0 && mFirstFieldIsFid ) + { + useUpdate = false; + break; + } + fd = mOgrLayer->GetLayerDefn().GetFieldDefn( + ( mFirstFieldIsFid && fieldIdx > 0 ) ? fieldIdx - 1 : fieldIdx ); + if ( !fd ) + { + useUpdate = false; + break; + } + type = OGR_Fld_GetType( fd ); + if ( type != OFTInteger && type != OFTInteger64 && type != OFTString && type != OFTReal ) + { + useUpdate = false; + break; + } + val = *it2; + } + else if ( fieldIdx != it2.key() || val != *it2 ) + { + useUpdate = false; + break; + } + } + if ( useUpdate && fids.size() != static_cast( attr_map.size() ) ) + { + useUpdate = false; + } + if ( useUpdate ) + { + QString sql = QStringLiteral( "UPDATE %1 SET %2 = %3" ) + .arg( QString::fromUtf8( QgsOgrProviderUtils::quotedIdentifier( mOgrLayer->name(), mGDALDriverName ) ) ) + .arg( QString::fromUtf8( QgsOgrProviderUtils::quotedIdentifier( QByteArray( OGR_Fld_GetNameRef( fd ) ), mGDALDriverName ) ) ) + .arg( QgsOgrProviderUtils::quotedValue( val ) ); + QgsDebugMsgLevel( QStringLiteral( "Using optimized changeAttributeValues(): %1" ).arg( sql ), 3 ); + CPLErrorReset(); + mOgrOrigLayer->ExecuteSQLNoReturn( sql.toUtf8() ); + if ( CPLGetLastErrorType() != CE_None ) + { + useUpdate = false; + returnValue = false; + } + } + } + + // General case: let's iterate over all features and attributes to update + QgsChangedAttributesMap::const_iterator it = attr_map.begin(); + if ( useUpdate ) + it = attr_map.end(); + + for ( ; it != attr_map.end(); ++it ) { QgsFeatureId fid = it.key(); diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 090fe11d7dde..f3ae9da33dbf 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -2765,6 +2765,87 @@ def testTransactionModeAutoWithFilter(self): attrs = [f['name'] for f in vl2.getFeatures()] self.assertEqual(attrs, ['a', 'b']) + def testChangeAttributeValuesOptimization(self): + """Test issuing 'UPDATE layer SET column_name = constant' when possible""" + + # Below value comes from QgsOgrProvider::changeAttributeValues() + THRESHOLD_UPDATE_OPTIM = 100 + + tmpfile = os.path.join(self.basetestpath, 'testChangeAttributeValuesOptimization.gpkg') + ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile) + lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint) + lyr.CreateField(ogr.FieldDefn('str_field', ogr.OFTString)) + lyr.CreateField(ogr.FieldDefn('int_field', ogr.OFTInteger)) + lyr.CreateField(ogr.FieldDefn('int64_field', ogr.OFTInteger64)) + lyr.CreateField(ogr.FieldDefn('real_field', ogr.OFTReal)) + for i in range(THRESHOLD_UPDATE_OPTIM + 1): + f = ogr.Feature(lyr.GetLayerDefn()) + lyr.CreateFeature(f) + ds = None + + vl = QgsVectorLayer(f'{tmpfile}' + "|layername=" + "test", 'test', 'ogr') + + # Does not trigger the optim: not constant value + field_name, value, other_value = "str_field", "my_value", "other_value" + vl.startEditing() + fieldid = vl.fields().indexFromName(field_name) + for idx, feature in enumerate(vl.getFeatures()): + if idx == THRESHOLD_UPDATE_OPTIM: + vl.changeAttributeValue(feature.id(), fieldid, other_value) + else: + vl.changeAttributeValue(feature.id(), fieldid, value) + vl.commitChanges() + + got = [feat[field_name] for feat in vl.getFeatures()] + self.assertEqual(set(got), set([value, other_value])) + + # Does not trigger the optim: update of different fields + vl.startEditing() + fieldid = vl.fields().indexFromName("int_field") + fieldid2 = vl.fields().indexFromName("int64_field") + for idx, feature in enumerate(vl.getFeatures()): + if idx == THRESHOLD_UPDATE_OPTIM: + vl.changeAttributeValue(feature.id(), fieldid2, 1) + else: + vl.changeAttributeValue(feature.id(), fieldid, 1) + vl.commitChanges() + + got = [feat["int_field"] for feat in vl.getFeatures()] + self.assertEqual(set(got), set([1, QVariant()])) + got = [feat["int64_field"] for feat in vl.getFeatures()] + self.assertEqual(set(got), set([1, QVariant()])) + + # Does not trigger the optim: not all features updated + vl.startEditing() + fieldid = vl.fields().indexFromName("real_field") + for idx, feature in enumerate(vl.getFeatures()): + if idx == THRESHOLD_UPDATE_OPTIM: + break + vl.changeAttributeValue(feature.id(), fieldid, 1.5) + vl.commitChanges() + + got = [feat["real_field"] for feat in vl.getFeatures()] + self.assertEqual(set(got), set([1.5, QVariant()])) + + # Triggers the optim + for field_name, value in [("str_field", "my_value"), + ("int_field", 123), + ("int64_field", 1234567890123), + ("real_field", 2.5), + ("real_field", None), + ]: + vl.startEditing() + fieldid = vl.fields().indexFromName(field_name) + for feature in vl.getFeatures(): + vl.changeAttributeValue(feature.id(), fieldid, value) + vl.commitChanges() + + got = [feat[field_name] for feat in vl.getFeatures()] + if value: + self.assertEqual(set(got), set([value])) + else: + self.assertEqual(set(got), set([QVariant()])) + if __name__ == '__main__': unittest.main() From f4e553447472e650ef15d49bb9de68dd650a6368 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 17 Sep 2023 16:11:35 +0200 Subject: [PATCH 068/151] SQLite layer export: do not set SRID= layer creation options that is interpreted by the OGR SQLite driver as meaning SRID=0 (fixes #54560) --- src/core/qgsvectorfilewriter.cpp | 2 +- src/gui/ogr/qgsvectorlayersaveasdialog.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index 731a9ec7bac9..893b7308d8bf 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -4452,7 +4452,7 @@ QStringList QgsVectorFileWriter::concatenateOptions( const QMap( option ); - if ( opt ) + if ( opt && !opt->mValue.isEmpty() ) { list.append( QStringLiteral( "%1=%2" ).arg( it.key(), opt->mValue ) ); } diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index d1fdf91d67e1..352977b05948 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp @@ -960,7 +960,8 @@ QStringList QgsVectorLayerSaveAsDialog::datasourceOptions() const { QgsVectorFileWriter::HiddenOption *opt = dynamic_cast( it.value() ); - options << QStringLiteral( "%1=%2" ).arg( it.key(), opt->mValue ); + if ( !opt->mValue.isEmpty() ) + options << QStringLiteral( "%1=%2" ).arg( it.key(), opt->mValue ); break; } } @@ -1019,7 +1020,8 @@ QStringList QgsVectorLayerSaveAsDialog::layerOptions() const { QgsVectorFileWriter::HiddenOption *opt = dynamic_cast( it.value() ); - options << QStringLiteral( "%1=%2" ).arg( it.key(), opt->mValue ); + if ( !opt->mValue.isEmpty() ) + options << QStringLiteral( "%1=%2" ).arg( it.key(), opt->mValue ); break; } } From b9f770210c03201a5d81324ac3b1523f57bc2bd8 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 7 Sep 2023 19:53:54 +0200 Subject: [PATCH 069/151] qgs3dmapscene: Handle mesh layers in elevationRange() This is a follow-up of https://github.com/qgis/QGIS/pull/51544 which only handled PointCloud layers. The switch logic, with no default clause, results in an explicit compilation warning. This brings attention to the cases which are not managed. --- src/3d/qgs3dmapscene.cpp | 37 ++++++++++++++++++++++++++++++++----- src/3d/qgs3dmapscene.h | 2 +- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 573abed36d3b..61e2ad62185e 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -1090,12 +1090,39 @@ QgsDoubleRange Qgs3DMapScene::elevationRange() const for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); it++ ) { QgsMapLayer *layer = it.key(); - if ( layer->type() == Qgis::LayerType::PointCloud ) + switch ( layer->type() ) { - QgsPointCloudLayer *pcl = qobject_cast< QgsPointCloudLayer *>( layer ); - QgsDoubleRange zRange = pcl->elevationProperties()->calculateZRange( pcl ); - yMin = std::min( yMin, zRange.lower() ); - yMax = std::max( yMax, zRange.upper() ); + case Qgis::LayerType::PointCloud: + { + QgsPointCloudLayer *pcl = qobject_cast< QgsPointCloudLayer *>( layer ); + QgsDoubleRange zRange = pcl->elevationProperties()->calculateZRange( pcl ); + yMin = std::min( yMin, zRange.lower() ); + yMax = std::max( yMax, zRange.upper() ); + break; + } + case Qgis::LayerType::Mesh: + { + QgsMeshLayer *meshLayer = qobject_cast< QgsMeshLayer *>( layer ); + QgsAbstract3DRenderer *renderer3D = meshLayer->renderer3D(); + if ( renderer3D ) + { + QgsMeshLayer3DRenderer *meshLayerRenderer = static_cast( renderer3D ); + const int verticalGroupDatasetIndex = meshLayerRenderer->symbol()->verticalDatasetGroupIndex(); + const QgsMeshDatasetGroupMetadata verticalGroupMetadata = meshLayer->datasetGroupMetadata( verticalGroupDatasetIndex ); + const double verticalScale = meshLayerRenderer->symbol()->verticalScale(); + yMin = std::min( yMin, verticalGroupMetadata.minimum() * verticalScale ); + yMax = std::max( yMax, verticalGroupMetadata.maximum() * verticalScale ); + } + break; + } + case Qgis::LayerType::Annotation: + case Qgis::LayerType::Group: + case Qgis::LayerType::Plugin: + case Qgis::LayerType::Raster: + case Qgis::LayerType::TiledScene: + case Qgis::LayerType::Vector: + case Qgis::LayerType::VectorTile: + break; } } const QgsDoubleRange yRange( std::min( yMin, std::numeric_limits::max() ), diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 1879f92c7dde..c9bf64d7d3b0 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -163,7 +163,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject /** * Returns the scene's elevation range - * \note Only terrain and point cloud layers are taken into account + * \note Only some layer types are considered by this method (eg terrain, point cloud and mesh layers) * * \since QGIS 3.30 */ From 84a55ed156d7872d911a1040122996743b2393cd Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 7 Sep 2023 17:40:28 +0200 Subject: [PATCH 070/151] qgsmesh3dentity: Remove empty line at the end of file --- src/3d/mesh/qgsmesh3dentity_p.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/3d/mesh/qgsmesh3dentity_p.cpp b/src/3d/mesh/qgsmesh3dentity_p.cpp index d617952d58aa..f77958729951 100644 --- a/src/3d/mesh/qgsmesh3dentity_p.cpp +++ b/src/3d/mesh/qgsmesh3dentity_p.cpp @@ -102,4 +102,3 @@ void QgsMesh3DTerrainTileEntity::applyMaterial() QgsMesh3DMaterial::ZValue ); addComponent( material ); } - From 4f345e74bb56048c4d0469db23ec5a7034788152 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 7 Sep 2023 18:00:51 +0200 Subject: [PATCH 071/151] qgsmesh3dgeometry: Fix typo in a variable name --- src/3d/mesh/qgsmesh3dgeometry_p.cpp | 4 ++-- src/3d/mesh/qgsmesh3dgeometry_p.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.cpp b/src/3d/mesh/qgsmesh3dgeometry_p.cpp index 68fcbaff3b80..61fe1b0c39df 100644 --- a/src/3d/mesh/qgsmesh3dgeometry_p.cpp +++ b/src/3d/mesh/qgsmesh3dgeometry_p.cpp @@ -319,9 +319,9 @@ void QgsMeshDataset3DGeometry::prepareData() QgsMeshTerrain3DGeometry::QgsMeshTerrain3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, - double verticalSacle, + double verticalScale, Qt3DCore::QNode *parent ) - : QgsMesh3DGeometry( triangularMesh, origin, verticalSacle, parent ) + : QgsMesh3DGeometry( triangularMesh, origin, verticalScale, parent ) { const int stride = ( 3 /*position*/ + diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.h b/src/3d/mesh/qgsmesh3dgeometry_p.h index 835dcb83f037..4c6015da082d 100644 --- a/src/3d/mesh/qgsmesh3dgeometry_p.h +++ b/src/3d/mesh/qgsmesh3dgeometry_p.h @@ -253,7 +253,7 @@ class QgsMeshTerrain3DGeometry: public QgsMesh3DGeometry //! Constructs a mesh layer geometry from triangular mesh. explicit QgsMeshTerrain3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, - double verticalSacle, + double verticalScale, QNode *parent ); }; From 4cf834499bceda58a9081e3ea44f04a6d6f61cb7 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Tue, 5 Sep 2023 17:25:08 +0200 Subject: [PATCH 072/151] qgsmesh3geometry: Limit geometry to 3d scenes' 2d extent This is a follow-up of https://github.com/qgis/QGIS/pull/51304 which did not handle mesh layers. This commit adds this support. The meshes displayed in a 3d view do not take into account the scene 2d extent. This issue is fixed by filtering the faces which are used to build the Qt3D geometry. By using `mesh.faceIndexesForRectangle( extent )`, it is possible to keep the faces inside `extent`. This list is then used to populate the `IndexAttribute` of the qt3d geometry. This way, only the faces inside the extent are displayed. --- src/3d/mesh/qgsmesh3dentity_p.cpp | 4 +- src/3d/mesh/qgsmesh3dgeometry_p.cpp | 52 ++++++++++-------- src/3d/mesh/qgsmesh3dgeometry_p.h | 8 +++ tests/src/3d/testqgs3drendering.cpp | 46 ++++++++++++++++ .../expected_mesh3d_filtered.png | Bin 0 -> 16236 bytes 5 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 tests/testdata/control_images/3d/expected_mesh3d_filtered/expected_mesh3d_filtered.png diff --git a/src/3d/mesh/qgsmesh3dentity_p.cpp b/src/3d/mesh/qgsmesh3dentity_p.cpp index f77958729951..e89134bdba69 100644 --- a/src/3d/mesh/qgsmesh3dentity_p.cpp +++ b/src/3d/mesh/qgsmesh3dentity_p.cpp @@ -54,7 +54,7 @@ void QgsMeshDataset3DEntity::buildGeometry() return; Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer; - mesh->setGeometry( new QgsMeshDataset3DGeometry( mTriangularMesh, layer(), mMapSettings.temporalRange(), mMapSettings.origin(), mSymbol.get(), mesh ) ); + mesh->setGeometry( new QgsMeshDataset3DGeometry( mTriangularMesh, layer(), mMapSettings.temporalRange(), mMapSettings.origin(), mMapSettings.extent(), mSymbol.get(), mesh ) ); addComponent( mesh ); } @@ -89,7 +89,7 @@ QgsMesh3DTerrainTileEntity::QgsMesh3DTerrainTileEntity( void QgsMesh3DTerrainTileEntity::buildGeometry() { Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer; - mesh->setGeometry( new QgsMeshTerrain3DGeometry( mTriangularMesh, mMapSettings.origin(), mSymbol.get()->verticalScale(), mesh ) ); + mesh->setGeometry( new QgsMeshTerrain3DGeometry( mTriangularMesh, mMapSettings.origin(), mMapSettings.extent(), mSymbol.get()->verticalScale(), mesh ) ); addComponent( mesh ); } diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.cpp b/src/3d/mesh/qgsmesh3dgeometry_p.cpp index 61fe1b0c39df..698b8afa5902 100644 --- a/src/3d/mesh/qgsmesh3dgeometry_p.cpp +++ b/src/3d/mesh/qgsmesh3dgeometry_p.cpp @@ -132,25 +132,25 @@ static QByteArray createDatasetVertexData( return bufferBytes; } -static QByteArray createIndexData( const QgsTriangularMesh &mesh ) +static QByteArray createIndexData( const QgsTriangularMesh &mesh, const QgsRectangle &extent ) { - const int faces = mesh.triangles().count(); - const quint32 indices = static_cast( 3 * faces ); + const QList facesInExtent = mesh.faceIndexesForRectangle( extent ); + const quint32 indices = static_cast( 3 * facesInExtent.count() ); Q_ASSERT( indices < std::numeric_limits::max() ); // count non void faces int nonVoidFaces = 0; - for ( int i = 0; i < faces; ++i ) - if ( !mesh.triangles().at( i ).isEmpty() ) + for ( const int nativeFaceIndex : facesInExtent ) + if ( !mesh.triangles().at( nativeFaceIndex ).isEmpty() ) nonVoidFaces++; QByteArray indexBytes; indexBytes.resize( int( nonVoidFaces * 3 * sizeof( quint32 ) ) ); quint32 *indexPtr = reinterpret_cast( indexBytes.data() ); - for ( int i = 0; i < faces; ++i ) + for ( const int nativeFaceIndex : facesInExtent ) { - const QgsMeshFace &face = mesh.triangles().at( i ); + const QgsMeshFace &face = mesh.triangles().at( nativeFaceIndex ); if ( face.isEmpty() ) continue; for ( int j = 0; j < 3; ++j ) @@ -160,36 +160,34 @@ static QByteArray createIndexData( const QgsTriangularMesh &mesh ) return indexBytes; } -static QByteArray createDatasetIndexData( const QgsTriangularMesh &mesh, const QgsMeshDataBlock &mActiveFaceFlagValues ) +static QByteArray createDatasetIndexData( const QgsTriangularMesh &mesh, const QgsMeshDataBlock &mActiveFaceFlagValues, const QgsRectangle &extent ) { + const QList facesInExtent = mesh.faceIndexesForRectangle( extent ); int activeFaceCount = 0; // First we need to know about the count of active faces if ( mActiveFaceFlagValues.active().isEmpty() ) - activeFaceCount = mesh.triangles().count(); + activeFaceCount = facesInExtent.count(); else { - for ( int i = 0; i < mesh.triangles().count(); ++i ) + for ( const int nativeFaceIndex : facesInExtent ) { - const int nativeIndex = mesh.trianglesToNativeFaces()[i]; - if ( mActiveFaceFlagValues.active( nativeIndex ) ) + if ( mActiveFaceFlagValues.active( nativeFaceIndex ) ) activeFaceCount++; } } - const int trianglesCount = mesh.triangles().count(); const quint32 indices = static_cast( 3 * activeFaceCount ); QByteArray indexBytes; indexBytes.resize( int( indices * sizeof( quint32 ) ) ); quint32 *indexPtr = reinterpret_cast( indexBytes.data() ); - for ( int i = 0; i < trianglesCount; ++i ) + for ( const int nativeFaceIndex : facesInExtent ) { - const int nativeFaceIndex = mesh.trianglesToNativeFaces()[i]; const bool isActive = mActiveFaceFlagValues.active().isEmpty() || mActiveFaceFlagValues.active( nativeFaceIndex ); if ( !isActive ) continue; - const QgsMeshFace &face = mesh.triangles().at( i ); + const QgsMeshFace &face = mesh.triangles().at( nativeFaceIndex ); for ( int j = 0; j < 3; ++j ) *indexPtr++ = quint32( face.at( j ) ); } @@ -199,6 +197,7 @@ static QByteArray createDatasetIndexData( const QgsTriangularMesh &mesh, const Q QgsMesh3DGeometry::QgsMesh3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, + const QgsRectangle &extent, double verticalScale, Qt3DCore::QNode *parent ) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -207,6 +206,7 @@ QgsMesh3DGeometry::QgsMesh3DGeometry( const QgsTriangularMesh &triangularMesh, : Qt3DCore::QGeometry( parent ) #endif , mOrigin( origin ) + , mExtent( extent ) , mVertScale( verticalScale ) , mTriangulaMesh( triangularMesh ) { @@ -219,9 +219,10 @@ QgsMeshDataset3DGeometry::QgsMeshDataset3DGeometry( QgsMeshLayer *layer, const QgsDateTimeRange &timeRange, const QgsVector3D &origin, + const QgsRectangle &extent, const QgsMesh3DSymbol *symbol, Qt3DCore::QNode *parent ) - : QgsMesh3DGeometry( triangularMesh, origin, symbol->verticalScale(), parent ) + : QgsMesh3DGeometry( triangularMesh, origin, extent, symbol->verticalScale(), parent ) , mIsVerticalMagnitudeRelative( symbol->isVerticalMagnitudeRelative() ) , mVerticalGroupDatasetIndex( symbol->verticalDatasetGroupIndex() ) , mTimeRange( timeRange ) @@ -310,7 +311,7 @@ void QgsMeshDataset3DGeometry::prepareData() data.activeFaceFlagValues = layer->areFacesActive( scalarDatasetIndex, 0, nativeMesh.faces.count() ); data.isVerticalMagnitudeRelative = mIsVerticalMagnitudeRelative; - mBuilder = new QgsMeshDataset3DGeometryBuilder( mTriangulaMesh, nativeMesh, mOrigin, mVertScale, data, this ); + mBuilder = new QgsMeshDataset3DGeometryBuilder( mTriangulaMesh, nativeMesh, mOrigin, mExtent, mVertScale, data, this ); connect( mBuilder, &QgsMeshDataset3DGeometryBuilder::dataIsReady, this, &QgsMeshDataset3DGeometry::getData ); mBuilder->start(); @@ -319,9 +320,10 @@ void QgsMeshDataset3DGeometry::prepareData() QgsMeshTerrain3DGeometry::QgsMeshTerrain3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, + const QgsRectangle &extent, double verticalScale, Qt3DCore::QNode *parent ) - : QgsMesh3DGeometry( triangularMesh, origin, verticalScale, parent ) + : QgsMesh3DGeometry( triangularMesh, origin, extent, verticalScale, parent ) { const int stride = ( 3 /*position*/ + @@ -331,7 +333,7 @@ QgsMeshTerrain3DGeometry::QgsMeshTerrain3DGeometry( const QgsTriangularMesh &tri prepareVerticesNormalAttribute( mVertexBuffer, stride, 3 ); prepareIndexesAttribute( mIndexBuffer ); - mBuilder = new QgsMesh3DGeometryBuilder( triangularMesh, origin, mVertScale, this ); + mBuilder = new QgsMesh3DGeometryBuilder( triangularMesh, origin, extent, mVertScale, this ); connect( mBuilder, &QgsMesh3DGeometryBuilder::dataIsReady, this, &QgsMeshTerrain3DGeometry::getData ); mBuilder->start(); } @@ -416,10 +418,11 @@ void QgsMesh3DGeometry::prepareIndexesAttribute( Qt3DQBuffer *buffer ) } -QgsMesh3DGeometryBuilder::QgsMesh3DGeometryBuilder( const QgsTriangularMesh &mesh, const QgsVector3D &origin, float vertScale, QObject *parent ): +QgsMesh3DGeometryBuilder::QgsMesh3DGeometryBuilder( const QgsTriangularMesh &mesh, const QgsVector3D &origin, const QgsRectangle &extent, float vertScale, QObject *parent ): QObject( parent ) , mMesh( mesh ) , mOrigin( origin ) + , mExtent( extent ) , mVertScale( vertScale ) {} @@ -434,7 +437,7 @@ void QgsMesh3DGeometryBuilder::start() mWatcherIndex = new QFutureWatcher( this ); connect( mWatcherIndex, &QFutureWatcher::finished, this, &QgsMesh3DGeometryBuilder::indexFinished ); - mFutureIndex = QtConcurrent::run( createIndexData, mMesh ); + mFutureIndex = QtConcurrent::run( createIndexData, mMesh, mExtent ); mWatcherIndex->setFuture( mFutureIndex ); } @@ -462,10 +465,11 @@ QgsMeshDataset3DGeometryBuilder::QgsMeshDataset3DGeometryBuilder ( const QgsTriangularMesh &mesh, const QgsMesh &nativeMesh, const QgsVector3D &origin, + const QgsRectangle &extent, float vertScale, const QgsMeshDataset3DGeometry::VertexData &vertexData, QObject *parent ): - QgsMesh3DGeometryBuilder( mesh, origin, vertScale, parent ) + QgsMesh3DGeometryBuilder( mesh, origin, extent, vertScale, parent ) , mNativeMesh( nativeMesh ) , mVertexData( vertexData ) {} @@ -482,6 +486,6 @@ void QgsMeshDataset3DGeometryBuilder::start() mWatcherIndex = new QFutureWatcher( this ); connect( mWatcherIndex, &QFutureWatcher::finished, this, &QgsMeshDataset3DGeometryBuilder::indexFinished ); - mFutureIndex = QtConcurrent::run( createDatasetIndexData, mMesh, mVertexData.activeFaceFlagValues ); + mFutureIndex = QtConcurrent::run( createDatasetIndexData, mMesh, mVertexData.activeFaceFlagValues, mExtent ); mWatcherIndex->setFuture( mFutureIndex ); } diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.h b/src/3d/mesh/qgsmesh3dgeometry_p.h index 4c6015da082d..2ebc8ada7aa1 100644 --- a/src/3d/mesh/qgsmesh3dgeometry_p.h +++ b/src/3d/mesh/qgsmesh3dgeometry_p.h @@ -32,6 +32,7 @@ #include "qgsmaplayerref.h" #include "qgsmesh3dsymbol.h" +#include "qgsrectangle.h" #include "qgstriangularmesh.h" ///@cond PRIVATE @@ -72,6 +73,7 @@ class QgsMesh3DGeometryBuilder: public QObject public: QgsMesh3DGeometryBuilder( const QgsTriangularMesh &mesh, const QgsVector3D &origin, + const QgsRectangle &extent, float vertScale, QObject *parent ); @@ -95,6 +97,7 @@ class QgsMesh3DGeometryBuilder: public QObject QgsTriangularMesh mMesh; QgsVector3D mOrigin; + QgsRectangle mExtent; float mVertScale; mutable QMutex mMutex; @@ -118,6 +121,7 @@ class QgsMesh3DGeometry: public Qt3DCore::QGeometry //! Constructor explicit QgsMesh3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, + const QgsRectangle &extent, double verticalScale, QNode *parent ); @@ -134,6 +138,7 @@ class QgsMesh3DGeometry: public Qt3DCore::QGeometry #endif QgsVector3D mOrigin; + QgsRectangle mExtent; float mVertScale; QgsTriangularMesh mTriangulaMesh; @@ -179,6 +184,7 @@ class QgsMeshDataset3DGeometry: public QgsMesh3DGeometry QgsMeshLayer *layer, const QgsDateTimeRange &timeRange, const QgsVector3D &origin, + const QgsRectangle &extent, const QgsMesh3DSymbol *symbol, QNode *parent ); @@ -230,6 +236,7 @@ class QgsMeshDataset3DGeometryBuilder: public QgsMesh3DGeometryBuilder QgsMeshDataset3DGeometryBuilder( const QgsTriangularMesh &mesh, const QgsMesh &nativeMesh, const QgsVector3D &origin, + const QgsRectangle &extent, float vertScale, const QgsMeshDataset3DGeometry::VertexData &vertexData, QObject *parent ); @@ -253,6 +260,7 @@ class QgsMeshTerrain3DGeometry: public QgsMesh3DGeometry //! Constructs a mesh layer geometry from triangular mesh. explicit QgsMeshTerrain3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, + const QgsRectangle &extent, double verticalScale, QNode *parent ); }; diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index fef670ea0b80..a74d62ec57e7 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -95,6 +95,7 @@ class TestQgs3DRendering : public QgsTest void testFilteredFlatTerrain(); void testFilteredDemTerrain(); void testFilteredExtrudedPolygons(); + void testFilteredMesh(); void testDepthBuffer(); void testAmbientOcclusion(); void testDebugMap(); @@ -1456,6 +1457,51 @@ void TestQgs3DRendering::testFilteredExtrudedPolygons() QVERIFY( imageCheck( "polygon3d_extrusion_filtered", "polygon3d_extrusion_filtered", img, QString(), 40, QSize( 0, 0 ), 2 ) ); } +void TestQgs3DRendering::testFilteredMesh() +{ + const QgsRectangle fullExtent = mLayerMeshDataset->extent(); + const QgsRectangle filteredExtent = QgsRectangle( fullExtent.xMinimum(), fullExtent.yMinimum(), + fullExtent.xMinimum() + fullExtent.width() / 3.0, fullExtent.yMinimum() + fullExtent.height() / 4.0 ); + + QgsMesh3DSymbol *symbolMesh3d = new QgsMesh3DSymbol; + symbolMesh3d->setVerticalDatasetGroupIndex( 0 ); + symbolMesh3d->setVerticalScale( 10 ); + symbolMesh3d->setRenderingStyle( QgsMesh3DSymbol::ColorRamp2DRendering ); + symbolMesh3d->setArrowsEnabled( true ); + symbolMesh3d->setArrowsSpacing( 300 ); + QgsMeshLayer3DRenderer *meshDatasetRenderer3d = new QgsMeshLayer3DRenderer( symbolMesh3d ); + mLayerMeshDataset->setRenderer3D( meshDatasetRenderer3d ); + + Qgs3DMapSettings *map = new Qgs3DMapSettings; + map->setCrs( mProject->crs() ); + map->setExtent( filteredExtent ); + map->setLayers( QList() << mLayerMeshDataset ); + QgsPointLightSettings defaultLight; + defaultLight.setIntensity( 0.5 ); + defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); + map->setLightSources( { defaultLight.clone() } ); + + QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; + flatTerrain->setCrs( map->crs() ); + map->setTerrainGenerator( flatTerrain ); + + QgsOffscreen3DEngine engine; + Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); + engine.setRootEntity( scene ); + scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 3000, 25, 45 ); + + // When running the test on Travis, it would initially return empty rendered image. + // Capturing the initial image and throwing it away fixes that. Hopefully we will + // find a better fix in the future. + Qgs3DUtils::captureSceneImage( engine, scene ); + + QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + delete scene; + delete map; + + QVERIFY( imageCheck( "mesh3d_filtered", "mesh3d_filtered", img, QString(), 40, QSize( 0, 0 ), 2 ) ); +} + void TestQgs3DRendering::testAmbientOcclusion() { // ============================================= diff --git a/tests/testdata/control_images/3d/expected_mesh3d_filtered/expected_mesh3d_filtered.png b/tests/testdata/control_images/3d/expected_mesh3d_filtered/expected_mesh3d_filtered.png new file mode 100644 index 0000000000000000000000000000000000000000..1722d09e4999d4526b06533bad4d46d8a87d3a24 GIT binary patch literal 16236 zcmeHuhc{b)*tjk_lS8at-WK%N>HmtsS#>K z>a_cxmcQN5|Cp-}g+SFcT*o-5okjRTV@33?lXrDG9Q~?`)y*D@xzq zjZR&?7-@SWQN;8|e-a(bUG_BA+qHFP7``=Hl4y%wAMTaEE4|A1;gjhz$JD|{wK*4m z*V5%{+P+snQp3GVT(-6E1_eMq-BeS>kF>6{v&Dl+0BEUC$>vdXM-Dlaru$zQu5T4w~o0a;su{&DoeUXBg)? zH_Ca?R=og=xWMa$^jxxM+1oBP*zS86?XHw+s)ijdI$Peup=numN}6*o zS=#7g%#+ZAU3d{V#+W4za-%~={Nh`>9v8^IN9$6iS=b-^8{;$D=ziF|`O%hASj~kP zCg5c$7F95RlU-d+1vk0aY#mLbwvrQT3eNYF6C>9H;EkRQw!ZEryD_|~>m=WGOMS&C z9Zl6!2U@0v*KAwd-S=m>8c~?KH>nWGSxTmH*QW}1sC-BQd-tRY-8q@(cJrLyTh>FE zz@1GtA|*009IaKfm=`jEa{~Y2+jpzfm8fG)vB9n0t8r%&|C!s3*lIm_8*+B;;(zx$ zXV+M+59FsA$TkzE4Yms00#W+hRM(>r@At}=J@^3X!Z#u@_ow#C?N2Hpi(hgdzjp7F ztIDK2z26Vgw9Vi+|LA>3tyoZghQhJ}{OM}M)n6tp+J7vOAB1I35>Jr6v=8rdp96S4 z4J_42;fJE4hPpb?gN$WAJ`o`q6EiQf28sPg5ckuc@9+Qj7#HxEmJCj)R7IMwj<)H@ zW+M8nubWQ0nw?&k_?qwxc%(=H)^TW@euNJ0{v2UNv06Ji8@tjA@vhTRnIJzZXQ{J`luI3xu* zQIPnW3y8tQ4YDuboYXQ}avFMH`YZOjlt`40-lYn|(&ZTQUms6RG6t8j!=$fx_yZj6 zwzf3r=LdnU( zZ22b=U4IO{Gh1B6Sy&Dk;@Qmsb*d$nS9q^aRC4fSL>SqK$UT-9ev2$LyC{Fq|8diL z@`2fjh{V^=00NSMrR#iR0azV7YDtD4*-YFR%!uT`pG)n2H<|HXdA-kcaHw@;Z!28- z^4|}1^w;`Eq|+ezi{5EHd5^2Dy69!i8e1$H1uQuYL`zB0mshDJ&4jH8o7!t_bU^^< z4Eg3@$(F-#YSIPHZ<)6${pY7Vn~{@0zYPUg!P*;3@pta+hJ5A=i>0IgK_pu7zgT3s z@9QAbOFF&m>ujdFWKG8TAu?S-UJmuWvjzLwPKYX*+S@&rQV$xG*M&qPgf(_R{j!+s zIO&V8@4Y{BlWg32CpdLo9KsqFv_7V!F(FOCQ9sI0@Q!ET57KI8vDQ33&Cs|Ox#w_6 zU5Z{pCp&OL90Nah+_loB!D*0~-j1tzfdhy~}AYrgy(D#R+a*^n-y#*+MuuxuerJu!Q_} zJdrSNd5x`9*M?ZZbk8eVK_y~7D*7^en;e|8alOEeqw~tJ$Kc)sMcUfUV=I$$*K^~y zAFg_sTxOY>bm+GKTNl}PF%jcpgS9xxYq9cZzt+~KhD08%K1~MOFveb)?7~;im4ey~ zNEO&XeAvQ0a!VALF-Ll`+dtq`YC(&3sx!Fu8asu8aY_Wm0e|kQLms`m-H{uJ#x$fk z_>G(9xy6ZyY_TitK|0vR`zezFEjOFTqOhmQ3Jmva#h5g{%+_(Vs7{qJ?f=|O()-rQ z1}LP(>%it%t8fj49wGD@g`)EH&y{Dh=uK(32{JAyTm8ZYe~#%zgf3hEi{Vz+VE)2te>Nn_Mcz|0;GDEKyBRf9P}=I<7=QoU;M$swn{FD z6-Obi&V}#vyj<9epraQ{LSZ$uJ&(w%D+-jB;1QxRiQ=~tD=anZf_t=T*s+i4n=AJW zGiHE(YWF)3&DY|kW za-@Tzz=c)hYvo?~n7(4nW3fiEo>9&aDV9iT1qh8jiDKfu(Lz=wo z>3MNfDYA7@U?%^G67tKeM~u-J&QHMH=XyvAyHfhaw{scOs#`%gBrEE)CD?oad5ZM>Hz3#e7>8QrGc!?eFShQh(>RDPZI9#z|a^to?o#kClhgFiwytnodB2ag^T0xK^TlPBqc)DZr z;D+tN*gXJg$5*(Dbl^`dZi6+V?rer_o*{KyZ0&VhGFsq3neVq{XQ!U{1~mWh9T^)R zZ$~eiI;1N<`qo)^J#;D)&Tm^G2ELUlb<^8t^CJQ(9;%*tbi zX=cU4TyZO}R-AqN<0m%FV}68q{$>bJ)(nyZ)I+ukd6cq|etkyPIDkG%wKNK^fQ+(4 zR%JDyZn>|^$Mg*~P?5(;4$F62THa*#>J%M*_Yu*QFcnhIadH0dA zBqMz}xNWm}Aw2(J?44CaJv>s!DpxspQYq6N5)wig|Gl5}#<3pDQTtSDwtUS}zg4^l zM|a#2-RjT+pX36=_`3>8DBTN`8ROhDCqgN~s3Cuy>X(%EQj1UW@o7?lpu8u3M$K?M8)#5IE~0Bp*p zrr~aJWs5Bi=BFh_w~{oa1pg+~DdOs`=~cQs1mm<3^jOpVUXb#WnGB?|0Jf4JR1D7M z0<*@sF7?V+k8)L7Tpb-0_L%;#rtVliyC3G=VZM@$Zq}5(-qG`(j(#vtyLlq&%dFO6 zsc}>#S0AR)J49%RX7%Pw&joPRoFP`D!#S9Nv z5&Hd?&S~sF*{g6Po6Nqg#$$DFvxLsd>fEHUQZt6&WZtCglQV}^)%XS1VRPJa^lTvurtaQku3e(t1I#9q1LQ8G ziKRmW@PdQ!B5+~{dN@d!U!`ZnbYZo8q8Zh-c}95d(8CcSWXhLUr7y$9>PW9eF`!od`y13@&owY8p zQO{L*mQ|t|`pSRYX3YG|(3cvu=u)$^p(FJhdFETA5mFDPi=cubV&v<{%mK#`{>q6H z29^K{7E+B5Qv+ni$bJPU;@P9!V}MCd{+DFBTFPvZ2d)!mv z%6mdf`v(iU5K~1V%5(P3QA3INs1~YZSNiU2Nb+y;**XxzdL@n&z$Oty2k>+X@Y9Ia zUjoNqmBdh8OL=;|zJ;ky52a9wnU;K+B6u=h{F8YmleU#l-5uD+xa2aj!q)UrgnoIj zuk?eEalqwx9@}Zsxm|6iUeFBxJ!zLE8+SeIJd{=WF1^DPhk#=tkwIJmaWqX z2^l^ic*-nSrmP5HoASwiyoCl*+PFCz>&!pxCCPPCS>d+l^=zVl4 zKLI<9$p32QVv_+W_kD+2b?Lv{utU~rhPB0m*79Ap=Q)u_oX5#FzAa9}Biryi6fM_`|dT?wH983vYIy%?FUX)2*<>B*H4iTv3gWCPOTSGB=+cr@#~bDMgi7 zLH_-4@{g^bo%AuGRzh5Pj4x018zZn|I3mUE6L)p1EWWyCD*yP3Uw6$b{2n08 zTVll1_@W?D2z5n5K{Q1jC0O!aZ3HCV>i7VJ4U{ZIr`d{57J}>U)H9^*8@1h=j8e%N zG#82bgiMe(PJe)STKZ=15~tz@Ckg7vk$$S8R;oADX;ahj&muiqvV2WBu>5P5>q2Ye z`g48>z`e9cz@>Q$%YPw%54Ujeu#J@@^ypTv$qt#L1FLh-lI1=P9j&!&SyV8(Lfm=E zmWbtGcZG@Ro>s!z9Ff|vI=)c}PMyd&3!QziT-p^N(oBWh*rfonh&MQhg!d=@c`LYB z&u=^JorT&MJB0Gxs%+fzm+3D*Lic}^;+!B<&MkP)fNO+!6dH*up_S5xRUD{@j{g3q zn+vh2wCIxZ-t}x^yy`3&0!fSl@?qVtLO~A#G3qx_4vRCJ_Sy6Lm&`@| zIv$ZZPKF2I)`i}Gt;~~tzHC^2fT-QP4DsG+L|Q7}&y$a$UP_bbZ)Gp!$&>~pdpZiS zE}kuT$hxIhl}w)X$EzI**#bhio$ME)xsu*|3bzLE-&&~QMCm)ST^MOcyHxI$YuJP) zNxw@VR(4h2uAj*RF~8ZR8oB8yfaHckY)2;oLGyOTY$fuGi3Bul&+d?|bPv2BP+oDO!&@ld(iZS=w`YnDkx!VNmH zPi5QXK~+D{VH>P<4=plRqsj3bQxLA{t{8`?sV>3>Sah(^FVTdz3Od;sHI-1-<=msv z46Z--5Q5kr>gjghjU2QWBMvI==~~}bq9o3H8}tutDv*U5lX*moxiKsWd)j@gZ!)S& zu(zK{H?|u2ht%aprT#BB-wFlkP|1USOeOO({S+?Tbce9_7LUxhTx`lhGNC}@F~=Y5An0i=BU n`>9NbdKLp2@ z6DtZ(J^%V03m-pZWJQa6_`M<4&h2aY#Wfq{>Y%Ud+*EO;qrJ)&ufOHu(h`SV^IJ;N zGb>jm1K+~7G4jktqiNN5$}BvLTYoJ{qP+XZYqbwc)jh;zzZ^On3)#ZiM}OJ=+M3?7DpA`wSB}>c%{nwsXFdR7UUjB#cv!3xm+c7ZL|CX3(P9Qr|z+nmf&)I*;1?B~Hfq(fB zt&6_x0&Ew7(_K)#o$2if8y6TdG^(Fch=Iji8ELv~889ON0WqWfXZl90 zX>P^@th`~)1)f<`Y80O2#otNDILQk18p_*svuJ*(_*`{Uoca2LH0ODC#v-` zW0Bg6ibM08^VQ1HrT8zo3Rh;nMa9ctIK_Jv`#oKT))d4PNAuF)AJnIZHWa9{gws>0 zJZ_zd}R zI-b|?g3U0bm5c|7Ec zwanc>d@V&HN1I9qd^t677Wgf5y650|`2qwjrnkDVK@wbd*7!>W6~rU$g2NF5czUzS zXV(BN-4{i54WxkKT(+F zERL@ceEDZkT7yblxnMQCs7b{?qY4?ewSBNBn4|x?hz0qY7)M&(inO|55$j?aqCh1+ zH0Zy%;hZ`oipz^#wh+*@C09=##?3D)Cj|HOj@>Oj-P~NSP#NO87wF}%t?;HfQfJxa z@90-9l7RY^)}YD}`!uYQpvn4bnN*k4Evw|$#5{x=-MMKX0rF?j^v;UrZKU1;$%$YW z>f9_~w_sfM7pF2${r=4+5uWye@8vjIHcH&KFO2xD5tfW#GxqB07#W>7XG|@{XnPqo z)=4=}hN-2|uo3_KNUXHJ{vJc)q;iSeYwlTEm!-)0h&>9@#r^k+cPW$G8~S z8*3ptp$8`~Y1L@cCb-HcNMDy%gsX(24LVs5v5dOCAs!#{PO%DiPXGTr++Rx-4j!ay88Rab7JFVOTTpS{u0mR}?3IK`k!s$*l5*RpE3(+Vu~9MXwx53&H!67RtuTN(nPc zQpaEXgB29Xv%7D-EdsW-83K?;y)SqLV*3_FbQty$WcmnM!UHJKPhsM^`|SARt%MtR zxj0u1lOV?{SvPm57IBwC`^Pjtk%&O$*wQRHlk+pA(b;*G_5LdOWBQ-N%{t6yQ8jc~ za>cjOl_z52MyGgE;30ZhM%QjLZZ}XRdp}zuD0`1}A|udjjkq46(>3(4LA^X!a#bnp zv=Qm&MnW)+1R6)FhXDl=_jU(7UwlZ_+Xo-JR$NhMrC8V*je^_b*fP{mwOvQj5#KAir5GkPDkTkS1DDE7zMOTOzj5xr*-id{r?mk*v( z_I_F~rnJ6BzT8++Ey;^}+UN85Fb0f#CwsJn_~RtiKm1BS+SGk`19c-;N_R$CQiC%x z`v+DI^2E9aD1S_K%(kdBOXzs1PO5l3wwYs2j?| z2jARkw&eZ14W8^<*jQb+QVA>pXND9Mi#YY;=#MjfZ>MFD$zP;$ znsOBbqBottp`)&~y}#eGk+GcA_|*;OD4O{8v~QVnLKK(HvwI@1oanUO@QouB4s)avy zN;8vaufNsw#4DTo|8==fXuf%d@g`MFotJV!9Sj3>#aNY<=J+LqW!0{tY-HyA)sRy9essAfN^sn%b`puU*{v zt+-XO3lZwsYFjI-XBdHAIsfR9@F?gOdgK)@tA8*#TpnFTE9iF4J96w@i&2)=y}B$G zOpWv@K&1EZC!*_~S*Fx!TgwtO+;&Ebzb1wHvEZ)=&)hZtz`mu1A-6U zKOpwH>*e%F6P;~Be6zY_CS_+}J))uIug{#Dw)V?nHTGg2z5C@ib9vT$zM~LGO*Q!4 z{*vP1nJ3lhWtjx|{o8CYQt%V*#cqYc<5%tvsua)AjCaOGPYZ5B`pkrLef_p4X0SO` zGr66ax$39~=KOHxP`YllLZC}QY)RORyI?YOCEK?$ksusEV<^jVo=y4L!yNWUnX%(h z(z$#{0KZAA@5q<17}71lur%EQ{x7-mXv2ZFwA`nB^J7mY+D@xwH0aE_42cPi?G1HTyfKLpg0r=F=cb|S z^$yN^%xbuEfRZh}>qYCL&J20OsTno>cI0RE!&fS%qQ3_V~4~XMc=f z%-d?$KeM>d)U%mjzo+GNt0;gPc|Z_O42hZPHP9ZNz)Ik{c$%`%o4@WBLhh^V-*0u1 zb(iZ}tP-w364=rP z_MRP|Q)w`jY>0}dO~XZNsD>c@k}%?`UCbLRtE<)H?q5bgkhNF1MyLC5yzB@0_U=5zXOyIjolMuViuu+@!^lG=7v3KHJnMzoH0Wlo z6@>R-bhe**BLmJfMojmKsqhk-uUS^CK^Qx%g%W9xSkLHaGCv^{ATd}O?|aK za>16!OPp<>Vx^A#l&3Bqd^Cro10L!klqMg6y3CD4_3f&9-TeGCy#v3Y^~2{Z3I|U{ zQ)*~3)ilxO0?X56IeQ@F;kk5{{TfT-$!7Y#`^3h$^Y)M|(f@rY9UbrW|7HPfUp*={ z;{NmyUTC=+@^E@J>+5Z@z{rGbc+s%l-=C|0Ee7>8ql={)&DU)<$euu{Zd294K_G5w z*)`BhP^pRN#vG?QwN6rE9ix05fq&TP`+i%U^`4oJPzyIk0X ztJj-eS^H|OL?k9H3@y{UJ5dXM4!4U@ASGpf^-DxB`BQUe>>gOX^uksF&fkJodXnQj z5b$}em8IrB+~7n(6L%j7foo_`iq5^!M5mJ>XNilBbsUyl6*D^+K_eY&<v7Wl* zy6|u4uKe-FLT{C)YMHrlSe-;8iBfP>^Ws=t+_1cg@kNE?zG|nkiQW_sWg3B6M2{jFAt!WIqt$od z9pFhpBR}3)Hn5qv1_E7ujneev?2J>*tFE&?aSnOYRwO-#>>UNv z-q7ia+{#ON#L6RnzkY56E7w(_w{4U^{fPL-O#{ju3?ZmjMuJ74n(jVMsTt3B#=5O# z#eG7vX@y(-xw5Hy9qu!3mp2Kl&3Qj8JJC4d#_%gl%2uoqBhp(X5Gt7ICN{w;9xZsp zO9;%n`1TG70PGVrafijAQf^Cz$U^B2|HGO4#zY5Z^rc95|JtauZviAy?q2VznUFe!5_+&x9@VLBbjq3XN5@#;fk3fu zT&v+B3(XaVVj;6LE3!Qp-wA`h^}>-w}Sgj0aFgBpf&RBJZMp zX7MmdxK)qbS6*EA_(aPdrWes?IO?u|SC#k;I46%Njbihjb3!^*eJ*g z7HA(vsnkeYs^ZvFh$*`g(|F4~$eZQ~^>-Exrxihzo(lgtka)-g1sg6Y%GTv)e@=|` zk}C&@%_}XZpdB5Q^Wa`ihUgKaE^k82d~RD)7w;c2uBUMKT+^5=68HLZr-Z$0mD9GA zwC>%9YEB=&H|7s^8keq7=#rMv`XQq!Cq{Mh+G7EyvF_jyNww`A+v}{A|B}UN z4&TrQF06)&DQNv{b*k26j|dlxI?h_}S|&n)c!AtC=cOW5V+W|X!FA^N$nvHaud#gk zkRKb3stUY0iRgJv)~E_WN!?vr2bIdWV0ky1km% zeT!A%{6 zC!_10P)0xq`p*b&n&d44=htt?s`L}$m6wgHB}V3w(n#YT^Ff56i^bn=NXn0`(@zN#iSrTOYuU5oV3%rO#vb> z83gI89|v#BIp7TESaSm@UkjDV|K{uLE@T`fVrnoli#9Fk?Oi{oDYo(j5)in&DKY`g z9|07mio~r>_x3|8-We_45rStb`C$eik)zTU?4aZV1kcj%kqK+7CZRf)f0OM5E|gPW z!~SkR7ah4Ns2PJ3W>4x9mDqisNfY(4l6r02%-M!7v6ARlxytx~wqiY<PQBEn@OxYkm}>%H>H_r@VL17bfTU{56S54Vz#V_aw`w@DaY0Iz=n@tc*52 z&UOO5VvTMZAu>&E6ar%(=-!hX^Z$VOFMM$?nf6LGjUexN4kMb38Fy=^nOzLA@rbdq zzIeoIO{EqtVH!@6*H&7UfrIVFl5I^@xbi06-?aVVw1(BhxV5fuvS6t`DI7dDnj z$5U@sH@R*;+KUp`mUN6cDzNR|Ze`{=&$kNW#nN^)WtSQAyoU&hH7;owq)69mkC?Yr z{OkfyzY1wO_F~Z|$U7UN(k)l8?oN`1^{@4;p~=fv9L;w03m_9EpsUHz#lw>^>*N8; zyUAI#F!H$(JsJFW=IgUR_Bji$Ptt^O5m_=qy20K)Tg z#_?$voE@_npyY ziV5B$N?+E!E%bKoBHWLrSKxpg*f);)ehZPl`n$LgF+|+Xny28N90v~*{YRy{npeO4 z3Fb9*a%y%Gh9}hYYK{()M8TW&g%WJ75@r1sg@I5ib^oa@hqcB-M*4BK4!H-mfmaK?yng_WO?A8}hBfavHhN zcOvdXUQI!B2Om3lxwdN&Nj+Fl1wlL@Rs^0;A>4O*o^Xk<;2yW~1I!{T78?>keC|y! z6rn}kSQw<9ug)#Z(FZRq>^TD^vK~A?G#F%rvifZ1$}1g!*`E8l?R)4m?k+{FJNDb;%M|y!`tbh4K5}Kdd?0+^(%396!5I5$x;X+ChCJ48uORIL3fCS|T>z`^9 z(D(L{20DwiBcP#gF8Pq{3!krmSI2clu0E$`!4w!6xk`sNS~uF|hpfakjGW_v;QXvw zULv|*LCb1P!PaSUCl9j25Hs{Az{I%lt-{M?e8aIjXTk8X?hpB{8$UI1HDBrIW2Or! z*5tWNr)k&z#S$wM#OI&y&_e#r7gz!eVR5T$ubG7!nO-yS`cj$dzy*4qGESe2A2e-_ zLgNCk^1%_m-JgbV;h~pDA4|ofoqn;l@}849Ke3^p@#;_0K`~B+m;*xnT+mrr=Z&=6 zr!Svw5P_fk5ItHqU=B-MkiHnmp&PL_2i=;25f=y5ngin}Jv$=D<$EixjOA^awtKg= z^&X6!@`fjWU;J!~8S0qGBbidgj#3L|hxb^qyYDL_$`WgI-f97N&tb))XRce4+jo*~UEXZA#| zB$9kT;&iO>yLCkqaWdE(FN2(C<=-18zlfz>%(Uj)%DTwSdr~lM{Xj+@Fo`$(rcbr= zO*cwD|HRm||N5(iTkz*5G>Zig?d_&oRX)+cqH(Il*o zrX^uLd%ZwZ^B#QY4$$%oKt|(arf+p@lDLshL(jjV9S2nGfuq-Z0~#?qzcJoJ_BedF zW#4W2s=5SB{G_{gH=|L1k}2`uAg3>Kw#R*XpcW=Ok5BGed8>Ks6s1!T=~i(a*~7?mvnz-wr>v_C5UZwyrfE#7^=(+^ z%+^cny{X|YQnbEhp{!L?hzzPEMKkk_b}e-$I!UH^hf9h?)mV0$wVbez6fBjWoNzpW{<-!ym4H-ls(st=GmG-d1>p3uv&VxKJPR!}}LuJzBj-^>!{e z?`WV&T2Z|(;%Hd}!Q4hEbYvuAzvT}Gr`cB_-4;?v*=4~AUCELQO@bC>YAF?3C{Oetl zJ%oy$^v%OI*Ij&Ie;6dQk>q|w7;rgNmM(7eaQV&e$8v2K$6Gh-iO|iDAK~8 zi)M(vK1Ni4zFHpbaNr%Gv;dr}!Jq5U&N-N2|CKm1A}8bWl$+jsA4YIpXiP)fy#gno zC$D7!6*a0lTk2lrn0MKm$sR^wJJkBXQVtmfj5W`_g3-3)w}0(cBi&W49~ZH8UNXb{ zQ@-0Dg}(_-e4%X(=OE`GIY#pk^Ju$%JzPs3I2n8I93c=548oZ1qvyM96GrrKjsi^8 zm>X@Q=0wE*%DZ&vH;@&RXfs^uB zu}rrTm<&I>dHLZNwm(7RKaI&g)s=R#DHpN2xCKAA?gB*WClOu0!I1NacUkzDiKYvP z*qGUk;U|ub;>%l&fE&cO-ygXWrMFK--gS`otRn zzFMLm*}WSvpOUxrx%9QMI%8TTe8tI-2CXBC>-=}QVn;eZW(ePj*V*56gL_{Dp;&vFj>fh znEWbelw-!5IB29EwXBgrGW_9t5!!#-8h2j?*s$hM9V>6LaAJjKtMzwBM%_kAi+6Oj ze9MD0sq{lrYdXuunP}Lzop6jU4lQe}mJN(4G|7{C#qD`qUb2p$}$7J%wC|Sv_TT>KIy|+KZJ`v1fsB@CS5CmdkcTMuV;3ye}*-d zaj*@94jSV2zt=XHG8GUEFVM~ z?Kmnd;4jaD1PA^uP)dCkJ=<0F2yUdO7|^#lt1KHu0yY^e?zEb8SK8NE2*1OR74w0= zqY={LYp_?ave>ymRN>lJYC(uurUDVU3 zCMDJq)ahOTd_M~tSYerWe-70=R%^lq@mx0Aj1^< zV2PxzPo{s{)JW2^oWfXKkMNiDUfk61s+_tZu^%7u^u)f_`9W!6S(b3G(#p+3NMqAv zlMGzD9O_z>TL#j!UIOBssP!sSZjmc}xN*a>o8UJRw~e~9xs}MpUi3*{L0M`o2+_#V ziS<@X>Ub98reK_=wZMN#7oG0F^itP7FI3AjCd1RD^yA>=Tn(#i*((V!w6vo0entRB z8@Ky6JRZFc|8uuf+uHkiJtyHx)MfVf7UJSE8NEZ)tj)QN#6Ba;lP?i1Av7HvJNmf4 z)M@Po#@e4Qa)XHOBCI`PB=G4=C%BJS!r3Y{6| znkYatPi3Pw_jL2bncn_aV4ABv&^Y`X5}$1@F77`g;95?+1wz(;@~wDMxX`d%`LGfz zjPX8fR->}w0Zvvi5TfM7RL%(;$G5b#LS2*aMqBk2scec3>8crmopP?Zq+5s)Ny^Xu zCl8{2m<29j{=9z4kOQmSzGD_V(DI>JE_AAYpV9r!+ zL`_15=f|VJx0?L-_Kx(W*vS3L3Q_7ymW1K^tqV%3wRhlGqDGMcIO$i`HEe{a{G#_~ zNL2A%1w@5pzn!y5@mIB5oujNy>b2;+nFhTLrLmiNh$oFrPvDLFQRi~c(*IqLF1a~A zjcV%R>smQ*V`Q&eZ4b?)DBfg2-rDHn`nR0#Z2nXu z5&T%xTYPU>%|P!&n~o5*2;_Yx8<(7qU1rOM6%q|A3hw?}D@6ELtqqR!!KR!(@241S z!Kg`vs?>#-9+x=UY$5DcLXK8GeC`$6=G4!e0e1OCK=d&+0V{F<7!C|yS4R8NLk{#F zL?>itx`}})j?8BpZ=@6L9ia=gQ>FDB%IdGE3kUzf`fxmviPw^~(XtMS+1!Xhoe=&$ zY((`AtnZrDE$dMtuj*5V3t9^I>>m-L?*H!|m-TFbE}CP8oxEVmzc9AZSptVREX#R2 zgn93I~MP0W?CQwlp+BjQZr5jEZGCEH@28ev-JX*lCSR*tH^e^XG-RRGJDgOR} zv_XyzQl_w-INTR&QGkFxZu{^bB{r~y!I~PR@$yE88hAxEy32R5AwA$+a%Q(tDLf%^ zdKdHoG<$ma4AU(D?zW=mzMI7D;t`Nu>n<+Vh6BG%F2$5x9%A6=%LNsv%OAy~WiDHU z(t8|O1s*z(v4i&lP8+wz-{m9xo1u9K*`}JUnN(6&)g55Z?~A!OO&n`j;5I_68!Rad zPEc<*-N%*3K{Aast-08MT|xcc$Nn!jIm=X!fpq|DD5l7`L)Lh+B$Hc*y1?$q%-%Lk z1&V=V%|a6c-v#g&OxAwR=cE@gS00FMtqd28C$t0XU;~auuJ8J?vV{9b9o*cd2Rl3* zew4&A{d@%KyAClbLE{3jw5Z@rNgeg_Gw^eNB$^p*dY4my!6J3u%@qtN1~8QF5mdnF zH_PHKWX3sCAki#F99nWwu(oGbp)UOUkBK{t%bUu#zVNEY7h6_?9rU2qH0PXdU&n-H z0E7P+XM**jWKU^)%0!&^b9x-kmbz~0YMzna2i(35+-^Xv1d5A~V>N=io>H+bC{n4| z^w#nnxaTpjn8yEh@GULQns&j&aYSpI%VT%}YBDi{+zK&&X`ONHq0Jc?O}*J8cl~VD zL21FOlWdQ^iY_)C8C_jZ>O0E5VJ#k!0Qevo8@twc0cRNoBNvLMTK16uSnM>5MzrzNzZZ zyKMYX+JEVEqY5pkqAU^=aK4wOtg8ii*J*@}KL%h}q{8wDL~Glsx2lTKtV-OkUcBwP zuV6Z<%lW_4;F|@D#oc7dL@(I8?~Z^-8s?tRL!&%#T?Rnv&ZMzGh5Obzr#D6MS4ReG zG=b|6{v$IQj9wS7mKvx9?O-!BD+EXQR&tOQzB zbcGXtpeGLdl7rV3ZUYCG5il48J|K}ei&?$j}Z0bJ+SX~o9qfYX?nSH znQ4U02?%TE+>Yu>gW*)Z<~UN@+w)}7h5uWB{{MpmTnHMo`{U2*^ylka Q|HD{QO;@$z`P Date: Sun, 24 Sep 2023 21:14:10 +0200 Subject: [PATCH 073/151] qgs3dmapscene: Fix sip file This fixes commit b9f770210c03201a5d81324ac3b1523f57bc2bd8. --- python/3d/auto_generated/qgs3dmapscene.sip.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index c3e1da710db2..dc131a15b6bd 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -106,7 +106,7 @@ Returns the scene's elevation range .. note:: - Only terrain and point cloud layers are taken into account + Only some layer types are considered by this method (eg terrain, point cloud and mesh layers) .. versionadded:: 3.30 %End From aad0381778b5384b27d5812de2cceebe22643468 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sun, 24 Sep 2023 13:33:28 +0700 Subject: [PATCH 074/151] Fix crash when removing the last recent project followed by selecting another recent project item --- src/app/qgsrecentprojectsitemsmodel.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/qgsrecentprojectsitemsmodel.cpp b/src/app/qgsrecentprojectsitemsmodel.cpp index 7861e2329002..d5ca5596c776 100644 --- a/src/app/qgsrecentprojectsitemsmodel.cpp +++ b/src/app/qgsrecentprojectsitemsmodel.cpp @@ -105,11 +105,9 @@ QVariant QgsRecentProjectItemsModel::data( const QModelIndex &index, int role ) } } - Qt::ItemFlags QgsRecentProjectItemsModel::flags( const QModelIndex &index ) const { - QString path; - if ( !index.isValid() || !rowCount( index.parent() ) ) + if ( !index.isValid() || !rowCount( index.parent() ) || index.row() >= mRecentProjects.size() ) return Qt::NoItemFlags; Qt::ItemFlags flags = QAbstractListModel::flags( index ); @@ -122,7 +120,7 @@ Qt::ItemFlags QgsRecentProjectItemsModel::flags( const QModelIndex &index ) cons QgsProjectStorage *storage = QgsApplication::projectStorageRegistry()->projectStorageFromUri( projectData.path ); if ( storage ) { - path = storage->filePath( projectData.path ); + QString path = storage->filePath( projectData.path ); if ( storage->type() == QLatin1String( "geopackage" ) && path.isEmpty() ) projectData.exists = false; else From 3b45191419bfd1c9bba445aa6904ceb98e587d12 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 23 Sep 2023 10:04:47 +1000 Subject: [PATCH 075/151] Fix clazy warnings --- src/3d/qgs3dmapscene.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 61e2ad62185e..4775e7dcb3ae 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -153,7 +153,7 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine if ( renderer->type() == QLatin1String( "vector" ) ) { const QgsPoint3DSymbol *pointSymbol = static_cast< const QgsPoint3DSymbol * >( static_cast< QgsVectorLayer3DRenderer *>( renderer )->symbol() ); - if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url ) + if ( pointSymbol->shapeProperties().value( QStringLiteral( "model" ) ).toString() == url ) { removeLayerEntity( layer ); addLayerEntity( layer ); @@ -165,7 +165,7 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine for ( auto rule : rules ) { const QgsPoint3DSymbol *pointSymbol = dynamic_cast< const QgsPoint3DSymbol * >( rule->symbol() ); - if ( pointSymbol->shapeProperties()[QStringLiteral( "model" )].toString() == url ) + if ( pointSymbol->shapeProperties().value( QStringLiteral( "model" ) ).toString() == url ) { removeLayerEntity( layer ); addLayerEntity( layer ); @@ -334,7 +334,8 @@ void Qgs3DMapScene::onCameraChanged() void removeQLayerComponentsFromHierarchy( Qt3DCore::QEntity *entity ) { QVector toBeRemovedComponents; - for ( Qt3DCore::QComponent *component : entity->components() ) + const Qt3DCore::QComponentVector entityComponents = entity->components(); + for ( Qt3DCore::QComponent *component : entityComponents ) { Qt3DRender::QLayer *layer = qobject_cast( component ); if ( layer != nullptr ) @@ -342,7 +343,8 @@ void removeQLayerComponentsFromHierarchy( Qt3DCore::QEntity *entity ) } for ( Qt3DCore::QComponent *component : toBeRemovedComponents ) entity->removeComponent( component ); - for ( Qt3DCore::QEntity *obj : entity->findChildren() ) + const QList< Qt3DCore::QEntity *> childEntities = entity->findChildren(); + for ( Qt3DCore::QEntity *obj : childEntities ) { if ( obj != nullptr ) removeQLayerComponentsFromHierarchy( obj ); @@ -353,7 +355,9 @@ void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVectoraddComponent( layer ); - for ( Qt3DCore::QEntity *child : entity->findChildren() ) + + const QList< Qt3DCore::QEntity *> childEntities = entity->findChildren(); + for ( Qt3DCore::QEntity *child : childEntities ) { if ( child != nullptr ) addQLayerComponentsToHierarchy( child, layers ); @@ -763,7 +767,8 @@ void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity ) { // this is probably not the best place for material-specific configuration, // maybe this could be more generalized when other materials need some specific treatment - for ( QgsLineMaterial *lm : newEntity->findChildren() ) + const QList< QgsLineMaterial *> childLineMaterials = newEntity->findChildren(); + for ( QgsLineMaterial *lm : childLineMaterials ) { connect( mEngine, &QgsAbstract3DEngine::sizeChanged, lm, [lm, this] { @@ -773,7 +778,8 @@ void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity ) lm->setViewportSize( mEngine->size() ); } // configure billboard's viewport when the viewport is changed. - for ( QgsPoint3DBillboardMaterial *bm : newEntity->findChildren() ) + const QList< QgsPoint3DBillboardMaterial *> childBillboardMaterials = newEntity->findChildren(); + for ( QgsPoint3DBillboardMaterial *bm : childBillboardMaterials ) { connect( mEngine, &QgsAbstract3DEngine::sizeChanged, bm, [bm, this] { @@ -786,7 +792,8 @@ void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity ) // Finalize adding the 3D transparent objects by adding the layer components to the entities QgsShadowRenderingFrameGraph *frameGraph = mEngine->frameGraph(); Qt3DRender::QLayer *transparentLayer = frameGraph->transparentObjectLayer(); - for ( Qt3DRender::QMaterial *material : newEntity->findChildren() ) + const QList< Qt3DRender::QMaterial *> childMaterials = newEntity->findChildren(); + for ( Qt3DRender::QMaterial *material : childMaterials ) { // This handles the phong material without data defined properties. if ( Qt3DExtras::QDiffuseSpecularMaterial *ph = qobject_cast( material ) ) @@ -806,7 +813,8 @@ void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity ) Qt3DRender::QEffect *effect = material->effect(); if ( effect ) { - for ( const auto *parameter : effect->parameters() ) + const QVector< Qt3DRender::QParameter *> parameters = effect->parameters(); + for ( const Qt3DRender::QParameter *parameter : parameters ) { if ( parameter->name() == "opacity" && parameter->value() != 1.0f ) { @@ -1065,7 +1073,8 @@ QVector Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLay if ( !mLayerEntities.contains( layer ) ) return chunks; if ( QgsChunkedEntity *c = qobject_cast( mLayerEntities[ layer ] ) ) { - for ( QgsChunkNode *n : c->activeNodes() ) + const QList< QgsChunkNode * > activeNodes = c->activeNodes(); + for ( QgsChunkNode *n : activeNodes ) chunks.push_back( n ); } return chunks; From 990b1657efb013ef2412fcc4b5d9b16cd5a6a070 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 23 Sep 2023 10:09:40 +1000 Subject: [PATCH 076/151] QString fixup --- src/analysis/processing/qgsalgorithmxyztiles.cpp | 2 +- src/auth/basic/core/qgsauthbasicmethod.cpp | 4 ++-- src/core/qgsruntimeprofiler.cpp | 4 ++-- tests/src/analysis/testqgsprocessing.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmxyztiles.cpp b/src/analysis/processing/qgsalgorithmxyztiles.cpp index 86ef9d3a0600..9adba410de85 100644 --- a/src/analysis/processing/qgsalgorithmxyztiles.cpp +++ b/src/analysis/processing/qgsalgorithmxyztiles.cpp @@ -193,7 +193,7 @@ void QgsXyzTilesBaseAlgorithm::startJobs() settings.setDestinationCrs( mMercatorCrs ); settings.setLayers( mLayers ); settings.setOutputDpi( mDpi ); - if ( mTileFormat == QStringLiteral( "PNG" ) ) + if ( mTileFormat == QLatin1String( "PNG" ) ) { settings.setBackgroundColor( mBackgroundColor ); } diff --git a/src/auth/basic/core/qgsauthbasicmethod.cpp b/src/auth/basic/core/qgsauthbasicmethod.cpp index c032987d8fd9..e1d6e112da20 100644 --- a/src/auth/basic/core/qgsauthbasicmethod.cpp +++ b/src/auth/basic/core/qgsauthbasicmethod.cpp @@ -199,14 +199,14 @@ bool QgsAuthBasicMethod::updateDataSourceUriItems( QStringList &connectionItems, // If username or password contains comma or double quote we need to quote the string if ( username.contains( ',' ) || username.contains( '"' ) ) { - username.replace( '"', QStringLiteral( R"(\")" ) ); + username.replace( '"', QLatin1String( R"(\")" ) ); username.prepend( '"' ); username.append( '"' ); } if ( password.contains( ',' ) || password.contains( '"' ) ) { - password.replace( '"', QStringLiteral( R"(\")" ) ); + password.replace( '"', QLatin1String( R"(\")" ) ); password.prepend( '"' ); password.append( '"' ); } diff --git a/src/core/qgsruntimeprofiler.cpp b/src/core/qgsruntimeprofiler.cpp index 9bf6b403ef59..6c766c786970 100644 --- a/src/core/qgsruntimeprofiler.cpp +++ b/src/core/qgsruntimeprofiler.cpp @@ -623,7 +623,7 @@ void QgsRuntimeProfiler::extractModelAsText( QStringList &lines, const QString & QModelIndex cellIndex = index( r, c, parent ); cells << data( cellIndex ).toString(); } - lines << QStringLiteral( "%1 %2" ).arg( QStringLiteral( "-" ).repeated( level + 1 ), cells.join( QStringLiteral( ": " ) ) ); + lines << QStringLiteral( "%1 %2" ).arg( QStringLiteral( "-" ).repeated( level + 1 ), cells.join( QLatin1String( ": " ) ) ); extractModelAsText( lines, group, rowIndex, level + 1 ); } } @@ -640,7 +640,7 @@ QString QgsRuntimeProfiler::asText( const QString &group ) lines << ( !groupName.isEmpty() ? groupName : g ); extractModelAsText( lines, g ); } - return lines.join( QStringLiteral( "\r\n" ) ); + return lines.join( QLatin1String( "\r\n" ) ); } diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 5ccf8f42a853..80869f9226f9 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -11663,10 +11663,10 @@ void TestQgsProcessing::parameterAlignRasterLayers() QVERIFY( def->checkValueIsAcceptable( layerList, &context ) ); const QString valueAsPythonString = def->valueAsPythonString( layerList, context ); - QCOMPARE( valueAsPythonString, QStringLiteral( "[{'inputFile': '%1','outputFile': '%2','resampleMethod': %3,'rescale': False}]" ).arg( rasterLayer->source() ).arg( QStringLiteral( "layer2.tif" ) ).arg( 0 ) ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[{'inputFile': '%1','outputFile': '%2','resampleMethod': %3,'rescale': False}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif") ).arg( 0 ) ); QCOMPARE( QString::fromStdString( QgsJsonUtils::jsonFromVariant( def->valueAsJsonObject( layerList, context ) ).dump() ), - QStringLiteral( "[{\"inputFile\":\"%1\",\"outputFile\":\"%2\"}]" ).arg( rasterLayer->source() ).arg( QStringLiteral( "layer2.tif" ) ) ); + QStringLiteral( "[{\"inputFile\":\"%1\",\"outputFile\":\"%2\"}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif") ) ); bool ok = false; QCOMPARE( def->valueAsString( layerList, context, ok ), QString() ); QVERIFY( !ok ); From 088f02d36659fc1c8f15deec56aac12054be8dcb Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 25 Sep 2023 13:54:55 +1000 Subject: [PATCH 077/151] Fix spell check false positive --- scripts/spell_check/spelling.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spell_check/spelling.dat b/scripts/spell_check/spelling.dat index 1a440d809242..36535992a0ab 100644 --- a/scripts/spell_check/spelling.dat +++ b/scripts/spell_check/spelling.dat @@ -1298,7 +1298,7 @@ chemcial:chemical chemcially:chemically chemestry:chemistry chemicaly:chemically -childen:children +childen:children:* chilren:children childern:children childs:children:* From bfee9b29f0cf437d6baf495e78728756187f2cb9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 25 Sep 2023 13:56:07 +1000 Subject: [PATCH 078/151] Identation --- tests/src/analysis/testqgsprocessing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 80869f9226f9..5f4443722ef9 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -11663,10 +11663,10 @@ void TestQgsProcessing::parameterAlignRasterLayers() QVERIFY( def->checkValueIsAcceptable( layerList, &context ) ); const QString valueAsPythonString = def->valueAsPythonString( layerList, context ); - QCOMPARE( valueAsPythonString, QStringLiteral( "[{'inputFile': '%1','outputFile': '%2','resampleMethod': %3,'rescale': False}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif") ).arg( 0 ) ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[{'inputFile': '%1','outputFile': '%2','resampleMethod': %3,'rescale': False}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif" ) ).arg( 0 ) ); QCOMPARE( QString::fromStdString( QgsJsonUtils::jsonFromVariant( def->valueAsJsonObject( layerList, context ) ).dump() ), - QStringLiteral( "[{\"inputFile\":\"%1\",\"outputFile\":\"%2\"}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif") ) ); + QStringLiteral( "[{\"inputFile\":\"%1\",\"outputFile\":\"%2\"}]" ).arg( rasterLayer->source() ).arg( QLatin1String( "layer2.tif" ) ) ); bool ok = false; QCOMPARE( def->valueAsString( layerList, context, ok ), QString() ); QVERIFY( !ok ); From fd696f17a3a9430a13e08af8b0edf6458abe1a4d Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 19 Sep 2023 15:45:10 +0300 Subject: [PATCH 079/151] add icon to Elevation style category (refs #53680) --- src/gui/qgsmaplayerstylecategoriesmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qgsmaplayerstylecategoriesmodel.cpp b/src/gui/qgsmaplayerstylecategoriesmodel.cpp index d427f2456cc5..e873833fc450 100644 --- a/src/gui/qgsmaplayerstylecategoriesmodel.cpp +++ b/src/gui/qgsmaplayerstylecategoriesmodel.cpp @@ -284,7 +284,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro case Qt::ToolTipRole: return tr( "Elevation properties" ); case Qt::DecorationRole: - return QIcon(); // TODO + return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/elevationscale.svg" ) ); } break; From 5315ae6cc513fb469350215708f144830e567fae Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Thu, 21 Sep 2023 15:37:24 +0200 Subject: [PATCH 080/151] make avoidIntersections return an enum --- python/core/auto_additions/qgis.py | 5 ++- .../geometry/qgsgeometry.sip.in | 18 +++++++- python/core/auto_generated/qgis.sip.in | 1 + src/app/gps/qgsappgpsdigitizing.cpp | 9 ++-- src/app/qgisapp.cpp | 2 +- src/app/qgsmaptooladdpart.cpp | 2 +- src/app/qgsmaptooladdring.cpp | 1 + src/app/qgsmaptoolreshape.cpp | 8 ++-- src/app/vertextool/qgsvertextool.cpp | 8 ++-- src/core/geometry/qgsgeometry.cpp | 42 ++++++++++++++++--- src/core/geometry/qgsgeometry.h | 18 +++++++- src/core/qgis.h | 1 + .../qgsmaptoolcapturelayergeometry.cpp | 4 +- 13 files changed, 92 insertions(+), 27 deletions(-) diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 30ffa5bd5ef5..759a97a55aec 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -1464,7 +1464,10 @@ QgsGeometry.SplitCannotSplitPoint = Qgis.GeometryOperationResult.SplitCannotSplitPoint QgsGeometry.SplitCannotSplitPoint.is_monkey_patched = True QgsGeometry.SplitCannotSplitPoint.__doc__ = "Cannot split points" -Qgis.GeometryOperationResult.__doc__ = "Success or failure of a geometry operation.\n\nThis enum gives details about cause of failure.\n\n.. versionadded:: 3.22\n\n" + '* ``Success``: ' + Qgis.GeometryOperationResult.Success.__doc__ + '\n' + '* ``NothingHappened``: ' + Qgis.GeometryOperationResult.NothingHappened.__doc__ + '\n' + '* ``InvalidBaseGeometry``: ' + Qgis.GeometryOperationResult.InvalidBaseGeometry.__doc__ + '\n' + '* ``InvalidInputGeometryType``: ' + Qgis.GeometryOperationResult.InvalidInputGeometryType.__doc__ + '\n' + '* ``SelectionIsEmpty``: ' + Qgis.GeometryOperationResult.SelectionIsEmpty.__doc__ + '\n' + '* ``SelectionIsGreaterThanOne``: ' + Qgis.GeometryOperationResult.SelectionIsGreaterThanOne.__doc__ + '\n' + '* ``GeometryEngineError``: ' + Qgis.GeometryOperationResult.GeometryEngineError.__doc__ + '\n' + '* ``LayerNotEditable``: ' + Qgis.GeometryOperationResult.LayerNotEditable.__doc__ + '\n' + '* ``AddPartSelectedGeometryNotFound``: ' + Qgis.GeometryOperationResult.AddPartSelectedGeometryNotFound.__doc__ + '\n' + '* ``AddPartNotMultiGeometry``: ' + Qgis.GeometryOperationResult.AddPartNotMultiGeometry.__doc__ + '\n' + '* ``AddRingNotClosed``: ' + Qgis.GeometryOperationResult.AddRingNotClosed.__doc__ + '\n' + '* ``AddRingNotValid``: ' + Qgis.GeometryOperationResult.AddRingNotValid.__doc__ + '\n' + '* ``AddRingCrossesExistingRings``: ' + Qgis.GeometryOperationResult.AddRingCrossesExistingRings.__doc__ + '\n' + '* ``AddRingNotInExistingFeature``: ' + Qgis.GeometryOperationResult.AddRingNotInExistingFeature.__doc__ + '\n' + '* ``SplitCannotSplitPoint``: ' + Qgis.GeometryOperationResult.SplitCannotSplitPoint.__doc__ +QgsGeometry.GeometryTypeHasChanged = Qgis.GeometryOperationResult.GeometryTypeHasChanged +QgsGeometry.GeometryTypeHasChanged.is_monkey_patched = True +QgsGeometry.GeometryTypeHasChanged.__doc__ = "Operation has changed geometry type" +Qgis.GeometryOperationResult.__doc__ = "Success or failure of a geometry operation.\n\nThis enum gives details about cause of failure.\n\n.. versionadded:: 3.22\n\n" + '* ``Success``: ' + Qgis.GeometryOperationResult.Success.__doc__ + '\n' + '* ``NothingHappened``: ' + Qgis.GeometryOperationResult.NothingHappened.__doc__ + '\n' + '* ``InvalidBaseGeometry``: ' + Qgis.GeometryOperationResult.InvalidBaseGeometry.__doc__ + '\n' + '* ``InvalidInputGeometryType``: ' + Qgis.GeometryOperationResult.InvalidInputGeometryType.__doc__ + '\n' + '* ``SelectionIsEmpty``: ' + Qgis.GeometryOperationResult.SelectionIsEmpty.__doc__ + '\n' + '* ``SelectionIsGreaterThanOne``: ' + Qgis.GeometryOperationResult.SelectionIsGreaterThanOne.__doc__ + '\n' + '* ``GeometryEngineError``: ' + Qgis.GeometryOperationResult.GeometryEngineError.__doc__ + '\n' + '* ``LayerNotEditable``: ' + Qgis.GeometryOperationResult.LayerNotEditable.__doc__ + '\n' + '* ``AddPartSelectedGeometryNotFound``: ' + Qgis.GeometryOperationResult.AddPartSelectedGeometryNotFound.__doc__ + '\n' + '* ``AddPartNotMultiGeometry``: ' + Qgis.GeometryOperationResult.AddPartNotMultiGeometry.__doc__ + '\n' + '* ``AddRingNotClosed``: ' + Qgis.GeometryOperationResult.AddRingNotClosed.__doc__ + '\n' + '* ``AddRingNotValid``: ' + Qgis.GeometryOperationResult.AddRingNotValid.__doc__ + '\n' + '* ``AddRingCrossesExistingRings``: ' + Qgis.GeometryOperationResult.AddRingCrossesExistingRings.__doc__ + '\n' + '* ``AddRingNotInExistingFeature``: ' + Qgis.GeometryOperationResult.AddRingNotInExistingFeature.__doc__ + '\n' + '* ``SplitCannotSplitPoint``: ' + Qgis.GeometryOperationResult.SplitCannotSplitPoint.__doc__ + '\n' + '* ``GeometryTypeHasChanged``: ' + Qgis.GeometryOperationResult.GeometryTypeHasChanged.__doc__ # -- Qgis.GeometryOperationResult.baseClass = Qgis QgsGeometry.ValidityFlag = Qgis.GeometryValidityFlag diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 0fb7b60e15dc..d50dce38f2ad 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2430,7 +2430,7 @@ empty if none of the child geometries match the desired type. .. versionadded:: 3.2 %End - int avoidIntersections( const QList &avoidIntersectionsLayers ); + int avoidIntersections( const QList &avoidIntersectionsLayers ); %Docstring Modifies geometry to avoid intersections with the layers specified in project properties @@ -2444,6 +2444,22 @@ Modifies geometry to avoid intersections with the layers specified in project pr 4 if the geometry is not intersected by one of the geometries present in the provided layers. .. versionadded:: 1.5 +%End + + Qgis::GeometryOperationResult avoidIntersectionsV2( const QList &avoidIntersectionsLayers ); + +%Docstring +Modifies geometry to avoid intersections with the layers specified in project properties + +:param avoidIntersectionsLayers: list of layers to check for intersections + +:return: Success in case of success + InvalidInputGeometryType if geometry is not of polygon type + GeometryTypeHasChanged if avoid intersection has changed the geometry type, + InvalidBaseGeometry at least one geometry intersected is invalid. The algorithm may not work and return the same geometry as the input. You must fix your intersecting geometries. + NothingHappened if the geometry is not intersected by one of the geometries present in the provided layers. + +.. versionadded:: 3.34 %End QgsGeometry makeValid( Qgis::MakeValidMethod method = Qgis::MakeValidMethod::Linework, bool keepCollapsed = false ) const throw( QgsNotSupportedException ); diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index ace323980b68..67d37833a608 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -891,6 +891,7 @@ The development version AddRingNotInExistingFeature, // Split features SplitCannotSplitPoint, + GeometryTypeHasChanged, }; enum class GeometryValidityFlag diff --git a/src/app/gps/qgsappgpsdigitizing.cpp b/src/app/gps/qgsappgpsdigitizing.cpp index 812d131cc1e4..bd3eafbc7973 100644 --- a/src/app/gps/qgsappgpsdigitizing.cpp +++ b/src/app/gps/qgsappgpsdigitizing.cpp @@ -277,18 +277,18 @@ void QgsAppGpsDigitizing::createFeature() if ( geometryLayerCrs.type() == Qgis::GeometryType::Polygon ) { - const int avoidIntersectionsReturn = geometryLayerCrs.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() ); - if ( avoidIntersectionsReturn == 1 ) + const Qgis::GeometryOperationResult avoidIntersectionsReturn = geometryLayerCrs.avoidIntersectionsV2( QgsProject::instance()->avoidIntersectionsLayers() ); + if ( avoidIntersectionsReturn == Qgis::GeometryOperationResult::InvalidInputGeometryType ) { //not a polygon type. Impossible to get there } - else if ( avoidIntersectionsReturn == 2 ) + else if ( avoidIntersectionsReturn == Qgis::GeometryOperationResult::GeometryTypeHasChanged ) { //bail out... QgisApp::instance()->messageBar()->pushWarning( tr( "Add Feature" ), tr( "The feature could not be added because removing the polygon intersections would change the geometry type." ) ); return; } - else if ( avoidIntersectionsReturn == 3 ) + else if ( avoidIntersectionsReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry ) { QgisApp::instance()->messageBar()->pushCritical( tr( "Add Feature" ), tr( "The feature has been added, but at least one geometry intersected is invalid. These geometries must be manually repaired." ) ); return; @@ -488,4 +488,3 @@ QVariant QgsAppGpsDigitizing::timestamp( QgsVectorLayer *vlayer, int idx ) const } return value; } - diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index eec638d74a92..c1172e1a5795 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -10221,7 +10221,7 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer ) } if ( avoidIntersectionsLayers.size() > 0 ) { - geom.avoidIntersections( avoidIntersectionsLayers ); + geom.avoidIntersectionsV2( avoidIntersectionsLayers ); } // count collapsed geometries diff --git a/src/app/qgsmaptooladdpart.cpp b/src/app/qgsmaptooladdpart.cpp index 8c184fcf393a..e6b10afd9a46 100644 --- a/src/app/qgsmaptooladdpart.cpp +++ b/src/app/qgsmaptooladdpart.cpp @@ -162,6 +162,7 @@ void QgsMapToolAddPart::finalizeEditCommand( QgsVectorLayer *layer, Qgis::Geomet case Qgis::GeometryOperationResult::LayerNotEditable: case Qgis::GeometryOperationResult::NothingHappened: case Qgis::GeometryOperationResult::SplitCannotSplitPoint: + case Qgis::GeometryOperationResult::GeometryTypeHasChanged: // Should not reach here // Other OperationResults should not be returned by addPart errorMessage = tr( "Unexpected OperationResult: %1" ).arg( qgsEnumValueToKey( errorCode ) ); @@ -228,4 +229,3 @@ QgsVectorLayer *QgsMapToolAddPart::getLayerAndCheckSelection() else return nullptr; } - diff --git a/src/app/qgsmaptooladdring.cpp b/src/app/qgsmaptooladdring.cpp index b412d852e51b..63554743f1c7 100644 --- a/src/app/qgsmaptooladdring.cpp +++ b/src/app/qgsmaptooladdring.cpp @@ -100,6 +100,7 @@ void QgsMapToolAddRing::polygonCaptured( const QgsCurvePolygon *polygon ) case Qgis::GeometryOperationResult::LayerNotEditable: case Qgis::GeometryOperationResult::AddPartSelectedGeometryNotFound: case Qgis::GeometryOperationResult::AddPartNotMultiGeometry: + case Qgis::GeometryOperationResult::GeometryTypeHasChanged: errorMessage = tr( "an unknown error occurred (%1)" ).arg( qgsEnumValueToKey( addRingReturnCode ) ); break; } diff --git a/src/app/qgsmaptoolreshape.cpp b/src/app/qgsmaptoolreshape.cpp index 292fb3cb5fef..e3d7290d822f 100644 --- a/src/app/qgsmaptoolreshape.cpp +++ b/src/app/qgsmaptoolreshape.cpp @@ -194,11 +194,11 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer ) case Qgis::AvoidIntersectionsMode::AllowIntersections: break; } - int res = -1; + Qgis::GeometryOperationResult res = Qgis::GeometryOperationResult::NothingHappened; if ( avoidIntersectionsLayers.size() > 0 ) { - res = geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ); - if ( res == 1 ) + res = geom.avoidIntersectionsV2( QgsProject::instance()->avoidIntersectionsLayers(), ignoreFeatures ); + if ( res == Qgis::GeometryOperationResult::InvalidInputGeometryType ) { emit messageEmitted( tr( "An error was reported during intersection removal" ), Qgis::MessageLevel::Critical ); vlayer->destroyEditCommand(); @@ -213,7 +213,7 @@ void QgsMapToolReshape::reshape( QgsVectorLayer *vlayer ) vlayer->destroyEditCommand(); return; } - if ( res == 3 ) + if ( res == Qgis::GeometryOperationResult::InvalidBaseGeometry ) { emit messageEmitted( tr( "At least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::MessageLevel::Warning ); } diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index 60dfad3aa3b9..c9f1645246bf 100644 --- a/src/app/vertextool/qgsvertextool.cpp +++ b/src/app/vertextool/qgsvertextool.cpp @@ -2425,11 +2425,11 @@ void QgsVertexTool::applyEditsToLayers( QgsVertexTool::VertexEdits &edits ) QgsGeometry featGeom = itFeatEdit.value().geom; if ( avoidIntersectionsLayers.size() > 0 ) { - int avoidIntersectionsReturn = featGeom.avoidIntersections( avoidIntersectionsLayers, ignoreFeatures ); + Qgis::GeometryOperationResult avoidIntersectionsReturn = featGeom.avoidIntersectionsV2( avoidIntersectionsLayers, ignoreFeatures ); switch ( avoidIntersectionsReturn ) { - case 2: // Geometry type was changed, let's try our best to make it compatible with the target layer + case Qgis::GeometryOperationResult::GeometryTypeHasChanged: // Geometry type was changed, let's try our best to make it compatible with the target layer { const QVector newGeoms = featGeom.coerceToType( layer->wkbType() ); if ( newGeoms.count() == 1 ) @@ -2476,7 +2476,7 @@ void QgsVertexTool::applyEditsToLayers( QgsVertexTool::VertexEdits &edits ) break; } - case 3: + case Qgis::GeometryOperationResult::InvalidBaseGeometry: emit messageEmitted( tr( "At least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::MessageLevel::Warning ); break; @@ -2485,7 +2485,7 @@ void QgsVertexTool::applyEditsToLayers( QgsVertexTool::VertexEdits &edits ) } // if the geometry has been changed - if ( avoidIntersectionsReturn != 1 && avoidIntersectionsReturn != 4 ) + if ( avoidIntersectionsReturn != Qgis::GeometryOperationResult::InvalidInputGeometryType && avoidIntersectionsReturn != Qgis::GeometryOperationResult::NothingHappened ) { // then add the new points generated by avoidIntersections QgsGeometry oldGeom = layer->getGeometry( itFeatEdit.key() ).convertToType( Qgis::GeometryType::Point, true ); diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 153af6fbb07b..956af86aac75 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3039,11 +3039,11 @@ bool QgsGeometry::deletePart( int partNum ) return ok; } -int QgsGeometry::avoidIntersections( const QList &avoidIntersectionsLayers, const QHash > &ignoreFeatures ) +Qgis::GeometryOperationResult QgsGeometry::avoidIntersectionsV2( const QList &avoidIntersectionsLayers, const QHash > &ignoreFeatures ) { if ( !d->geometry ) { - return 1; + return Qgis::GeometryOperationResult::InvalidInputGeometryType; } Qgis::WkbType geomTypeBeforeModification = wkbType(); @@ -3059,15 +3059,45 @@ int QgsGeometry::avoidIntersections( const QList &avoidInterse } if ( geomTypeBeforeModification != wkbType() ) - return 2; + return Qgis::GeometryOperationResult::GeometryTypeHasChanged; if ( haveInvalidGeometry ) - return 3; + return Qgis::GeometryOperationResult::InvalidBaseGeometry; if ( !geomModified ) - return 4; + return Qgis::GeometryOperationResult::NothingHappened; - return 0; + return Qgis::GeometryOperationResult::Success; } +int QgsGeometry::avoidIntersections( const QList &avoidIntersectionsLayers, const QHash > &ignoreFeatures ) +{ + const Qgis::GeometryOperationResult result = avoidIntersectionsV2( avoidIntersectionsLayers, ignoreFeatures ); + switch ( result ) + { + case Qgis::GeometryOperationResult::Success: + return 0; + case Qgis::GeometryOperationResult::InvalidInputGeometryType: + return 1; + case Qgis::GeometryOperationResult::GeometryTypeHasChanged: + return 2; + case Qgis::GeometryOperationResult::InvalidBaseGeometry: + return 3; + case Qgis::GeometryOperationResult::NothingHappened: + return 4; + case Qgis::GeometryOperationResult::SelectionIsEmpty: + case Qgis::GeometryOperationResult::SelectionIsGreaterThanOne: + case Qgis::GeometryOperationResult::GeometryEngineError: + case Qgis::GeometryOperationResult::LayerNotEditable: + case Qgis::GeometryOperationResult::AddPartSelectedGeometryNotFound: + case Qgis::GeometryOperationResult::AddPartNotMultiGeometry: + case Qgis::GeometryOperationResult::AddRingNotClosed: + case Qgis::GeometryOperationResult::AddRingNotValid: + case Qgis::GeometryOperationResult::AddRingCrossesExistingRings: + case Qgis::GeometryOperationResult::AddRingNotInExistingFeature: + case Qgis::GeometryOperationResult::SplitCannotSplitPoint: + // should never happen + return 4; + } +} QgsGeometry QgsGeometry::makeValid( Qgis::MakeValidMethod method, bool keepCollapsed ) const { diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index eb69fbddc1a2..5d0a6eca75b5 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2551,8 +2551,22 @@ class CORE_EXPORT QgsGeometry * 4 if the geometry is not intersected by one of the geometries present in the provided layers. * \since QGIS 1.5 */ - int avoidIntersections( const QList &avoidIntersectionsLayers, - const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ); + Q_DECL_DEPRECATED int avoidIntersections( const QList &avoidIntersectionsLayers, + const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ); + + /** + * Modifies geometry to avoid intersections with the layers specified in project properties + * \param avoidIntersectionsLayers list of layers to check for intersections + * \param ignoreFeatures possibility to give a list of features where intersections should be ignored (not available in Python bindings) + * \returns Success in case of success + * InvalidInputGeometryType if geometry is not of polygon type + * GeometryTypeHasChanged if avoid intersection has changed the geometry type, + * InvalidBaseGeometry at least one geometry intersected is invalid. The algorithm may not work and return the same geometry as the input. You must fix your intersecting geometries. + * NothingHappened if the geometry is not intersected by one of the geometries present in the provided layers. + * \since QGIS 3.34 + */ + Qgis::GeometryOperationResult avoidIntersectionsV2( const QList &avoidIntersectionsLayers, + const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ); /** * Attempts to make an invalid geometry valid without losing vertices. diff --git a/src/core/qgis.h b/src/core/qgis.h index b48b63e24c37..95a117a05478 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -1483,6 +1483,7 @@ class CORE_EXPORT Qgis AddRingNotInExistingFeature, //!< The input ring doesn't have any existing ring to fit into // Split features SplitCannotSplitPoint, //!< Cannot split points + GeometryTypeHasChanged, //!< Operation has changed geometry type }; Q_ENUM( GeometryOperationResult ) diff --git a/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp b/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp index c32230b9fc63..b9519dd22277 100644 --- a/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp +++ b/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp @@ -57,8 +57,8 @@ void QgsMapToolCaptureLayerGeometry::geometryCaptured( const QgsGeometry &geomet } if ( avoidIntersectionsLayers.size() > 0 ) { - const int avoidIntersectionsReturn = g.avoidIntersections( avoidIntersectionsLayers ); - if ( avoidIntersectionsReturn == 3 ) + const Qgis::GeometryOperationResult avoidIntersectionsReturn = g.avoidIntersectionsV2( avoidIntersectionsLayers ); + if ( avoidIntersectionsReturn == Qgis::GeometryOperationResult::InvalidBaseGeometry ) { emit messageEmitted( tr( "The feature has been added, but at least one geometry intersected is invalid. These geometries must be manually repaired." ), Qgis::MessageLevel::Warning ); } From 7ef5717c7ac3cc82d52c1ad7ef9bb81973ced2c5 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Mon, 25 Sep 2023 11:12:15 +0200 Subject: [PATCH 081/151] add deprecated instructions --- python/core/auto_generated/geometry/qgsgeometry.sip.in | 5 ++++- src/core/geometry/qgsgeometry.h | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index d50dce38f2ad..ba07e4d0b77f 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2430,7 +2430,7 @@ empty if none of the child geometries match the desired type. .. versionadded:: 3.2 %End - int avoidIntersections( const QList &avoidIntersectionsLayers ); + int avoidIntersections( const QList &avoidIntersectionsLayers ) /Deprecated/; %Docstring Modifies geometry to avoid intersections with the layers specified in project properties @@ -2444,6 +2444,9 @@ Modifies geometry to avoid intersections with the layers specified in project pr 4 if the geometry is not intersected by one of the geometries present in the provided layers. .. versionadded:: 1.5 + +.. deprecated:: + QGIS 3.34 %End Qgis::GeometryOperationResult avoidIntersectionsV2( const QList &avoidIntersectionsLayers ); diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 5d0a6eca75b5..b7ddd18c3a44 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2550,9 +2550,10 @@ class CORE_EXPORT QgsGeometry * 3 at least one geometry intersected is invalid. The algorithm may not work and return the same geometry as the input. You must fix your intersecting geometries. * 4 if the geometry is not intersected by one of the geometries present in the provided layers. * \since QGIS 1.5 + * \deprecated QGIS 3.34 */ Q_DECL_DEPRECATED int avoidIntersections( const QList &avoidIntersectionsLayers, - const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ); + const QHash > &ignoreFeatures SIP_PYARGREMOVE = ( QHash >() ) ) SIP_DEPRECATED; /** * Modifies geometry to avoid intersections with the layers specified in project properties From 657e65dc6127905d0ded315383aba2c84f6cdf93 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Mon, 25 Sep 2023 11:15:16 +0200 Subject: [PATCH 082/151] fix remove/rename Arcgis connections in browser (#54725) fixes #54718 fixes #53321 --- src/providers/arcgisrest/qgsarcgisrestdataitemguiprovider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/arcgisrest/qgsarcgisrestdataitemguiprovider.cpp b/src/providers/arcgisrest/qgsarcgisrestdataitemguiprovider.cpp index b7b3cbf813bb..45e3c312a1b1 100644 --- a/src/providers/arcgisrest/qgsarcgisrestdataitemguiprovider.cpp +++ b/src/providers/arcgisrest/qgsarcgisrestdataitemguiprovider.cpp @@ -169,7 +169,7 @@ void QgsArcGisRestDataItemGuiProvider::deleteConnection( QgsDataItem *item ) QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) return; - QgsOwsConnection::deleteConnection( QStringLiteral( "arcgisfeatureserver" ), item->name() ); + QgsArcGisConnectionSettings::sTreeConnectionArcgis->deleteItem( item->name() ); // the parent should be updated if ( item->parent() ) From 6ca67d8c05fb27538654480a094aa6df4798926c Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 26 Jul 2023 19:59:11 +0300 Subject: [PATCH 083/151] custom widget for raster calculator expressions --- src/gui/CMakeLists.txt | 2 + ...singrastercalculatorexpressionlineedit.cpp | 354 ++++++++++++++++++ ...essingrastercalculatorexpressionlineedit.h | 183 +++++++++ ...ingrastercalculatorexpressiondialogbase.ui | 305 +++++++++++++++ 4 files changed, 844 insertions(+) create mode 100644 src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp create mode 100644 src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h create mode 100644 src/ui/processing/qgsprocessingrastercalculatorexpressiondialogbase.ui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 0d0183af4c5d..016da19d4b83 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -393,6 +393,7 @@ set(QGIS_GUI_SRCS processing/qgsprocessingparameterdefinitionwidget.cpp processing/qgsprocessingparameterswidget.cpp processing/qgsprocessingpointcloudexpressionlineedit.cpp + processing/qgsprocessingrastercalculatorexpressionlineedit.cpp processing/qgsprocessingrecentalgorithmlog.cpp processing/qgsprocessingtininputlayerswidget.cpp processing/qgsprocessingtoolboxmodel.cpp @@ -1341,6 +1342,7 @@ set(QGIS_GUI_HDRS processing/qgsprocessingparameterdefinitionwidget.h processing/qgsprocessingparameterswidget.h processing/qgsprocessingpointcloudexpressionlineedit.h + processing/qgsprocessingrastercalculatorexpressionlineedit.h processing/qgsprocessingrecentalgorithmlog.h processing/qgsprocessingtininputlayerswidget.h processing/qgsprocessingtoolboxmodel.h diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp new file mode 100644 index 000000000000..a694ec91c575 --- /dev/null +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp @@ -0,0 +1,354 @@ +/*************************************************************************** + qgsprocessingrastercalculatorexpressionlineedit.cpp + --------------------- + begin : July 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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 "qgsprocessingrastercalculatorexpressionlineedit.h" +#include "qgsgui.h" +#include "qgsapplication.h" +#include "qgsfilterlineedit.h" +#include "qgsmaplayer.h" +//#include "qgsrastercalculator.h" + +#include +#include +#include + +/// @cond PRIVATE + +QgsProcessingRasterCalculatorExpressionLineEdit::QgsProcessingRasterCalculatorExpressionLineEdit( QWidget *parent ) + : QWidget( parent ) +{ + mLineEdit = new QgsFilterLineEdit(); + mLineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); + + mButton = new QToolButton(); + mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); + mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpression.svg" ) ) ); + connect( mButton, &QAbstractButton::clicked, this, &QgsProcessingRasterCalculatorExpressionLineEdit::editExpression ); + + QHBoxLayout *layout = new QHBoxLayout(); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( mLineEdit ); + layout->addWidget( mButton ); + setLayout( layout ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum ); + setFocusProxy( mLineEdit ); + connect( mLineEdit, &QLineEdit::textChanged, this, static_cast < void ( QgsProcessingRasterCalculatorExpressionLineEdit::* )( const QString & ) > ( &QgsProcessingRasterCalculatorExpressionLineEdit::expressionEdited ) ); + + setExpression( expression() ); +} + +QgsProcessingRasterCalculatorExpressionLineEdit::~QgsProcessingRasterCalculatorExpressionLineEdit() = default; + +void QgsProcessingRasterCalculatorExpressionLineEdit::setLayers( QList layers ) +{ + mLayers = layers; +} + +QList QgsProcessingRasterCalculatorExpressionLineEdit::layers() const +{ + return mLayers; +} + +QString QgsProcessingRasterCalculatorExpressionLineEdit::expression() const +{ + if ( mLineEdit ) + return mLineEdit->text(); + + return QString(); +} + +void QgsProcessingRasterCalculatorExpressionLineEdit::setExpression( const QString &newExpression ) +{ + if ( mLineEdit ) + mLineEdit->setText( newExpression ); +} + +void QgsProcessingRasterCalculatorExpressionLineEdit::editExpression() +{ + const QString currentExpression = expression(); + QgsProcessingRasterCalculatorExpressionDialog dlg( mLayers ); + dlg.setExpression( currentExpression ); + + if ( dlg.exec() ) + { + const QString newExpression = dlg.expression(); + setExpression( newExpression ); + } +} + +void QgsProcessingRasterCalculatorExpressionLineEdit::expressionEdited() +{ + emit expressionChanged( expression() ); +} + +void QgsProcessingRasterCalculatorExpressionLineEdit::expressionEdited( const QString &expression ) +{ + emit expressionChanged( expression ); +} + + +QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpressionDialog( QList layers, const QString &startExpression, QWidget *parent ) + : QDialog( parent ) + , mLayers( layers ) + , mInitialText( startExpression ) +{ + setupUi( this ); + QgsGui::enableAutoGeometryRestore( this ); + + populateLayers(); + + connect( mLayersList, &QListWidget::itemDoubleClicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mLayersList_itemDoubleClicked ); + //connect( mExpressionTextEdit, &QTextEdit::textChanged, this, &QgsProcessingRasterCalculatorExpressionDialog::mExpressionTextEdit_textChanged ); + + connect( mBtnPlus, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnPlus_clicked ); + connect( mBtnMinus, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnMinus_clicked ); + connect( mBtnMultiply, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnMultiply_clicked ); + connect( mBtnDivide, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnDivide_clicked ); + connect( mBtnPower, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnPower_clicked ); + connect( mBtnSqrt, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnSqrt_clicked ); + connect( mBtnOpenBracket, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnOpenBracket_clicked ); + connect( mBtnCloseBracket, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnCloseBracket_clicked ); + connect( mBtnGreater, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnGreater_clicked ); + connect( mBtnGreaterEqual, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnGreaterEqual_clicked ); + connect( mBtnLess, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnLess_clicked ); + connect( mBtnLessEqual, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnLessEqual_clicked ); + connect( mBtnEqual, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnEqual_clicked ); + connect( mBtnNotEqual, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnNotEqual_clicked ); + connect( mBtnAnd, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnAnd_clicked ); + connect( mBtnOr, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnOr_clicked ); + connect( mBtnIf, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnIf_clicked ); + connect( mBtnMin, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnMin_clicked ); + connect( mBtnMax, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnMax_clicked ); + connect( mBtnAbs, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnAbs_clicked ); + connect( mBtnSin, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnSin_clicked ); + connect( mBtnCos, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnCos_clicked ); + connect( mBtnTan, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnTan_clicked ); + connect( mBtnLog, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnLog_clicked ); + connect( mBtnAsin, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnAsin_clicked ); + connect( mBtnAcos, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnAcos_clicked ); + connect( mBtnAtan, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnAtan_clicked ); + connect( mBtnLn, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnLn_clicked ); + + mExpressionTextEdit->setPlainText( mInitialText ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::setExpression( const QString &text ) +{ + mExpressionTextEdit->setPlainText( text ); +} + +QString QgsProcessingRasterCalculatorExpressionDialog::expression() +{ + return mExpressionTextEdit->toPlainText(); +} + +void QgsProcessingRasterCalculatorExpressionDialog::populateLayers() +{ + if ( mLayers.isEmpty() ) + { + return; + } + + for ( const QgsMapLayer *layer : mLayers ) + { + QListWidgetItem *item = new QListWidgetItem( layer->name(), mLayersList ); + item->setData( Qt::ToolTipRole, layer->source() ); + mLayersList->addItem( item ); + } +} +/* +bool QgsProcessingRasterCalculatorExpressionDialog::expressionValid() const +{ + QString errorString; + QgsRasterCalcNode *testNode = QgsRasterCalcNode::parseRasterCalcString( mExpressionTextEdit->toPlainText(), errorString ); + if ( testNode ) + { + delete testNode; + return true; + } + return false; +} +*/ +QString QgsProcessingRasterCalculatorExpressionDialog::quoteBandEntry( const QString &layerName ) +{ + // '"' -> '\\"' + QString quotedName = layerName; + quotedName.replace( '\"', QLatin1String( "\\\"" ) ); + quotedName.append( '\"' ); + quotedName.prepend( '\"' ); + return quotedName; +} + +void QgsProcessingRasterCalculatorExpressionDialog::mLayersList_itemDoubleClicked( QListWidgetItem *item ) +{ + mExpressionTextEdit->insertPlainText( quoteBandEntry( item->text() ) ); +} +/* +void QgsProcessingRasterCalculatorExpressionDialog::mExpressionTextEdit_textChanged() +{ + if ( expressionValid() ) + { + mExpressionValidLabel->setText( tr( "Expression valid" ) ); + } + else + { + mExpressionValidLabel->setText( tr( "Expression invalid" ) ); + } +} +*/ +void QgsProcessingRasterCalculatorExpressionDialog::mBtnPlus_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " + " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnMinus_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " - " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnMultiply_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " * " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnDivide_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " / " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnPower_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " ^ " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnSqrt_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " sqrt ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnOpenBracket_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnCloseBracket_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " ) " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnGreater_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " > " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnGreaterEqual_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " >= " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnLess_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " < " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnLessEqual_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " <= " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnEqual_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " = " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnNotEqual_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " != " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnAnd_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " AND " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnOr_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " OR " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnIf_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " if ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnMin_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " MIN ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnMax_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " MAX ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnAbs_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " ABS ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnSin_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " sin ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnCos_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " cos ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnTan_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " tan ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnLog_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " log10 ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnAsin_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " asin ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnAcos_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " acos ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnAtan_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " atan ( " ) ); +} + +void QgsProcessingRasterCalculatorExpressionDialog::mBtnLn_clicked() +{ + mExpressionTextEdit->insertPlainText( QStringLiteral( " ln ( " ) ); +} + +///@endcond diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h new file mode 100644 index 000000000000..7ad0f1b8013b --- /dev/null +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h @@ -0,0 +1,183 @@ +/*************************************************************************** + qgsprocessingrastercalculatorexpressionlineedit.h + --------------------- + begin : July 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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. * + * * + ***************************************************************************/ + +#ifndef QGSPROCESSINGRASTERCALCULATOREXPRESSIONLINEEDIT_H +#define QGSPROCESSINGRASTERCALCULATOREXPRESSIONLINEEDIT_H + +#define SIP_NO_FILE + +#include +#include + +#include "qgis.h" +#include "qgis_gui.h" +#include "ui_qgsprocessingrastercalculatorexpressiondialogbase.h" + + +class QgsFilterLineEdit; +class QToolButton; +class QgsMapLayer; + +/// @cond PRIVATE + +/** + * Processing raster calculator expression line edit. + * \ingroup gui + * \class QgsProcessingRasterCalculatorExpressionLineEdit + * \warning Not part of stable API and may change in future QGIS releases. + * \since QGIS 3.34 + */ +class GUI_EXPORT QgsProcessingRasterCalculatorExpressionLineEdit : public QWidget +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsProcessingRasterCalculatorExpressionLineEdit. + * \param parent parent widget + */ + explicit QgsProcessingRasterCalculatorExpressionLineEdit( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + ~QgsProcessingRasterCalculatorExpressionLineEdit() override; + + /** + * Sets a layers associated with the widget. + */ + void setLayers( QList layers ); + + /** + * Returns the layers currently associated with the widget. + * \see setLayers() + */ + QList layers() const; + + /** + * Returns the current expression shown in the widget. + * \see setExpression() + */ + QString expression() const; + + signals: + + /** + * Emitted when the expression is changed. + * \param expression new expression + */ + void expressionChanged( const QString &expression ); + + public slots: + + /** + * Sets the current expression to show in the widget. + * \param expression expression string + * \see expression() + */ + void setExpression( const QString &expression ); + + private slots: + + void expressionEdited( const QString &expression ); + void expressionEdited(); + + //! Opens the expression editor dialog to edit the current expression + void editExpression(); + + private: + QgsFilterLineEdit *mLineEdit = nullptr; + QToolButton *mButton = nullptr; + QList mLayers; +}; + +/** + * A dialog for editing point cloud expressions. + * \ingroup gui + * \warning Not part of stable API and may change in future QGIS releases. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsProcessingRasterCalculatorExpressionDialog : public QDialog, private Ui::QgsProcessingRasterCalculatorExpressionDialogBase +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsProcessingRasterCalculatorExpressionDialog. + */ + QgsProcessingRasterCalculatorExpressionDialog( QList layers, const QString &startExpression = QString(), QWidget *parent = nullptr ); + + /** + * Sets the current expression to show in the widget. + * \param text expression string + * \see expression() + */ + void setExpression( const QString &text ); + + /** + * Returns the current expression shown in the widget. + * \see setExpression() + */ + QString expression(); + + private slots: + void mLayersList_itemDoubleClicked( QListWidgetItem *item ); + //void mExpressionTextEdit_textChanged(); + + //calculator buttons + void mBtnPlus_clicked(); + void mBtnMinus_clicked(); + void mBtnMultiply_clicked(); + void mBtnDivide_clicked(); + void mBtnPower_clicked(); + void mBtnSqrt_clicked(); + void mBtnOpenBracket_clicked(); + void mBtnCloseBracket_clicked(); + void mBtnGreater_clicked(); + void mBtnGreaterEqual_clicked(); + void mBtnLess_clicked(); + void mBtnLessEqual_clicked(); + void mBtnEqual_clicked(); + void mBtnNotEqual_clicked(); + void mBtnAnd_clicked(); + void mBtnOr_clicked(); + void mBtnIf_clicked(); + void mBtnMin_clicked(); + void mBtnMax_clicked(); + void mBtnAbs_clicked(); + void mBtnSin_clicked(); + void mBtnCos_clicked(); + void mBtnTan_clicked(); + void mBtnLog_clicked(); + void mBtnAsin_clicked(); + void mBtnAcos_clicked(); + void mBtnAtan_clicked(); + void mBtnLn_clicked(); + + private: + //! Returns true if raster calculator expression has valid syntax + //bool expressionValid() const; + + //! Populate the layer list + void populateLayers(); + + static QString quoteBandEntry( const QString &layerName ); + + QList mLayers; + const QString mInitialText; +}; + +///@endcond +#endif // QGSPROCESSINGRASTERCALCULATOREXPRESSIONLINEEDIT_H diff --git a/src/ui/processing/qgsprocessingrastercalculatorexpressiondialogbase.ui b/src/ui/processing/qgsprocessingrastercalculatorexpressiondialogbase.ui new file mode 100644 index 000000000000..40158795a82b --- /dev/null +++ b/src/ui/processing/qgsprocessingrastercalculatorexpressiondialogbase.ui @@ -0,0 +1,305 @@ + + + QgsProcessingRasterCalculatorExpressionDialogBase + + + + 0 + 0 + 714 + 318 + + + + Raster Calculator Expression + + + + + + Qt::Horizontal + + + + + + + Layers + + + + + + + + + + + Operators + + + + + + ( + + + + + + + >= + + + + + + + tan + + + + + + + != + + + + + + + sin + + + + + + + sqrt + + + + + + + asin + + + + + + + < + + + + + + + cos + + + + + + + ^ + + + + + + + - + + + + + + + + + + + + + + + AND + + + + + + + IF + + + + + + + min + + + + + + + * + + + + + + + acos + + + + + + + atan + + + + + + + > + + + + + + + max + + + + + + + = + + + + + + + / + + + + + + + ) + + + + + + + <= + + + + + + + OR + + + + + + + abs + + + + + + + log10 + + + + + + + ln + + + + + + + + + + + Raster Calculator Expression + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QgsProcessingRasterCalculatorExpressionDialogBase + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QgsProcessingRasterCalculatorExpressionDialogBase + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From 0f93479e1c775eb11aa852e5932aa2de8d8e27a3 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Thu, 27 Jul 2023 19:21:31 +0300 Subject: [PATCH 084/151] support for raster calculator expressions in expression parameter --- python/core/auto_additions/qgis.py | 3 +- python/core/auto_generated/qgis.sip.in | 1 + src/core/qgis.h | 1 + ...singrastercalculatorexpressionlineedit.cpp | 44 +------ ...essingrastercalculatorexpressionlineedit.h | 18 +-- .../qgsprocessingwidgetwrapperimpl.cpp | 116 ++++++++++++++---- .../qgsprocessingwidgetwrapperimpl.h | 3 + 7 files changed, 110 insertions(+), 76 deletions(-) diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 30ffa5bd5ef5..05a692649844 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -3759,7 +3759,8 @@ # monkey patching scoped based enum Qgis.ExpressionType.Qgis.__doc__ = "Native QGIS expression" Qgis.ExpressionType.PointCloud.__doc__ = "Point cloud expression" -Qgis.ExpressionType.__doc__ = "Expression types\n\n.. versionadded:: 3.32\n\n" + '* ``Qgis``: ' + Qgis.ExpressionType.Qgis.__doc__ + '\n' + '* ``PointCloud``: ' + Qgis.ExpressionType.PointCloud.__doc__ +Qgis.ExpressionType.RasterCalculator.__doc__ = "Raster calculator expression" +Qgis.ExpressionType.__doc__ = "Expression types\n\n.. versionadded:: 3.32\n\n" + '* ``Qgis``: ' + Qgis.ExpressionType.Qgis.__doc__ + '\n' + '* ``PointCloud``: ' + Qgis.ExpressionType.PointCloud.__doc__ + '\n' + '* ``RasterCalculator``: ' + Qgis.ExpressionType.RasterCalculator.__doc__ # -- Qgis.ExpressionType.baseClass = Qgis QgsVectorFileWriter.SymbologyExport = Qgis.FeatureSymbologyExport diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index ace323980b68..68d2e6e89301 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2162,6 +2162,7 @@ The development version { Qgis, PointCloud, + RasterCalculator, }; enum class FeatureSymbologyExport diff --git a/src/core/qgis.h b/src/core/qgis.h index b48b63e24c37..b2cb193bcde4 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3765,6 +3765,7 @@ class CORE_EXPORT Qgis { Qgis, //!< Native QGIS expression PointCloud, //!< Point cloud expression + RasterCalculator, //!< Raster calculator expression }; Q_ENUM( ExpressionType ) diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp index a694ec91c575..7e784d4dc4f3 100644 --- a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp @@ -20,7 +20,6 @@ #include "qgsapplication.h" #include "qgsfilterlineedit.h" #include "qgsmaplayer.h" -//#include "qgsrastercalculator.h" #include #include @@ -54,16 +53,11 @@ QgsProcessingRasterCalculatorExpressionLineEdit::QgsProcessingRasterCalculatorEx QgsProcessingRasterCalculatorExpressionLineEdit::~QgsProcessingRasterCalculatorExpressionLineEdit() = default; -void QgsProcessingRasterCalculatorExpressionLineEdit::setLayers( QList layers ) +void QgsProcessingRasterCalculatorExpressionLineEdit::setLayers( QVariantList layers ) { mLayers = layers; } -QList QgsProcessingRasterCalculatorExpressionLineEdit::layers() const -{ - return mLayers; -} - QString QgsProcessingRasterCalculatorExpressionLineEdit::expression() const { if ( mLineEdit ) @@ -102,7 +96,7 @@ void QgsProcessingRasterCalculatorExpressionLineEdit::expressionEdited( const QS } -QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpressionDialog( QList layers, const QString &startExpression, QWidget *parent ) +QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpressionDialog( QVariantList layers, const QString &startExpression, QWidget *parent ) : QDialog( parent ) , mLayers( layers ) , mInitialText( startExpression ) @@ -113,7 +107,6 @@ QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpr populateLayers(); connect( mLayersList, &QListWidget::itemDoubleClicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mLayersList_itemDoubleClicked ); - //connect( mExpressionTextEdit, &QTextEdit::textChanged, this, &QgsProcessingRasterCalculatorExpressionDialog::mExpressionTextEdit_textChanged ); connect( mBtnPlus, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnPlus_clicked ); connect( mBtnMinus, &QPushButton::clicked, this, &QgsProcessingRasterCalculatorExpressionDialog::mBtnMinus_clicked ); @@ -164,26 +157,13 @@ void QgsProcessingRasterCalculatorExpressionDialog::populateLayers() return; } - for ( const QgsMapLayer *layer : mLayers ) + for ( const QVariant &layer : mLayers ) { - QListWidgetItem *item = new QListWidgetItem( layer->name(), mLayersList ); - item->setData( Qt::ToolTipRole, layer->source() ); + QListWidgetItem *item = new QListWidgetItem( layer.toString(), mLayersList ); mLayersList->addItem( item ); } } -/* -bool QgsProcessingRasterCalculatorExpressionDialog::expressionValid() const -{ - QString errorString; - QgsRasterCalcNode *testNode = QgsRasterCalcNode::parseRasterCalcString( mExpressionTextEdit->toPlainText(), errorString ); - if ( testNode ) - { - delete testNode; - return true; - } - return false; -} -*/ + QString QgsProcessingRasterCalculatorExpressionDialog::quoteBandEntry( const QString &layerName ) { // '"' -> '\\"' @@ -198,19 +178,7 @@ void QgsProcessingRasterCalculatorExpressionDialog::mLayersList_itemDoubleClicke { mExpressionTextEdit->insertPlainText( quoteBandEntry( item->text() ) ); } -/* -void QgsProcessingRasterCalculatorExpressionDialog::mExpressionTextEdit_textChanged() -{ - if ( expressionValid() ) - { - mExpressionValidLabel->setText( tr( "Expression valid" ) ); - } - else - { - mExpressionValidLabel->setText( tr( "Expression invalid" ) ); - } -} -*/ + void QgsProcessingRasterCalculatorExpressionDialog::mBtnPlus_clicked() { mExpressionTextEdit->insertPlainText( QStringLiteral( " + " ) ); diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h index 7ad0f1b8013b..1472d08deead 100644 --- a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h @@ -57,13 +57,7 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionLineEdit : public QWidge /** * Sets a layers associated with the widget. */ - void setLayers( QList layers ); - - /** - * Returns the layers currently associated with the widget. - * \see setLayers() - */ - QList layers() const; + void setLayers( QVariantList layers ); /** * Returns the current expression shown in the widget. @@ -99,7 +93,7 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionLineEdit : public QWidge private: QgsFilterLineEdit *mLineEdit = nullptr; QToolButton *mButton = nullptr; - QList mLayers; + QVariantList mLayers; }; /** @@ -117,7 +111,7 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionDialog : public QDialog, /** * Constructor for QgsProcessingRasterCalculatorExpressionDialog. */ - QgsProcessingRasterCalculatorExpressionDialog( QList layers, const QString &startExpression = QString(), QWidget *parent = nullptr ); + QgsProcessingRasterCalculatorExpressionDialog( QVariantList layers, const QString &startExpression = QString(), QWidget *parent = nullptr ); /** * Sets the current expression to show in the widget. @@ -134,7 +128,6 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionDialog : public QDialog, private slots: void mLayersList_itemDoubleClicked( QListWidgetItem *item ); - //void mExpressionTextEdit_textChanged(); //calculator buttons void mBtnPlus_clicked(); @@ -167,15 +160,12 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionDialog : public QDialog, void mBtnLn_clicked(); private: - //! Returns true if raster calculator expression has valid syntax - //bool expressionValid() const; - //! Populate the layer list void populateLayers(); static QString quoteBandEntry( const QString &layerName ); - QList mLayers; + QVariantList mLayers; const QString mInitialText; }; diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index b076169eab4f..020f75226eef 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -66,6 +66,7 @@ #include "qgspointcloudattributecombobox.h" #include "qgspointcloudlayer.h" #include "qgsprocessingpointcloudexpressionlineedit.h" +#include "qgsprocessingrastercalculatorexpressionlineedit.h" #include "qgsunittypes.h" #include #include @@ -2124,10 +2125,12 @@ QgsProcessingExpressionParameterDefinitionWidget::QgsProcessingExpressionParamet mDefaultQgisLineEdit->registerExpressionContextGenerator( this ); mDefaultPointCloudLineEdit = new QgsProcessingPointCloudExpressionLineEdit(); + mDefaultRasterCalculatorLineEdit = new QgsProcessingRasterCalculatorExpressionLineEdit(); QStackedWidget *stackedWidget = new QStackedWidget(); stackedWidget->addWidget( mDefaultQgisLineEdit ); stackedWidget->addWidget( mDefaultPointCloudLineEdit ); + stackedWidget->addWidget( mDefaultRasterCalculatorLineEdit ); vlayout->addWidget( stackedWidget ); if ( const QgsProcessingParameterExpression *expParam = dynamic_cast( definition ) ) @@ -2146,6 +2149,7 @@ QgsProcessingExpressionParameterDefinitionWidget::QgsProcessingExpressionParamet mExpressionTypeComboBox = new QComboBox(); mExpressionTypeComboBox->addItem( tr( "QGIS" ), static_cast< int >( Qgis::ExpressionType::Qgis ) ); mExpressionTypeComboBox->addItem( tr( "Point Cloud" ), static_cast< int >( Qgis::ExpressionType::PointCloud ) ); + mExpressionTypeComboBox->addItem( tr( "Raster Calculator" ), static_cast< int >( Qgis::ExpressionType::RasterCalculator ) ); connect( mExpressionTypeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, [ = ]( int ) { @@ -2166,35 +2170,50 @@ QgsProcessingExpressionParameterDefinitionWidget::QgsProcessingExpressionParamet const QMap components = model->parameterComponents(); for ( auto it = components.constBegin(); it != components.constEnd(); ++it ) { - if ( exprType == Qgis::ExpressionType::Qgis ) + switch ( exprType ) { - if ( const QgsProcessingParameterFeatureSource *definition = dynamic_cast< const QgsProcessingParameterFeatureSource * >( model->parameterDefinition( it.value().parameterName() ) ) ) - { - mParentLayerComboBox-> addItem( definition->description(), definition->name() ); - if ( !initialParent.isEmpty() && initialParent == definition->name() ) + case Qgis::ExpressionType::Qgis: + if ( const QgsProcessingParameterFeatureSource *definition = dynamic_cast< const QgsProcessingParameterFeatureSource * >( model->parameterDefinition( it.value().parameterName() ) ) ) { - mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + mParentLayerComboBox-> addItem( definition->description(), definition->name() ); + if ( !initialParent.isEmpty() && initialParent == definition->name() ) + { + mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + } } - } - else if ( const QgsProcessingParameterVectorLayer *definition = dynamic_cast< const QgsProcessingParameterVectorLayer * >( model->parameterDefinition( it.value().parameterName() ) ) ) - { - mParentLayerComboBox-> addItem( definition->description(), definition->name() ); - if ( !initialParent.isEmpty() && initialParent == definition->name() ) + else if ( const QgsProcessingParameterVectorLayer *definition = dynamic_cast< const QgsProcessingParameterVectorLayer * >( model->parameterDefinition( it.value().parameterName() ) ) ) { - mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + mParentLayerComboBox-> addItem( definition->description(), definition->name() ); + if ( !initialParent.isEmpty() && initialParent == definition->name() ) + { + mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + } } - } - } - else - { - if ( const QgsProcessingParameterPointCloudLayer *definition = dynamic_cast< const QgsProcessingParameterPointCloudLayer * >( model->parameterDefinition( it.value().parameterName() ) ) ) - { - mParentLayerComboBox-> addItem( definition->description(), definition->name() ); - if ( !initialParent.isEmpty() && initialParent == definition->name() ) + break; + case Qgis::ExpressionType::PointCloud: + if ( const QgsProcessingParameterPointCloudLayer *definition = dynamic_cast< const QgsProcessingParameterPointCloudLayer * >( model->parameterDefinition( it.value().parameterName() ) ) ) { - mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + mParentLayerComboBox-> addItem( definition->description(), definition->name() ); + if ( !initialParent.isEmpty() && initialParent == definition->name() ) + { + mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + } } - } + break; + case Qgis::ExpressionType::RasterCalculator: + if ( const QgsProcessingParameterMultipleLayers *definition = dynamic_cast< const QgsProcessingParameterMultipleLayers * >( model->parameterDefinition( it.value().parameterName() ) ) ) + { + if ( definition->layerType() != QgsProcessing::SourceType::TypeRaster ) + { + continue; + } + mParentLayerComboBox-> addItem( definition->description(), definition->name() ); + if ( !initialParent.isEmpty() && initialParent == definition->name() ) + { + mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 ); + } + } + break; } } } @@ -2222,7 +2241,19 @@ QgsProcessingExpressionParameterDefinitionWidget::QgsProcessingExpressionParamet QgsProcessingParameterDefinition *QgsProcessingExpressionParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const { Qgis::ExpressionType expressionType = static_cast< Qgis::ExpressionType >( mExpressionTypeComboBox->currentData().toInt() ); - QString expression = expressionType == Qgis::ExpressionType::Qgis ? mDefaultQgisLineEdit->expression() : mDefaultPointCloudLineEdit->expression(); + QString expression; + switch ( expressionType ) + { + case Qgis::ExpressionType::Qgis: + expression = mDefaultQgisLineEdit->expression(); + break; + case Qgis::ExpressionType::PointCloud: + expression = mDefaultPointCloudLineEdit->expression(); + break; + case Qgis::ExpressionType::RasterCalculator: + expression = mDefaultRasterCalculatorLineEdit->expression(); + break; + } auto param = std::make_unique< QgsProcessingParameterExpression >( name, description, expression, mParentLayerComboBox->currentData().toString(), false, expressionType ); param->setFlags( flags ); return param.release(); @@ -2268,6 +2299,17 @@ QWidget *QgsProcessingExpressionWidgetWrapper::createWidget() return mPointCloudExpLineEdit; } + if ( expParam->expressionType() == Qgis::ExpressionType::RasterCalculator ) + { + mRasterCalculatorExpLineEdit = new QgsProcessingRasterCalculatorExpressionLineEdit(); + mRasterCalculatorExpLineEdit->setToolTip( parameterDefinition()->toolTip() ); + connect( mRasterCalculatorExpLineEdit, &QgsProcessingRasterCalculatorExpressionLineEdit::expressionChanged, this, [ = ]( const QString & ) + { + emit widgetValueHasChanged( this ); + } ); + return mRasterCalculatorExpLineEdit; + } + // native QGIS expression if ( expParam->metadata().value( QStringLiteral( "inlineEditor" ) ).toBool() ) { @@ -2428,6 +2470,30 @@ void QgsProcessingExpressionWidgetWrapper::setParentLayerWrapperValue( const Qgs if ( mPointCloudExpLineEdit ) mPointCloudExpLineEdit->setLayer( layer ); + break; + } + case Qgis::ExpressionType::RasterCalculator: + { + QList layers = QgsProcessingParameters::parameterAsLayerList( parentWrapper->parameterDefinition(), val, *context, QgsProcessing::LayerOptionsFlag::SkipIndexGeneration ); + if ( layers.isEmpty() ) + { + if ( mRasterCalculatorExpLineEdit ) + { + mRasterCalculatorExpLineEdit->setLayers( val.type() == QVariant::List ? val.toList() : QVariantList() << val ); + } + return; + } + + if ( mRasterCalculatorExpLineEdit ) + { + QVariantList layersList; + for ( QgsMapLayer *layer : layers ) + { + layersList << layer->name(); + } + mRasterCalculatorExpLineEdit->setLayers( layersList ); + } + break; } } @@ -2444,6 +2510,8 @@ void QgsProcessingExpressionWidgetWrapper::setWidgetValue( const QVariant &value mExpLineEdit->setExpression( v ); else if ( mPointCloudExpLineEdit ) mPointCloudExpLineEdit->setExpression( v ); + else if ( mRasterCalculatorExpLineEdit ) + mRasterCalculatorExpLineEdit->setExpression( v ); } QVariant QgsProcessingExpressionWidgetWrapper::widgetValue() const @@ -2456,6 +2524,8 @@ QVariant QgsProcessingExpressionWidgetWrapper::widgetValue() const return mExpLineEdit->expression(); else if ( mPointCloudExpLineEdit ) return mPointCloudExpLineEdit->expression(); + else if ( mRasterCalculatorExpLineEdit ) + return mRasterCalculatorExpLineEdit->expression(); else return QVariant(); } diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index 0b63873f44d6..3ebc019193db 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -71,6 +71,7 @@ class QgsProcessingLayerOutputDestinationWidget; class QgsCheckableComboBox; class QgsMapLayerComboBox; class QgsProcessingPointCloudExpressionLineEdit; +class QgsProcessingRasterCalculatorExpressionLineEdit; ///@cond PRIVATE @@ -700,6 +701,7 @@ class GUI_EXPORT QgsProcessingExpressionParameterDefinitionWidget : public QgsPr QComboBox *mParentLayerComboBox = nullptr; QgsExpressionLineEdit *mDefaultQgisLineEdit = nullptr; QgsProcessingPointCloudExpressionLineEdit *mDefaultPointCloudLineEdit = nullptr; + QgsProcessingRasterCalculatorExpressionLineEdit *mDefaultRasterCalculatorLineEdit = nullptr; QComboBox *mExpressionTypeComboBox = nullptr; }; @@ -746,6 +748,7 @@ class GUI_EXPORT QgsProcessingExpressionWidgetWrapper : public QgsAbstractProces QgsExpressionBuilderWidget *mExpBuilderWidget = nullptr; QgsExpressionLineEdit *mExpLineEdit = nullptr; QgsProcessingPointCloudExpressionLineEdit *mPointCloudExpLineEdit = nullptr; + QgsProcessingRasterCalculatorExpressionLineEdit *mRasterCalculatorExpLineEdit = nullptr; std::unique_ptr< QgsMapLayer > mParentLayer; friend class TestProcessingGui; From eda287d46c76040bd64b18401941ee4956a6c022 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 28 Jul 2023 08:42:15 +0300 Subject: [PATCH 085/151] add test for raster calculator expression --- tests/src/analysis/testqgsprocessing.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 5ccf8f42a853..d65c67ef0305 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -6741,6 +6741,11 @@ void TestQgsProcessing::parameterExpression() def.reset( new QgsProcessingParameterExpression( "non_optional", QString(), QString( "default" ), QString(), false, Qgis::ExpressionType::PointCloud ) ); pythonCode = def->asPythonString(); QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExpression('non_optional', '', parentLayerParameterName='', defaultValue='default', type=Qgis.ExpressionType.PointCloud)" ) ); + + // set raster calculator expression type + def.reset( new QgsProcessingParameterExpression( "non_optional", QString(), QString( "default" ), QString(), false, Qgis::ExpressionType::RasterCalculator ) ); + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterExpression('non_optional', '', parentLayerParameterName='', defaultValue='default', type=Qgis.ExpressionType.RasterCalculator)" ) ); } void TestQgsProcessing::parameterField() From 653402593fc597d7b45a8afe880274ee1cb3d54f Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 28 Jul 2023 10:20:29 +0300 Subject: [PATCH 086/151] raster calculator algorithm (WIP) --- images/images.qrc | 1 + .../algorithms/mAlgorithmRasterCalculator.svg | 1 + src/analysis/CMakeLists.txt | 1 + .../qgsalgorithmrastercalculator.cpp | 80 +++++++++++++++++++ .../processing/qgsalgorithmrastercalculator.h | 57 +++++++++++++ .../processing/qgsnativealgorithms.cpp | 2 + 6 files changed, 142 insertions(+) create mode 100644 images/themes/default/algorithms/mAlgorithmRasterCalculator.svg create mode 100644 src/analysis/processing/qgsalgorithmrastercalculator.cpp create mode 100644 src/analysis/processing/qgsalgorithmrastercalculator.h diff --git a/images/images.qrc b/images/images.qrc index cf0cd27e2c3f..dd58e4420a7a 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -127,6 +127,7 @@ themes/default/algorithms/mAlgorithmRandomPointsWithinExtent.svg themes/default/algorithms/mAlgorithmRandomPoissonRaster.svg themes/default/algorithms/mAlgorithmRandomRaster.svg + themes/default/algorithms/mAlgorithmRasterCalculator.svg themes/default/algorithms/mAlgorithmRegularPoints.svg themes/default/algorithms/mAlgorithmRoundRastervalues.svg themes/default/algorithms/mAlgorithmSelectLocation.svg diff --git a/images/themes/default/algorithms/mAlgorithmRasterCalculator.svg b/images/themes/default/algorithms/mAlgorithmRasterCalculator.svg new file mode 100644 index 000000000000..13d02247e754 --- /dev/null +++ b/images/themes/default/algorithms/mAlgorithmRasterCalculator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index cf0d76e3969c..54cb6ccd4fd0 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -168,6 +168,7 @@ set(QGIS_ANALYSIS_SRCS processing/qgsalgorithmrandompointsinpolygons.cpp processing/qgsalgorithmrandompointsonlines.cpp processing/qgsalgorithmrandomraster.cpp + processing/qgsalgorithmrastercalculator.cpp processing/qgsalgorithmrasterdtmslopebasedfilter.cpp processing/qgsalgorithmrasterfrequencybycomparisonoperator.cpp processing/qgsalgorithmrasterlayerproperties.cpp diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp new file mode 100644 index 000000000000..a33b9040177b --- /dev/null +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** + qgsalgorithmrastercalculator.cpp + --------------------- + begin : July 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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 "qgsalgorithmrastercalculator.h" +#include "qgsrasterfilewriter.h" + +///@cond PRIVATE + +QString QgsRasterCalculatorAlgorithm::name() const +{ + return QStringLiteral( "rastercalc" ); +} + +QString QgsRasterCalculatorAlgorithm::displayName() const +{ + return QObject::tr( "Raster calculator" ); +} + +QStringList QgsRasterCalculatorAlgorithm::tags() const +{ + return QObject::tr( "raster,calculator" ).split( ',' ); +} + +QString QgsRasterCalculatorAlgorithm::group() const +{ + return QObject::tr( "Raster analysis" ); +} + +QString QgsRasterCalculatorAlgorithm::groupId() const +{ + return QStringLiteral( "rasteranalysis" ); +} + +QString QgsRasterCalculatorAlgorithm::shortHelpString() const +{ + return QObject::tr( "Performing algebraic operations using raster layers." ); +} + +QgsRasterCalculatorAlgorithm *QgsRasterCalculatorAlgorithm::createInstance() const +{ + return new QgsRasterCalculatorAlgorithm(); +} + +void QgsRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); + addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ) ); + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ) ); + addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ) ); + addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Calculated" ) ) ); +} + +QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ); + const QFileInfo fi( outputFile ); + const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() ); + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), outputFile ); + return outputs; +} + +///@endcond + diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.h b/src/analysis/processing/qgsalgorithmrastercalculator.h new file mode 100644 index 000000000000..3c088e4478e8 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmrastercalculator.h @@ -0,0 +1,57 @@ +/*************************************************************************** + qgsalgorithmrastercalculator.h + --------------------- + begin : July 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMRASTERCALCULATOR_H +#define QGSALGORITHMRASTERCALCULATOR_H + +#define SIP_NO_FILE + +#include "qgis_sip.h" +#include "qgsprocessingalgorithm.h" +#include "qgsapplication.h" + +///@cond PRIVATE + +/** + * Native raster calculator algorithm. + */ +class QgsRasterCalculatorAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsRasterCalculatorAlgorithm() = default; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } + QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QgsRasterCalculatorAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMRASTERCALCULATOR_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 6e64d82366b2..97b78498f1e2 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -152,6 +152,7 @@ #include "qgsalgorithmrandompointsinpolygons.h" #include "qgsalgorithmrandompointsonlines.h" #include "qgsalgorithmrandomraster.h" +#include "qgsalgorithmrastercalculator.h" #include "qgsalgorithmrasterdtmslopebasedfilter.h" #include "qgsalgorithmrasterfrequencybycomparisonoperator.h" #include "qgsalgorithmrasterlayerproperties.h" @@ -440,6 +441,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsRandomPointsOnLinesAlgorithm() ); addAlgorithm( new QgsRandomPoissonRasterAlgorithm() ); addAlgorithm( new QgsRandomUniformRasterAlgorithm() ); + addAlgorithm( new QgsRasterCalculatorAlgorithm() ); addAlgorithm( new QgsRasterDtmSlopeBasedFilterAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByEqualOperatorAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByGreaterThanOperatorAlgorithm() ); From 6ff700ec4fb71dfbbdedee05eb0ad0e900ad1ab7 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 31 Jul 2023 15:21:59 +0300 Subject: [PATCH 087/151] finalize raster calculator algorithm --- .../qgsalgorithmrastercalculator.cpp | 112 ++++++++++++++++++ .../processing/qgsalgorithmrastercalculator.h | 5 + 2 files changed, 117 insertions(+) diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp index a33b9040177b..90efdbf52152 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -17,6 +17,7 @@ #include "qgsalgorithmrastercalculator.h" #include "qgsrasterfilewriter.h" +#include "qgsrastercalculator.h" ///@cond PRIVATE @@ -65,12 +66,123 @@ void QgsRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Calculated" ) ) ); } +bool QgsRasterCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context ); + + for ( const QgsMapLayer *layer : std::as_const( layers ) ) + { + QgsMapLayer *clonedLayer { layer->clone() }; + mLayers << clonedLayer; + } + + if ( mLayers.isEmpty() ) + { + feedback->reportError( QObject::tr( "No layers selected" ), false ); + return false; + } + + return true; +} + + QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { + QgsCoordinateReferenceSystem crs; + if ( parameters.value( QStringLiteral( "CRS" ) ).isValid() ) + { + crs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context ); + } + else + { + crs = mLayers.at( 0 )->crs(); + } + + QgsRectangle bbox; + if ( parameters.value( QStringLiteral( "EXTENT" ) ).isValid() ) + { + bbox = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs ); + } + else + { + bbox = QgsProcessingUtils::combineLayerExtents( mLayers, crs, context ); + } + + double minCellSize = 1e9; + + QVector< QgsRasterCalculatorEntry > entries; + for ( QgsMapLayer *layer : mLayers ) + { + QgsRasterLayer *rLayer = static_cast( layer ); + if ( !rLayer ) + { + continue; + } + + const int nBands = rLayer->dataProvider()->bandCount(); + for ( int i = 0; i < nBands; ++i ) + { + QgsRasterCalculatorEntry entry; + entry.ref = QStringLiteral( "%1@%2" ).arg( rLayer->name() ).arg( i + 1 ); + entry.raster = rLayer; + entry.bandNumber = i + 1; + entries << entry; + } + + QgsRectangle ext = rLayer->extent(); + if ( rLayer->crs().authid() != crs.authid() ) + { + QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); + ext = ct.transformBoundingBox( ext ); + } + + double cellSize = ( ext.xMaximum() - ext.xMinimum() ) / rLayer->width(); + if ( cellSize < minCellSize ) + { + minCellSize = cellSize; + } + } + + double cellSize = parameterAsDouble( parameters, QStringLiteral( "CELL_SIZE" ), context ); + if ( cellSize == 0 ) + { + cellSize = minCellSize; + } + + const QString expression = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ); const QFileInfo fi( outputFile ); const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() ); + double width = std::round( ( bbox.xMaximum() - bbox.xMinimum() ) / cellSize ); + double height = std::round( ( bbox.yMaximum() - bbox.yMinimum() ) / cellSize ); + + QgsRasterCalculator calc( expression, outputFile, outputFormat, bbox, crs, width, height, entries, context.transformContext() ); + QgsRasterCalculator::Result result = calc.processCalculation( feedback ); + switch ( result ) + { + case QgsRasterCalculator::CreateOutputError: + throw QgsProcessingException( QObject::tr( "Error creating output file." ) ); + break; + case QgsRasterCalculator::InputLayerError: + throw QgsProcessingException( QObject::tr( "Error reading input layer." ) ); + break; + case QgsRasterCalculator::ParserError: + throw QgsProcessingException( QObject::tr( "Error parsing formula." ) ); + break; + case QgsRasterCalculator::MemoryError: + throw QgsProcessingException( QObject::tr( "Error allocating memory for result." ) ); + break; + case QgsRasterCalculator::BandError: + throw QgsProcessingException( QObject::tr( "Invalid band number for input." ) ); + break; + case QgsRasterCalculator::CalculationError: + throw QgsProcessingException( QObject::tr( "Error occurred while performing calculation." ) ); + break; + default: + break; + } + QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), outputFile ); return outputs; diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.h b/src/analysis/processing/qgsalgorithmrastercalculator.h index 3c088e4478e8..469c36101b81 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.h +++ b/src/analysis/processing/qgsalgorithmrastercalculator.h @@ -48,8 +48,13 @@ class QgsRasterCalculatorAlgorithm : public QgsProcessingAlgorithm protected: + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + QList< QgsMapLayer * > mLayers; }; ///@endcond PRIVATE From fae27f2bb4a16be2c5dda254e2334f039e1ff0b1 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 1 Aug 2023 12:08:44 +0300 Subject: [PATCH 088/151] add algorithm to create virtual raster --- src/analysis/CMakeLists.txt | 1 + .../qgsalgorithmvirtualrastercalculator.cpp | 177 ++++++++++++++++++ .../qgsalgorithmvirtualrastercalculator.h | 61 ++++++ .../processing/qgsnativealgorithms.cpp | 2 + 4 files changed, 241 insertions(+) create mode 100644 src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp create mode 100644 src/analysis/processing/qgsalgorithmvirtualrastercalculator.h diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 54cb6ccd4fd0..068a93d4a1d2 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -238,6 +238,7 @@ set(QGIS_ANALYSIS_SRCS processing/qgsalgorithmunion.cpp processing/qgsalgorithmuniquevalueindex.cpp processing/qgsalgorithmvectorize.cpp + processing/qgsalgorithmvirtualrastercalculator.cpp processing/qgsalgorithmwedgebuffers.cpp processing/qgsalgorithmwritevectortiles.cpp processing/qgsalgorithmxyztiles.cpp diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp new file mode 100644 index 000000000000..46759033e53d --- /dev/null +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp @@ -0,0 +1,177 @@ +/*************************************************************************** + qgsalgorithmvirtualrastercalculator.cpp + --------------------- + begin : August 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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 "qgsalgorithmvirtualrastercalculator.h" +#include "qgsrasterdataprovider.h" + +///@cond PRIVATE + +QgsProcessingAlgorithm::Flags QgsVirtualRasterCalculatorAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagNoThreading; +} + +QString QgsVirtualRasterCalculatorAlgorithm::name() const +{ + return QStringLiteral( "virtualrastercalc" ); +} + +QString QgsVirtualRasterCalculatorAlgorithm::displayName() const +{ + return QObject::tr( "Raster calculator (virtual)" ); +} + +QStringList QgsVirtualRasterCalculatorAlgorithm::tags() const +{ + return QObject::tr( "raster,calculator,virtual" ).split( ',' ); +} + +QString QgsVirtualRasterCalculatorAlgorithm::group() const +{ + return QObject::tr( "Raster analysis" ); +} + +QString QgsVirtualRasterCalculatorAlgorithm::groupId() const +{ + return QStringLiteral( "rasteranalysis" ); +} + +QString QgsVirtualRasterCalculatorAlgorithm::shortHelpString() const +{ + return QObject::tr( "Performs algebraic operations using raster layers and generates in-memory result." ); +} + +QgsVirtualRasterCalculatorAlgorithm *QgsVirtualRasterCalculatorAlgorithm::createInstance() const +{ + return new QgsVirtualRasterCalculatorAlgorithm(); +} + +void QgsVirtualRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); + addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ) ); + addParameter( new QgsProcessingParameterNumber( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ) ); + addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ) ); + addParameter( new QgsProcessingParameterString( QStringLiteral( "LAYER_NAME" ), QObject::tr( "Output layer name" ), QVariant(), false, true ) ); + addOutput( new QgsProcessingOutputRasterLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Calculated" ) ) ); +} + +QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_UNUSED( feedback ); + + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context ); + if ( layers.isEmpty() ) + { + throw QgsProcessingException( QObject::tr( "No input layers selected" ) ); + } + + QgsCoordinateReferenceSystem crs; + if ( parameters.value( QStringLiteral( "CRS" ) ).isValid() ) + { + crs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context ); + } + else + { + crs = layers.at( 0 )->crs(); + } + + QgsRectangle bbox; + if ( parameters.value( QStringLiteral( "EXTENT" ) ).isValid() ) + { + bbox = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs ); + } + else + { + bbox = QgsProcessingUtils::combineLayerExtents( layers, crs, context ); + } + + double minCellSize = 1e9; + QgsRasterDataProvider::VirtualRasterParameters rasterParameters; + + for ( const QgsMapLayer *layer : layers ) + { + const QgsRasterLayer *rLayer = static_cast( layer ); + if ( !rLayer ) + { + continue; + } + + QgsRasterDataProvider::VirtualRasterInputLayers rasterLayer; + rasterLayer.name = rLayer->name(); + rasterLayer.provider = rLayer->dataProvider()->name(); + rasterLayer.uri = rLayer->source(); + rasterParameters.rInputLayers.append( rasterLayer ); + + QgsRectangle ext = rLayer->extent(); + if ( rLayer->crs().authid() != crs.authid() ) + { + QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); + ext = ct.transformBoundingBox( ext ); + } + + double cellSize = ( ext.xMaximum() - ext.xMinimum() ) / rLayer->width(); + if ( cellSize < minCellSize ) + { + minCellSize = cellSize; + } + } + + double cellSize = parameterAsDouble( parameters, QStringLiteral( "CELL_SIZE" ), context ); + if ( cellSize == 0 ) + { + cellSize = minCellSize; + } + + const QString expression = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + QString layerName = parameterAsString( parameters, QStringLiteral( "LAYER_NAME" ), context ); + if ( layerName.isEmpty() ) + { + layerName = expression; + } + + double width = std::round( ( bbox.xMaximum() - bbox.xMinimum() ) / cellSize ); + double height = std::round( ( bbox.yMaximum() - bbox.yMinimum() ) / cellSize ); + + rasterParameters.crs = crs; + rasterParameters.extent = bbox; + rasterParameters.width = width; + rasterParameters.height = height; + rasterParameters.formula = expression; + + std::unique_ptr< QgsRasterLayer > layer; + layer = std::make_unique< QgsRasterLayer >( QgsRasterDataProvider::encodeVirtualRasterProviderUri( rasterParameters ), + layerName, QStringLiteral( "virtualraster" ) ); + if ( !layer->isValid() ) + { + feedback->reportError( QObject::tr( "Failed to create virtual raster layer" ) ); + } + else + { + } + const QString layerId = layer->id(); + const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT" ), QgsProcessingUtils::LayerHint::Raster ); + context.addLayerToLoadOnCompletion( layerId, details ); + context.temporaryLayerStore()->addMapLayer( layer.release() ); + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), layerId ); + return outputs; +} + +///@endcond diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h new file mode 100644 index 000000000000..84448d5c1e7a --- /dev/null +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h @@ -0,0 +1,61 @@ +/*************************************************************************** + qgsalgorithmvirtualrastercalculator.h + --------------------- + begin : August 2023 + copyright : (C) 2023 by Alexander Bruy + email : alexander dot bruy 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. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMVIRTUALRASTERCALCULATOR_H +#define QGSALGORITHMVIRTUALRASTERCALCULATOR_H + +#define SIP_NO_FILE + +#include "qgis_sip.h" +#include "qgsprocessingalgorithm.h" +#include "qgsapplication.h" + +///@cond PRIVATE + +/** + * Native virtual raster calculator algorithm. + */ +class QgsVirtualRasterCalculatorAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsVirtualRasterCalculatorAlgorithm() = default; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } + QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } + Flags flags() const override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QgsVirtualRasterCalculatorAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + QList< QgsMapLayer * > mLayers; +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMVIRTUALRASTERCALCULATOR_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 97b78498f1e2..a47107062d09 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -222,6 +222,7 @@ #include "qgsalgorithmunion.h" #include "qgsalgorithmuniquevalueindex.h" #include "qgsalgorithmvectorize.h" +#include "qgsalgorithmvirtualrastercalculator.h" #include "qgsalgorithmwedgebuffers.h" #include "qgsalgorithmwritevectortiles.h" #include "qgsalgorithmxyztiles.h" @@ -523,6 +524,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsTruncateTableAlgorithm() ); addAlgorithm( new QgsUnionAlgorithm() ); addAlgorithm( new QgsVariableWidthBufferByMAlgorithm() ); + addAlgorithm( new QgsVirtualRasterCalculatorAlgorithm() ); addAlgorithm( new QgsWedgeBuffersAlgorithm() ); addAlgorithm( new QgsWriteVectorTilesXyzAlgorithm() ); addAlgorithm( new QgsWriteVectorTilesMbtilesAlgorithm() ); From 8d52b54f3374751e28ed70de185c99b62c271427 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 15 Sep 2023 06:53:08 +0300 Subject: [PATCH 089/151] address review --- python/core/auto_additions/qgis.py | 2 +- .../qgsalgorithmrastercalculator.cpp | 30 ++++++++++++------- .../qgsalgorithmvirtualrastercalculator.cpp | 14 ++++++--- .../processing/qgsprocessingparameters.cpp | 17 +++++++---- src/core/qgis.h | 2 +- ...singrastercalculatorexpressionlineedit.cpp | 4 +-- ...essingrastercalculatorexpressionlineedit.h | 4 +-- 7 files changed, 47 insertions(+), 26 deletions(-) diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 05a692649844..2ccec75f1de5 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -3759,7 +3759,7 @@ # monkey patching scoped based enum Qgis.ExpressionType.Qgis.__doc__ = "Native QGIS expression" Qgis.ExpressionType.PointCloud.__doc__ = "Point cloud expression" -Qgis.ExpressionType.RasterCalculator.__doc__ = "Raster calculator expression" +Qgis.ExpressionType.RasterCalculator.__doc__ = "Raster calculator expression (since QGIS 3.34)" Qgis.ExpressionType.__doc__ = "Expression types\n\n.. versionadded:: 3.32\n\n" + '* ``Qgis``: ' + Qgis.ExpressionType.Qgis.__doc__ + '\n' + '* ``PointCloud``: ' + Qgis.ExpressionType.PointCloud.__doc__ + '\n' + '* ``RasterCalculator``: ' + Qgis.ExpressionType.RasterCalculator.__doc__ # -- Qgis.ExpressionType.baseClass = Qgis diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp index 90efdbf52152..d3729fac4681 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -58,11 +58,18 @@ QgsRasterCalculatorAlgorithm *QgsRasterCalculatorAlgorithm::createInstance() con void QgsRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) { + addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); - addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ) ); - addParameter( new QgsProcessingParameterNumber( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ) ); - addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ) ); + std::unique_ptr extentParam = std::make_unique( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ); + extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) ); + addParameter( extentParam.release() ); + std::unique_ptr cellSizeParam = std::make_unique( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ); + cellSizeParam->setHelp( QObject::tr( "Cell size of the output layer. If not specified, the smallest cell size from the input layers will be used" ) ); + addParameter( cellSizeParam.release() ); + std::unique_ptr crsParam = std::make_unique( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ); + crsParam->setHelp( QObject::tr( "CRS of the output layer. If not specified, the CRS of the first input layer will be used" ) ); + addParameter( crsParam.release() ); addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Calculated" ) ) ); } @@ -72,7 +79,9 @@ bool QgsRasterCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶mete for ( const QgsMapLayer *layer : std::as_const( layers ) ) { + QgsMapLayer *clonedLayer { layer->clone() }; + clonedLayer->moveToThread( nullptr ); mLayers << clonedLayer; } @@ -88,6 +97,11 @@ bool QgsRasterCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶mete QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { + for ( QgsMapLayer *layer : std::as_const( mLayers ) ) + { + layer->moveToThread( QThread::currentThread() ); + } + QgsCoordinateReferenceSystem crs; if ( parameters.value( QStringLiteral( "CRS" ) ).isValid() ) { @@ -130,7 +144,7 @@ QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap &p } QgsRectangle ext = rLayer->extent(); - if ( rLayer->crs().authid() != crs.authid() ) + if ( rLayer->crs() != crs ) { QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); ext = ct.transformBoundingBox( ext ); @@ -159,26 +173,22 @@ QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap &p QgsRasterCalculator calc( expression, outputFile, outputFormat, bbox, crs, width, height, entries, context.transformContext() ); QgsRasterCalculator::Result result = calc.processCalculation( feedback ); + qDeleteAll( mLayers ); + mLayers.clear(); switch ( result ) { case QgsRasterCalculator::CreateOutputError: throw QgsProcessingException( QObject::tr( "Error creating output file." ) ); - break; case QgsRasterCalculator::InputLayerError: throw QgsProcessingException( QObject::tr( "Error reading input layer." ) ); - break; case QgsRasterCalculator::ParserError: throw QgsProcessingException( QObject::tr( "Error parsing formula." ) ); - break; case QgsRasterCalculator::MemoryError: throw QgsProcessingException( QObject::tr( "Error allocating memory for result." ) ); - break; case QgsRasterCalculator::BandError: throw QgsProcessingException( QObject::tr( "Invalid band number for input." ) ); - break; case QgsRasterCalculator::CalculationError: throw QgsProcessingException( QObject::tr( "Error occurred while performing calculation." ) ); - break; default: break; } diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp index 46759033e53d..06e6439663e4 100644 --- a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp @@ -64,9 +64,15 @@ void QgsVirtualRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) { addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); - addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ) ); - addParameter( new QgsProcessingParameterNumber( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ) ); - addParameter( new QgsProcessingParameterCrs( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ) ); + std::unique_ptr extentParam = std::make_unique( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ); + extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) ); + addParameter( extentParam.release() ); + std::unique_ptr cellSizeParam = std::make_unique( QStringLiteral( "CELL_SIZE" ), QObject::tr( "Output cell size (leave empty to set automatically)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0.0 ); + cellSizeParam->setHelp( QObject::tr( "Cell size of the output layer. If not specified, the smallest cell size from the input layers will be used" ) ); + addParameter( cellSizeParam.release() ); + std::unique_ptr crsParam = std::make_unique( QStringLiteral( "CRS" ), QObject::tr( "Output CRS" ), QVariant(), true ); + crsParam->setHelp( QObject::tr( "CRS of the output layer. If not specified, the CRS of the first input layer will be used" ) ); + addParameter( crsParam.release() ); addParameter( new QgsProcessingParameterString( QStringLiteral( "LAYER_NAME" ), QObject::tr( "Output layer name" ), QVariant(), false, true ) ); addOutput( new QgsProcessingOutputRasterLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Calculated" ) ) ); } @@ -119,7 +125,7 @@ QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVarian rasterParameters.rInputLayers.append( rasterLayer ); QgsRectangle ext = rLayer->extent(); - if ( rLayer->crs().authid() != crs.authid() ) + if ( rLayer->crs() != crs ) { QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); ext = ct.transformBoundingBox( ext ); diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 6b1362e1448a..bf856d1758cf 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -5470,13 +5470,18 @@ QString QgsProcessingParameterExpression::asPythonString( const QgsProcessing::P QgsProcessingContext c; code += QStringLiteral( ", defaultValue=%1" ).arg( valueAsPythonString( mDefault, c ) ); - if ( mExpressionType == Qgis::ExpressionType::PointCloud ) - { - code += QLatin1String( ", type=Qgis.ExpressionType.PointCloud)" ); - } - else + + switch ( mExpressionType ) { - code += QLatin1Char( ')' ); + case Qgis::ExpressionType::PointCloud: + code += QLatin1String( ", type=Qgis.ExpressionType.PointCloud)" ); + break; + case Qgis::ExpressionType::RasterCalculator: + code += QLatin1String( ", type=Qgis.ExpressionType.RasterCalculator)" ); + break; + default: + code += QLatin1Char( ')' ); + break; } return code; } diff --git a/src/core/qgis.h b/src/core/qgis.h index b2cb193bcde4..95d88fa115d3 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3765,7 +3765,7 @@ class CORE_EXPORT Qgis { Qgis, //!< Native QGIS expression PointCloud, //!< Point cloud expression - RasterCalculator, //!< Raster calculator expression + RasterCalculator, //!< Raster calculator expression (since QGIS 3.34) }; Q_ENUM( ExpressionType ) diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp index 7e784d4dc4f3..52d9d12d69b6 100644 --- a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp @@ -53,7 +53,7 @@ QgsProcessingRasterCalculatorExpressionLineEdit::QgsProcessingRasterCalculatorEx QgsProcessingRasterCalculatorExpressionLineEdit::~QgsProcessingRasterCalculatorExpressionLineEdit() = default; -void QgsProcessingRasterCalculatorExpressionLineEdit::setLayers( QVariantList layers ) +void QgsProcessingRasterCalculatorExpressionLineEdit::setLayers( const QVariantList &layers ) { mLayers = layers; } @@ -96,7 +96,7 @@ void QgsProcessingRasterCalculatorExpressionLineEdit::expressionEdited( const QS } -QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpressionDialog( QVariantList layers, const QString &startExpression, QWidget *parent ) +QgsProcessingRasterCalculatorExpressionDialog::QgsProcessingRasterCalculatorExpressionDialog( const QVariantList &layers, const QString &startExpression, QWidget *parent ) : QDialog( parent ) , mLayers( layers ) , mInitialText( startExpression ) diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h index 1472d08deead..cdd250b8e272 100644 --- a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h @@ -57,7 +57,7 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionLineEdit : public QWidge /** * Sets a layers associated with the widget. */ - void setLayers( QVariantList layers ); + void setLayers( const QVariantList &layers ); /** * Returns the current expression shown in the widget. @@ -111,7 +111,7 @@ class GUI_EXPORT QgsProcessingRasterCalculatorExpressionDialog : public QDialog, /** * Constructor for QgsProcessingRasterCalculatorExpressionDialog. */ - QgsProcessingRasterCalculatorExpressionDialog( QVariantList layers, const QString &startExpression = QString(), QWidget *parent = nullptr ); + QgsProcessingRasterCalculatorExpressionDialog( const QVariantList &layers, const QString &startExpression = QString(), QWidget *parent = nullptr ); /** * Sets the current expression to show in the widget. From 3c1f510115101f16c412524808e556f405499c2c Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Fri, 15 Sep 2023 13:44:42 +0300 Subject: [PATCH 090/151] add modeler support and deprecate Python implementation --- .../processing/algs/qgis/RasterCalculator.py | 4 + .../tests/testdata/qgis_algorithm_tests2.yaml | 9 +- .../qgsalgorithmrastercalculator.cpp | 166 +++++++++++++++++- .../processing/qgsalgorithmrastercalculator.h | 30 +++- .../qgsalgorithmvirtualrastercalculator.cpp | 163 ++++++++++++++++- .../qgsalgorithmvirtualrastercalculator.h | 28 ++- .../processing/qgsnativealgorithms.cpp | 2 + ...singrastercalculatorexpressionlineedit.cpp | 2 +- .../qgsprocessingwidgetwrapperimpl.cpp | 4 + 9 files changed, 394 insertions(+), 14 deletions(-) diff --git a/python/plugins/processing/algs/qgis/RasterCalculator.py b/python/plugins/processing/algs/qgis/RasterCalculator.py index 9367b263fa9c..2ea965b5c8fc 100644 --- a/python/plugins/processing/algs/qgis/RasterCalculator.py +++ b/python/plugins/processing/algs/qgis/RasterCalculator.py @@ -24,6 +24,7 @@ from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.algs.gdal.GdalUtils import GdalUtils from qgis.core import (QgsProcessing, + QgsProcessingAlgorithm, QgsProcessingException, QgsProcessingUtils, QgsProcessingParameterCrs, @@ -88,6 +89,9 @@ def clone(self): self.addParameter(QgsProcessingParameterCrs(self.CRS, 'Output CRS', optional=True)) self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT, self.tr('Output'))) + def flags(self): + return super().flags() | QgsProcessingAlgorithm.FlagDeprecated | QgsProcessingAlgorithm.FlagNotAvailableInStandaloneTool + def name(self): return 'rastercalculator' diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml index 9a1040ed9b95..f43816b277bc 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml @@ -1190,7 +1190,7 @@ tests: - 'Maximum value: 15:29:22' - 'NULL \(missing\) values: 1' - - algorithm: qgis:rastercalculator + - algorithm: native:rastercalc name: Raster Calculator with cellsize params: LAYERS: @@ -1199,13 +1199,14 @@ tests: type: raster type: multi CELLSIZE: 0.001 - EXPRESSION: dem@1 + EXPRESSION: '"dem.tif@1"' results: OUTPUT: hash: 525577c05dd999239d9c6f95fd5e70d96355da3a0ea71bfcf021e729 + hash: 6ced822cc490c7a3d9346b6c8cd4b282eb4e2a9fdd6e7371f6174117 type: rasterhash - - algorithm: qgis:rastercalculator + - algorithm: native:rastercalc name: Raster Calculator params: LAYERS: @@ -1214,7 +1215,7 @@ tests: type: raster type: multi CELLSIZE: 0.0 - EXPRESSION: dem@1 * 2 + EXPRESSION: '"dem.tif@1" * 2' results: OUTPUT: hash: 98daf025230ec9d031f7502c6a80a3b04dd060808d6b7bcb4328e87c diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp index d3729fac4681..217fd650c728 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -21,6 +21,11 @@ ///@cond PRIVATE +QgsProcessingAlgorithm::Flags QgsRasterCalculatorAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagHideFromModeler; +} + QString QgsRasterCalculatorAlgorithm::name() const { return QStringLiteral( "rastercalc" ); @@ -59,8 +64,8 @@ QgsRasterCalculatorAlgorithm *QgsRasterCalculatorAlgorithm::createInstance() con void QgsRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) { - addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); - addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); + addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "LAYERS" ), false, Qgis::ExpressionType::RasterCalculator ) ); std::unique_ptr extentParam = std::make_unique( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ); extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) ); addParameter( extentParam.release() ); @@ -75,7 +80,7 @@ void QgsRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) bool QgsRasterCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context ); + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ); for ( const QgsMapLayer *layer : std::as_const( layers ) ) { @@ -198,5 +203,160 @@ QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap &p return outputs; } +QgsProcessingAlgorithm::Flags QgsRasterCalculatorModelerAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagHideFromToolbox; +} + +QString QgsRasterCalculatorModelerAlgorithm::name() const +{ + return QStringLiteral( "modelerrastercalc" ); +} + +QString QgsRasterCalculatorModelerAlgorithm::displayName() const +{ + return QObject::tr( "Raster calculator" ); +} + +QStringList QgsRasterCalculatorModelerAlgorithm::tags() const +{ + return QObject::tr( "raster,calculator" ).split( ',' ); +} + +QString QgsRasterCalculatorModelerAlgorithm::group() const +{ + return QObject::tr( "Raster analysis" ); +} + +QString QgsRasterCalculatorModelerAlgorithm::groupId() const +{ + return QStringLiteral( "rasteranalysis" ); +} + +QgsRasterCalculatorModelerAlgorithm *QgsRasterCalculatorModelerAlgorithm::createInstance() const +{ + return new QgsRasterCalculatorModelerAlgorithm(); +} + +QVariantMap QgsRasterCalculatorModelerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + for ( QgsMapLayer *layer : std::as_const( mLayers ) ) + { + layer->moveToThread( QThread::currentThread() ); + } + + QgsCoordinateReferenceSystem crs; + if ( parameters.value( QStringLiteral( "CRS" ) ).isValid() ) + { + crs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context ); + } + else + { + crs = mLayers.at( 0 )->crs(); + } + + QgsRectangle bbox; + if ( parameters.value( QStringLiteral( "EXTENT" ) ).isValid() ) + { + bbox = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs ); + } + else + { + bbox = QgsProcessingUtils::combineLayerExtents( mLayers, crs, context ); + } + + double minCellSize = 1e9; + + QVector< QgsRasterCalculatorEntry > entries; + int n = 0; + for ( QgsMapLayer *layer : mLayers ) + { + QgsRasterLayer *rLayer = static_cast( layer ); + if ( !rLayer ) + { + continue; + } + + n++; + const int nBands = rLayer->dataProvider()->bandCount(); + for ( int i = 0; i < nBands; ++i ) + { + QgsRasterCalculatorEntry entry; + entry.ref = QStringLiteral( "%1@%2" ).arg( indexToName( n ) ).arg( i + 1 ); + entry.raster = rLayer; + entry.bandNumber = i + 1; + entries << entry; + } + + QgsRectangle ext = rLayer->extent(); + if ( rLayer->crs() != crs ) + { + QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); + ext = ct.transformBoundingBox( ext ); + } + + double cellSize = ( ext.xMaximum() - ext.xMinimum() ) / rLayer->width(); + if ( cellSize < minCellSize ) + { + minCellSize = cellSize; + } + } + + double cellSize = parameterAsDouble( parameters, QStringLiteral( "CELL_SIZE" ), context ); + if ( cellSize == 0 ) + { + cellSize = minCellSize; + } + + const QString expression = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ); + const QFileInfo fi( outputFile ); + const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() ); + + double width = std::round( ( bbox.xMaximum() - bbox.xMinimum() ) / cellSize ); + double height = std::round( ( bbox.yMaximum() - bbox.yMinimum() ) / cellSize ); + + QgsRasterCalculator calc( expression, outputFile, outputFormat, bbox, crs, width, height, entries, context.transformContext() ); + QgsRasterCalculator::Result result = calc.processCalculation( feedback ); + qDeleteAll( mLayers ); + mLayers.clear(); + switch ( result ) + { + case QgsRasterCalculator::CreateOutputError: + throw QgsProcessingException( QObject::tr( "Error creating output file." ) ); + case QgsRasterCalculator::InputLayerError: + throw QgsProcessingException( QObject::tr( "Error reading input layer." ) ); + case QgsRasterCalculator::ParserError: + throw QgsProcessingException( QObject::tr( "Error parsing formula." ) ); + case QgsRasterCalculator::MemoryError: + throw QgsProcessingException( QObject::tr( "Error allocating memory for result." ) ); + case QgsRasterCalculator::BandError: + throw QgsProcessingException( QObject::tr( "Invalid band number for input." ) ); + case QgsRasterCalculator::CalculationError: + throw QgsProcessingException( QObject::tr( "Error occurred while performing calculation." ) ); + default: + break; + } + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), outputFile ); + return outputs; +} + +QString QgsRasterCalculatorModelerAlgorithm::indexToName( int index ) const +{ + QString name; + int div = index; + int mod = 0; + + while ( div > 0 ) + { + mod = ( div - 1 ) % 26; + name = static_cast( 65 + mod ) + name; + div = ( int )( ( div - mod ) / 26 ); + } + return name; +} + ///@endcond diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.h b/src/analysis/processing/qgsalgorithmrastercalculator.h index 469c36101b81..1521e17cba42 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.h +++ b/src/analysis/processing/qgsalgorithmrastercalculator.h @@ -38,6 +38,7 @@ class QgsRasterCalculatorAlgorithm : public QgsProcessingAlgorithm void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRasterCalculator.svg" ) ); } + Flags flags() const override; QString name() const override; QString displayName() const override; QStringList tags() const override; @@ -53,10 +54,37 @@ class QgsRasterCalculatorAlgorithm : public QgsProcessingAlgorithm QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - private: QList< QgsMapLayer * > mLayers; }; +class QgsRasterCalculatorModelerAlgorithm : public QgsRasterCalculatorAlgorithm +{ + + public: + + QgsRasterCalculatorModelerAlgorithm() = default; + Flags flags() const override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QgsRasterCalculatorModelerAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + /** + * Generates Excel-like names from the number + * A, B, C, …, Y, Z, AA, AB, AC, …, AZ, BA, BB, BC… + */ + QString indexToName( int index ) const; +}; + ///@endcond PRIVATE #endif // QGSALGORITHMRASTERCALCULATOR_H diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp index 06e6439663e4..2a773507e926 100644 --- a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp @@ -22,7 +22,7 @@ QgsProcessingAlgorithm::Flags QgsVirtualRasterCalculatorAlgorithm::flags() const { - return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagNoThreading; + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagNoThreading | FlagHideFromModeler; } QString QgsVirtualRasterCalculatorAlgorithm::name() const @@ -62,8 +62,8 @@ QgsVirtualRasterCalculatorAlgorithm *QgsVirtualRasterCalculatorAlgorithm::create void QgsVirtualRasterCalculatorAlgorithm::initAlgorithm( const QVariantMap & ) { - addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "INPUT" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); - addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ), false, Qgis::ExpressionType::RasterCalculator ) ); + addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::SourceType::TypeRaster ) ); + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "LAYERS" ), false, Qgis::ExpressionType::RasterCalculator ) ); std::unique_ptr extentParam = std::make_unique( QStringLiteral( "EXTENT" ), QObject::tr( "Output extent" ), QVariant(), true ); extentParam->setHelp( QObject::tr( "Extent of the output layer. If not specified, the extent will be the overall extent of all input layers" ) ); addParameter( extentParam.release() ); @@ -81,7 +81,7 @@ QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVarian { Q_UNUSED( feedback ); - const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context ); + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ); if ( layers.isEmpty() ) { throw QgsProcessingException( QObject::tr( "No input layers selected" ) ); @@ -180,4 +180,159 @@ QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVarian return outputs; } +QgsProcessingAlgorithm::Flags QgsVirtualRasterCalculatorModelerAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagNoThreading | FlagHideFromToolbox; +} + +QString QgsVirtualRasterCalculatorModelerAlgorithm::name() const +{ + return QStringLiteral( "modelervirtualrastercalc" ); +} + +QString QgsVirtualRasterCalculatorModelerAlgorithm::displayName() const +{ + return QObject::tr( "Raster calculator (virtual)" ); +} + +QStringList QgsVirtualRasterCalculatorModelerAlgorithm::tags() const +{ + return QObject::tr( "raster,calculator,virtual" ).split( ',' ); +} + +QString QgsVirtualRasterCalculatorModelerAlgorithm::group() const +{ + return QObject::tr( "Raster analysis" ); +} + +QString QgsVirtualRasterCalculatorModelerAlgorithm::groupId() const +{ + return QStringLiteral( "rasteranalysis" ); +} + +QgsVirtualRasterCalculatorModelerAlgorithm *QgsVirtualRasterCalculatorModelerAlgorithm::createInstance() const +{ + return new QgsVirtualRasterCalculatorModelerAlgorithm(); +} + +QVariantMap QgsVirtualRasterCalculatorModelerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_UNUSED( feedback ); + + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "INPUT" ), context ); + if ( layers.isEmpty() ) + { + throw QgsProcessingException( QObject::tr( "No input layers selected" ) ); + } + + QgsCoordinateReferenceSystem crs; + if ( parameters.value( QStringLiteral( "CRS" ) ).isValid() ) + { + crs = parameterAsCrs( parameters, QStringLiteral( "CRS" ), context ); + } + else + { + crs = layers.at( 0 )->crs(); + } + + QgsRectangle bbox; + if ( parameters.value( QStringLiteral( "EXTENT" ) ).isValid() ) + { + bbox = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs ); + } + else + { + bbox = QgsProcessingUtils::combineLayerExtents( layers, crs, context ); + } + + double minCellSize = 1e9; + QgsRasterDataProvider::VirtualRasterParameters rasterParameters; + + int n = 0; + for ( const QgsMapLayer *layer : layers ) + { + const QgsRasterLayer *rLayer = static_cast( layer ); + if ( !rLayer ) + { + continue; + } + + n++; + QgsRasterDataProvider::VirtualRasterInputLayers rasterLayer; + rasterLayer.name = indexToName( n ); + rasterLayer.provider = rLayer->dataProvider()->name(); + rasterLayer.uri = rLayer->source(); + rasterParameters.rInputLayers.append( rasterLayer ); + + QgsRectangle ext = rLayer->extent(); + if ( rLayer->crs() != crs ) + { + QgsCoordinateTransform ct( rLayer->crs(), crs, context.transformContext() ); + ext = ct.transformBoundingBox( ext ); + } + + double cellSize = ( ext.xMaximum() - ext.xMinimum() ) / rLayer->width(); + if ( cellSize < minCellSize ) + { + minCellSize = cellSize; + } + } + + double cellSize = parameterAsDouble( parameters, QStringLiteral( "CELL_SIZE" ), context ); + if ( cellSize == 0 ) + { + cellSize = minCellSize; + } + + const QString expression = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + QString layerName = parameterAsString( parameters, QStringLiteral( "LAYER_NAME" ), context ); + if ( layerName.isEmpty() ) + { + layerName = expression; + } + + double width = std::round( ( bbox.xMaximum() - bbox.xMinimum() ) / cellSize ); + double height = std::round( ( bbox.yMaximum() - bbox.yMinimum() ) / cellSize ); + + rasterParameters.crs = crs; + rasterParameters.extent = bbox; + rasterParameters.width = width; + rasterParameters.height = height; + rasterParameters.formula = expression; + + std::unique_ptr< QgsRasterLayer > layer; + layer = std::make_unique< QgsRasterLayer >( QgsRasterDataProvider::encodeVirtualRasterProviderUri( rasterParameters ), + layerName, QStringLiteral( "virtualraster" ) ); + if ( !layer->isValid() ) + { + feedback->reportError( QObject::tr( "Failed to create virtual raster layer" ) ); + } + else + { + } + const QString layerId = layer->id(); + const QgsProcessingContext::LayerDetails details( layer->name(), context.project(), QStringLiteral( "OUTPUT" ), QgsProcessingUtils::LayerHint::Raster ); + context.addLayerToLoadOnCompletion( layerId, details ); + context.temporaryLayerStore()->addMapLayer( layer.release() ); + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), layerId ); + return outputs; +} + +QString QgsVirtualRasterCalculatorModelerAlgorithm::indexToName( int index ) const +{ + QString name; + int div = index; + int mod = 0; + + while ( div > 0 ) + { + mod = ( div - 1 ) % 26; + name = static_cast( 65 + mod ) + name; + div = ( int )( ( div - mod ) / 26 ); + } + return name; +} + ///@endcond diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h index 84448d5c1e7a..a73b5347e0d8 100644 --- a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h @@ -52,10 +52,36 @@ class QgsVirtualRasterCalculatorAlgorithm : public QgsProcessingAlgorithm QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - private: QList< QgsMapLayer * > mLayers; }; +class QgsVirtualRasterCalculatorModelerAlgorithm : public QgsVirtualRasterCalculatorAlgorithm +{ + + public: + + QgsVirtualRasterCalculatorModelerAlgorithm() = default; + Flags flags() const override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QgsVirtualRasterCalculatorModelerAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + private: + + /** + * Generates Excel-like names from the number + * A, B, C, …, Y, Z, AA, AB, AC, …, AZ, BA, BB, BC… + */ + QString indexToName( int index ) const; +}; + ///@endcond PRIVATE #endif // QGSALGORITHMVIRTUALRASTERCALCULATOR_H diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index a47107062d09..3b3697653701 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -443,6 +443,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsRandomPoissonRasterAlgorithm() ); addAlgorithm( new QgsRandomUniformRasterAlgorithm() ); addAlgorithm( new QgsRasterCalculatorAlgorithm() ); + addAlgorithm( new QgsRasterCalculatorModelerAlgorithm() ); addAlgorithm( new QgsRasterDtmSlopeBasedFilterAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByEqualOperatorAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByGreaterThanOperatorAlgorithm() ); @@ -525,6 +526,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsUnionAlgorithm() ); addAlgorithm( new QgsVariableWidthBufferByMAlgorithm() ); addAlgorithm( new QgsVirtualRasterCalculatorAlgorithm() ); + addAlgorithm( new QgsVirtualRasterCalculatorModelerAlgorithm() ); addAlgorithm( new QgsWedgeBuffersAlgorithm() ); addAlgorithm( new QgsWriteVectorTilesXyzAlgorithm() ); addAlgorithm( new QgsWriteVectorTilesMbtilesAlgorithm() ); diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp index 52d9d12d69b6..b4ae5f88f642 100644 --- a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp @@ -176,7 +176,7 @@ QString QgsProcessingRasterCalculatorExpressionDialog::quoteBandEntry( const QSt void QgsProcessingRasterCalculatorExpressionDialog::mLayersList_itemDoubleClicked( QListWidgetItem *item ) { - mExpressionTextEdit->insertPlainText( quoteBandEntry( item->text() ) ); + mExpressionTextEdit->insertPlainText( quoteBandEntry( QStringLiteral( "%1@1" ).arg( item->text() ) ) ); } void QgsProcessingRasterCalculatorExpressionDialog::mBtnPlus_clicked() diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 020f75226eef..395fa9a8a16f 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -2303,6 +2303,10 @@ QWidget *QgsProcessingExpressionWidgetWrapper::createWidget() { mRasterCalculatorExpLineEdit = new QgsProcessingRasterCalculatorExpressionLineEdit(); mRasterCalculatorExpLineEdit->setToolTip( parameterDefinition()->toolTip() ); + if ( type() == QgsProcessingGui::Modeler ) + { + mRasterCalculatorExpLineEdit->setLayers( QVariantList() << "A" << "B" << "C" << "D" << "E" << "F" << "G" ); + } connect( mRasterCalculatorExpLineEdit, &QgsProcessingRasterCalculatorExpressionLineEdit::expressionChanged, this, [ = ]( const QString & ) { emit widgetValueHasChanged( this ); From d29c2196f3487e44a00d3db094f6cf0cdd1d06dd Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sat, 16 Sep 2023 09:51:00 +0700 Subject: [PATCH 091/151] [maptools] Fix extra duplicated feature when using the copy+move map tool action --- src/app/qgsmaptoolmovefeature.cpp | 1 + src/core/vector/qgsvectorlayertools.cpp | 36 ++++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index f19d53313e07..292a24d0118d 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -244,6 +244,7 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) } case CopyMove: QgsFeatureRequest request; + qDebug() << mMovedFeatures; request.setFilterFids( mMovedFeatures ); QString errorMsg; QString childrenInfoMsg; diff --git a/src/core/vector/qgsvectorlayertools.cpp b/src/core/vector/qgsvectorlayertools.cpp index 99aba7f309b1..19a5730c07a5 100644 --- a/src/core/vector/qgsvectorlayertools.cpp +++ b/src/core/vector/qgsvectorlayertools.cpp @@ -48,6 +48,13 @@ bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureReq { browsedFeatureCount++; + if ( f.hasGeometry() ) + { + QgsGeometry geom = f.geometry(); + geom.translate( dx, dy ); + f.setGeometry( geom ); + } + QgsFeature newFeature; if ( mProject ) { @@ -69,34 +76,25 @@ bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureReq else { newFeature = QgsVectorLayerUtils::createFeature( layer, f.geometry(), f.attributes().toMap() ); + if ( !layer->addFeature( newFeature ) ) + { + couldNotWriteCount++; + QgsDebugError( QStringLiteral( "Could not add new feature. Original copied feature id: %1" ).arg( f.id() ) ); + } } // translate if ( newFeature.hasGeometry() ) { QgsGeometry geom = newFeature.geometry(); - geom.translate( dx, dy ); - newFeature.setGeometry( geom ); -#ifdef QGISDEBUG - const QgsFeatureId fid = newFeature.id(); -#endif - // paste feature - if ( !layer->addFeature( newFeature ) ) - { - couldNotWriteCount++; - QgsDebugError( QStringLiteral( "Could not add new feature. Original copied feature id: %1" ).arg( fid ) ); - } - else + fidList.insert( newFeature.id() ); + if ( topologicalEditing ) { - fidList.insert( newFeature.id() ); - if ( topologicalEditing ) + if ( topologicalLayer ) { - if ( topologicalLayer ) - { - topologicalLayer->addTopologicalPoints( geom ); - } - layer->addTopologicalPoints( geom ); + topologicalLayer->addTopologicalPoints( geom ); } + layer->addTopologicalPoints( geom ); } } else From 0d1e390173409a62c9b79b6b961b76a3b47cafdf Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Fri, 22 Sep 2023 10:02:55 +0700 Subject: [PATCH 092/151] Address review --- src/app/qgsmaptoolmovefeature.cpp | 1 - src/core/vector/qgsvectorlayertools.cpp | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/qgsmaptoolmovefeature.cpp b/src/app/qgsmaptoolmovefeature.cpp index 292a24d0118d..f19d53313e07 100644 --- a/src/app/qgsmaptoolmovefeature.cpp +++ b/src/app/qgsmaptoolmovefeature.cpp @@ -244,7 +244,6 @@ void QgsMapToolMoveFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) } case CopyMove: QgsFeatureRequest request; - qDebug() << mMovedFeatures; request.setFilterFids( mMovedFeatures ); QString errorMsg; QString childrenInfoMsg; diff --git a/src/core/vector/qgsvectorlayertools.cpp b/src/core/vector/qgsvectorlayertools.cpp index 19a5730c07a5..96637c6aba71 100644 --- a/src/core/vector/qgsvectorlayertools.cpp +++ b/src/core/vector/qgsvectorlayertools.cpp @@ -59,6 +59,15 @@ bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureReq if ( mProject ) { newFeature = QgsVectorLayerUtils::duplicateFeature( layer, f, mProject, duplicateFeatureContext ); + if ( !newFeature.isValid() ) + { + couldNotWriteCount++; + QgsDebugError( QStringLiteral( "Could not add new feature. Original copied feature id: %1" ).arg( f.id() ) ); + } + else + { + fidList.insert( newFeature.id() ); + } const auto duplicateFeatureContextLayers = duplicateFeatureContext.layers(); for ( QgsVectorLayer *chl : duplicateFeatureContextLayers ) @@ -81,13 +90,16 @@ bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureReq couldNotWriteCount++; QgsDebugError( QStringLiteral( "Could not add new feature. Original copied feature id: %1" ).arg( f.id() ) ); } + else + { + fidList.insert( newFeature.id() ); + } } // translate if ( newFeature.hasGeometry() ) { QgsGeometry geom = newFeature.geometry(); - fidList.insert( newFeature.id() ); if ( topologicalEditing ) { if ( topologicalLayer ) From 98431b35dca86300daed31b6a88b05e0f8a375ed Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 25 Sep 2023 15:50:35 +0700 Subject: [PATCH 093/151] Tweak test to check that the copy move action only copies once --- tests/src/python/test_qgsvectorlayertools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/python/test_qgsvectorlayertools.py b/tests/src/python/test_qgsvectorlayertools.py index 895245addb5c..2230eb8f52ab 100644 --- a/tests/src/python/test_qgsvectorlayertools.py +++ b/tests/src/python/test_qgsvectorlayertools.py @@ -96,9 +96,11 @@ def testCopyMoveFeature(self): """ Test copy and move features""" rqst = QgsFeatureRequest() rqst.setFilterFid(4) + features_count = self.vl.featureCount() self.vl.startEditing() (ok, rqst, msg) = self.vltools.copyMoveFeatures(self.vl, rqst, -0.1, 0.2) self.assertTrue(ok) + self.assertEqual(self.vl.featureCount(), features_count + 1) for f in self.vl.getFeatures(rqst): geom = f.geometry() self.assertAlmostEqual(geom.asPoint().x(), -65.42) From 22003e7324765d2ede3c13da5893f1a772afabb0 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Mon, 25 Sep 2023 16:12:21 +0200 Subject: [PATCH 094/151] fix clang-tidy --- src/core/geometry/qgsgeometry.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 956af86aac75..f8af87190218 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3082,7 +3082,8 @@ int QgsGeometry::avoidIntersections( const QList &avoidInterse case Qgis::GeometryOperationResult::InvalidBaseGeometry: return 3; case Qgis::GeometryOperationResult::NothingHappened: - return 4; + + // these should never happen case Qgis::GeometryOperationResult::SelectionIsEmpty: case Qgis::GeometryOperationResult::SelectionIsGreaterThanOne: case Qgis::GeometryOperationResult::GeometryEngineError: @@ -3094,7 +3095,6 @@ int QgsGeometry::avoidIntersections( const QList &avoidInterse case Qgis::GeometryOperationResult::AddRingCrossesExistingRings: case Qgis::GeometryOperationResult::AddRingNotInExistingFeature: case Qgis::GeometryOperationResult::SplitCannotSplitPoint: - // should never happen return 4; } } From ca92d29fe2869163b89c2e3c1fe1eb3e0563dd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1thory=20P=C3=A9ter?= Date: Mon, 25 Sep 2023 11:47:24 +0200 Subject: [PATCH 095/151] Fix typo in qgscoordinatereferencesystemutils.cpp --- src/core/proj/qgscoordinatereferencesystemutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/proj/qgscoordinatereferencesystemutils.cpp b/src/core/proj/qgscoordinatereferencesystemutils.cpp index 54f711f4f1c7..a9528e5fba1b 100644 --- a/src/core/proj/qgscoordinatereferencesystemutils.cpp +++ b/src/core/proj/qgscoordinatereferencesystemutils.cpp @@ -259,7 +259,7 @@ QString QgsCoordinateReferenceSystemUtils::translateProjection( const QString &p if ( projection == QLatin1String( "eck6" ) ) return QObject::tr( "Eckert VI" ); if ( projection == QLatin1String( "eqc" ) ) - return QObject::tr( "Equidistant Cylindrical (Plate Caree)" ); + return QObject::tr( "Equidistant Cylindrical (Plate Carrée)" ); if ( projection == QLatin1String( "eqdc" ) ) return QObject::tr( "Equidistant Conic" ); if ( projection == QLatin1String( "eqearth" ) ) From 1dba5d8db3aede2f7e9bde168759b91e8adcb381 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Fri, 15 Sep 2023 18:41:44 +0700 Subject: [PATCH 096/151] [maptools] Fix adding part on a multipolygon geopackage dataset --- src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp b/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp index c32230b9fc63..c3a10c89064a 100644 --- a/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp +++ b/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp @@ -38,8 +38,9 @@ void QgsMapToolCaptureLayerGeometry::geometryCaptured( const QgsGeometry &geomet case QgsMapToolCapture::CaptureLine: case QgsMapToolCapture::CapturePolygon: //does provider support circular strings? + const bool datasetIsCurved = QgsWkbTypes::isCurvedType( vlayer->wkbType() ); const bool providerSupportsCurvedSegments = vlayer && ( vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::CircularGeometries ); - if ( !providerSupportsCurvedSegments ) + if ( !datasetIsCurved || !providerSupportsCurvedSegments ) g = QgsGeometry( g.constGet()->segmentize() ); QList avoidIntersectionsLayers; From 775d33cc5c34aa1141b22eb62fcbec62f8f148b8 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Mon, 25 Sep 2023 15:18:29 +0700 Subject: [PATCH 097/151] Add test coverage to add part map tool --- tests/src/app/CMakeLists.txt | 1 + tests/src/app/testqgsmaptooladdpart.cpp | 176 ++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 tests/src/app/testqgsmaptooladdpart.cpp diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 4ace890a0ee1..edd91d35c2db 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -20,6 +20,7 @@ set(TESTS testqgsdwgimportdialog.cpp testqgsfieldcalculator.cpp testqgsmapcanvasdockwidget.cpp + testqgsmaptooladdpart.cpp testqgsmaptooleditannotation.cpp testqgsmaptoolidentifyaction.cpp testqgsmaptoollabel.cpp diff --git a/tests/src/app/testqgsmaptooladdpart.cpp b/tests/src/app/testqgsmaptooladdpart.cpp new file mode 100644 index 000000000000..fd0111d58bb7 --- /dev/null +++ b/tests/src/app/testqgsmaptooladdpart.cpp @@ -0,0 +1,176 @@ +/*************************************************************************** + testqgsmaptooladdpart.cpp + -------------------------------- + Date : 2023-09-25 + Copyright : (C) 2023 by Mathieu Pellerin + Email : mathieu@opengis.ch + *************************************************************************** + * * + * 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 "qgsmaptooladdpart.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 part map tool + */ +class TestQgsMapToolAddPart: public QObject +{ + Q_OBJECT + public: + TestQgsMapToolAddPart(); + + 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 testAddPart(); + + private: + QPoint mapToPoint( double x, double y ); + + QgisApp *mQgisApp = nullptr; + QgsMapCanvas *mCanvas = nullptr; + QgsMapToolAddPart *mCaptureTool = nullptr; + QgsVectorLayer *mLayerMultiPolygon = nullptr; +}; + +TestQgsMapToolAddPart::TestQgsMapToolAddPart() = default; + + +//runs before all tests +void TestQgsMapToolAddPart::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 (((2 2, 4 2, 4 4, 2 4)))" ); + 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 QgsMapToolAddPart( 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 TestQgsMapToolAddPart::cleanupTestCase() +{ + delete mCaptureTool; + delete mCanvas; + QgsApplication::exitQgis(); +} + +QPoint TestQgsMapToolAddPart::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 TestQgsMapToolAddPart::testAddPart() +{ + mLayerMultiPolygon->select( 1 ); + + std::unique_ptr< QgsMapMouseEvent > event( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 5, 5 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 5, 5 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 6, 5 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 6, 6 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 5, 6 ), + Qt::LeftButton + ) ); + mCaptureTool->cadCanvasReleaseEvent( event.get() ); + + event.reset( new QgsMapMouseEvent( + mCanvas, + QEvent::MouseButtonRelease, + mapToPoint( 5, 5 ), + 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)))"; + QCOMPARE( mLayerMultiPolygon->getFeature( 1 ).geometry().asWkt(), wkt ); +} + +QGSTEST_MAIN( TestQgsMapToolAddPart ) +#include "testqgsmaptooladdpart.moc" From c561e1b173bc732365c3a76e16f39b541b7995a6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 27 Sep 2023 08:11:37 +1000 Subject: [PATCH 098/151] Improve tooltip for character widget Changes: - Include the character in both the selected font and a standard font, so that it's possible to see what the associated character actually is when the font is a symbol font - Use a table for improved alignment - Simplify translated strings, avoid translation of HTML --- src/gui/symbology/characterwidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/symbology/characterwidget.cpp b/src/gui/symbology/characterwidget.cpp index 84e91a57569c..24850b906404 100644 --- a/src/gui/symbology/characterwidget.cpp +++ b/src/gui/symbology/characterwidget.cpp @@ -212,11 +212,14 @@ void CharacterWidget::mouseMoveEvent( QMouseEvent *event ) const QPoint widgetPosition = mapFromGlobal( event->globalPos() ); const uint key = ( widgetPosition.y() / mSquareSize ) * mColumns + widgetPosition.x() / mSquareSize; - const QString text = tr( "

Character: %2

Decimal: %3

Hex: 0x%4" ) + const QString text = QStringLiteral( "

%2

%3%2
%4%5
%60x%7
" ) .arg( mDisplayFont.family() ) .arg( QChar( key ) ) + .arg( tr( "Character" ), + tr( "Decimal" ) ) .arg( key ) - .arg( QString::number( key, 16 ) ); + .arg( tr( "Hex" ), + QString::number( key, 16 ) ); QToolTip::showText( event->globalPos(), text, this ); } From 68f025b2e59d5ed60882fe8341b55c476a0aeb83 Mon Sep 17 00:00:00 2001 From: Denis Rouzaud Date: Wed, 27 Sep 2023 14:00:25 +0200 Subject: [PATCH 099/151] expose QgsField configuration flags in python bindings (#54753) --- python/core/auto_generated/qgsfield.sip.in | 30 ++++++++++++++++++++++ src/core/qgsfield.h | 11 +++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/python/core/auto_generated/qgsfield.sip.in b/python/core/auto_generated/qgsfield.sip.in index ad205b675185..3291365483ad 100644 --- a/python/core/auto_generated/qgsfield.sip.in +++ b/python/core/auto_generated/qgsfield.sip.in @@ -34,6 +34,15 @@ length, and if applicable, precision. public: + enum class ConfigurationFlag + { + None, + NotSearchable, + HideFromWms, + HideFromWfs, + }; + typedef QFlags ConfigurationFlags; + QgsField( const QString &name = QString(), QVariant::Type type = QVariant::Invalid, @@ -342,13 +351,31 @@ Sets the alias for the field (the friendly displayed name of the field ). .. versionadded:: 3.0 %End + QgsField::ConfigurationFlags configurationFlags() const; +%Docstring +Returns the Flags for the field (searchable, …) +.. versionadded:: 3.16 +%End + + void setConfigurationFlags( QgsField::ConfigurationFlags configurationFlags ); +%Docstring +Sets the Flags for the field (searchable, …) + +.. versionadded:: 3.16 +%End QString displayString( const QVariant &v ) const; %Docstring Formats string for display %End + static QString readableConfigurationFlag( QgsField::ConfigurationFlag flag ); +%Docstring +Returns the readable and translated value of the configuration flag + +.. versionadded:: 3.16 +%End bool convertCompatible( QVariant &v ) const; @@ -477,6 +504,9 @@ be handled during a split operation. }; // class QgsField +QFlags operator|(QgsField::ConfigurationFlag f1, QFlags f2); + + /************************************************************************ diff --git a/src/core/qgsfield.h b/src/core/qgsfield.h index 00f824c37e77..64c0f23522fa 100644 --- a/src/core/qgsfield.h +++ b/src/core/qgsfield.h @@ -69,8 +69,6 @@ class CORE_EXPORT QgsField public: -#ifndef SIP_RUN - /** * Configuration flags for fields * These flags are meant to be user-configurable @@ -88,7 +86,6 @@ class CORE_EXPORT QgsField Q_ENUM( ConfigurationFlag ) Q_DECLARE_FLAGS( ConfigurationFlags, ConfigurationFlag ) Q_FLAG( ConfigurationFlags ) -#endif /** * Constructor. Constructs a new QgsField object. @@ -378,13 +375,13 @@ class CORE_EXPORT QgsField * Returns the Flags for the field (searchable, …) * \since QGIS 3.16 */ - QgsField::ConfigurationFlags configurationFlags() const SIP_SKIP; + QgsField::ConfigurationFlags configurationFlags() const; /** * Sets the Flags for the field (searchable, …) * \since QGIS 3.16 */ - void setConfigurationFlags( QgsField::ConfigurationFlags configurationFlags ) SIP_SKIP; + void setConfigurationFlags( QgsField::ConfigurationFlags configurationFlags ); //! Formats string for display QString displayString( const QVariant &v ) const; @@ -393,7 +390,7 @@ class CORE_EXPORT QgsField * Returns the readable and translated value of the configuration flag * \since QGIS 3.16 */ - static QString readableConfigurationFlag( QgsField::ConfigurationFlag flag ) SIP_SKIP; + static QString readableConfigurationFlag( QgsField::ConfigurationFlag flag ); #ifndef SIP_RUN @@ -548,7 +545,7 @@ class CORE_EXPORT QgsField Q_DECLARE_METATYPE( QgsField ) -Q_DECLARE_OPERATORS_FOR_FLAGS( QgsField::ConfigurationFlags ) SIP_SKIP +Q_DECLARE_OPERATORS_FOR_FLAGS( QgsField::ConfigurationFlags ) //! Writes the field to stream out. QGIS version compatibility is not guaranteed. CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsField &field ); From 028d25439ad1e27d35d575e5494abb6cd5e638cf Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 27 Sep 2023 17:00:25 +0200 Subject: [PATCH 100/151] remove comma Co-authored-by: Denis Rouzaud --- src/core/geometry/qgsgeometry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index b7ddd18c3a44..3ce2ddad1ede 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2561,7 +2561,7 @@ class CORE_EXPORT QgsGeometry * \param ignoreFeatures possibility to give a list of features where intersections should be ignored (not available in Python bindings) * \returns Success in case of success * InvalidInputGeometryType if geometry is not of polygon type - * GeometryTypeHasChanged if avoid intersection has changed the geometry type, + * GeometryTypeHasChanged if avoid intersection has changed the geometry type * InvalidBaseGeometry at least one geometry intersected is invalid. The algorithm may not work and return the same geometry as the input. You must fix your intersecting geometries. * NothingHappened if the geometry is not intersected by one of the geometries present in the provided layers. * \since QGIS 3.34 From 29f19dafc6d31d03a76593bd3d7e04e5e19e6ae5 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 27 Sep 2023 18:39:19 +0200 Subject: [PATCH 101/151] fix sip --- python/core/auto_generated/geometry/qgsgeometry.sip.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index ba07e4d0b77f..3a53b66397d2 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2458,7 +2458,7 @@ Modifies geometry to avoid intersections with the layers specified in project pr :return: Success in case of success InvalidInputGeometryType if geometry is not of polygon type - GeometryTypeHasChanged if avoid intersection has changed the geometry type, + GeometryTypeHasChanged if avoid intersection has changed the geometry type InvalidBaseGeometry at least one geometry intersected is invalid. The algorithm may not work and return the same geometry as the input. You must fix your intersecting geometries. NothingHappened if the geometry is not intersected by one of the geometries present in the provided layers. From deccbb6fd567d3e0146a42bdb0c577f8966c7052 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 27 Sep 2023 12:34:31 +0300 Subject: [PATCH 102/151] use "@id" instead of deprecated "$id" in geometry checker (fix #52658) --- src/plugins/geometry_checker/qgsgeometrycheckerresulttab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/geometry_checker/qgsgeometrycheckerresulttab.cpp b/src/plugins/geometry_checker/qgsgeometrycheckerresulttab.cpp index 40d664202b14..dad48fa0bfc1 100644 --- a/src/plugins/geometry_checker/qgsgeometrycheckerresulttab.cpp +++ b/src/plugins/geometry_checker/qgsgeometrycheckerresulttab.cpp @@ -459,7 +459,7 @@ void QgsGeometryCheckerResultTab::openAttributeTable() QStringList expr; for ( QgsFeatureId id : it.value() ) { - expr.append( QStringLiteral( "$id = %1 " ).arg( id ) ); + expr.append( QStringLiteral( "@id = %1 " ).arg( id ) ); } if ( mAttribTableDialogs[layerId] ) { From 38f0c2211e37ae5406b2e2b3337427dd26f5fa13 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 27 Sep 2023 11:18:55 +0200 Subject: [PATCH 103/151] GUI: fix vector lyr properties dlg project dirty Fix #54741 Fix unreported CRS not restored on cancel after change --- src/gui/vector/qgsvectorlayerproperties.cpp | 15 +++++++++++++-- src/gui/vector/qgsvectorlayerproperties.h | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/gui/vector/qgsvectorlayerproperties.cpp b/src/gui/vector/qgsvectorlayerproperties.cpp index 392d713fbed9..6cb01d0189d8 100644 --- a/src/gui/vector/qgsvectorlayerproperties.cpp +++ b/src/gui/vector/qgsvectorlayerproperties.cpp @@ -120,6 +120,8 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( connect( buttonAddMetadataUrl, &QPushButton::clicked, this, &QgsVectorLayerProperties::addMetadataUrl ); connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsVectorLayerProperties::showHelp ); + mProjectDirtyBlocker = std::make_unique( QgsProject::instance() ); + // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left), // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots @@ -554,6 +556,7 @@ void QgsVectorLayerProperties::syncToLayer() // populate the general information mLayerOrigNameLineEdit->setText( mLayer->name() ); mBackupCrs = mLayer->crs(); + //see if we are dealing with a pg layer here mSubsetGroupBox->setEnabled( true ); txtSubsetSQL->setText( mLayer->subsetString() ); @@ -937,7 +940,10 @@ void QgsVectorLayerProperties::apply() mLayer->triggerRepaint(); // notify the project we've made a change + mProjectDirtyBlocker.reset(); QgsProject::instance()->setDirty( true ); + mProjectDirtyBlocker = std::make_unique( QgsProject::instance() ); + } void QgsVectorLayerProperties::rollback() @@ -963,10 +969,15 @@ void QgsVectorLayerProperties::rollback() mLayer->setSubsetString( mOriginalSubsetSQL ); } + // Store it because QgsLayerPropertiesDialog::rollback() calls syncToLayer() which + // resets the backupCrs + const QgsCoordinateReferenceSystem backupCrs { mBackupCrs }; + QgsLayerPropertiesDialog::rollback(); - if ( mBackupCrs != mLayer->crs() ) - mLayer->setCrs( mBackupCrs ); + if ( backupCrs != mLayer->crs() ) + mLayer->setCrs( backupCrs ); + } void QgsVectorLayerProperties::pbnQueryBuilder_clicked() diff --git a/src/gui/vector/qgsvectorlayerproperties.h b/src/gui/vector/qgsvectorlayerproperties.h index 0a25805d023c..b0144ae6768b 100644 --- a/src/gui/vector/qgsvectorlayerproperties.h +++ b/src/gui/vector/qgsvectorlayerproperties.h @@ -255,6 +255,8 @@ class GUI_EXPORT QgsVectorLayerProperties : public QgsLayerPropertiesDialog, pri QgsCoordinateReferenceSystem mBackupCrs; + std::unique_ptr mProjectDirtyBlocker; + void initMapTipPreview(); QgsWebView *mMapTipPreview = nullptr; From ce71ecde7323d25422cf8740df49f186688ae525 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 28 Sep 2023 09:16:54 +0200 Subject: [PATCH 104/151] LAYOUT attr table widget width localization Fix #54204 --- src/gui/layout/qgslayoutattributeselectiondialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/layout/qgslayoutattributeselectiondialog.cpp b/src/gui/layout/qgslayoutattributeselectiondialog.cpp index e375d0cbd51d..7c7a8ea1a5d4 100644 --- a/src/gui/layout/qgslayoutattributeselectiondialog.cpp +++ b/src/gui/layout/qgslayoutattributeselectiondialog.cpp @@ -149,7 +149,7 @@ QVariant QgsLayoutAttributeTableColumnModelBase::data( const QModelIndex &index, { if ( role == Qt::DisplayRole ) { - return column.width() <= 0 ? tr( "Automatic" ) : tr( "%1 mm" ).arg( column.width(), 0, 'f', 2 ); + return column.width() <= 0 ? tr( "Automatic" ) : tr( "%L1 mm" ).arg( column.width(), 0, 'f', 2 ); } else { From 859a851e394360819d7e04bd401508d31ea257b5 Mon Sep 17 00:00:00 2001 From: Martin Dobias Date: Thu, 28 Sep 2023 17:06:16 +0200 Subject: [PATCH 105/151] Allow GPU memory limit configuration + show a warning when limit got hit --- python/3d/auto_generated/qgs3dmapscene.sip.in | 7 +++ src/3d/chunks/qgschunkedentity_p.cpp | 29 ++------- src/3d/chunks/qgschunkedentity_p.h | 15 ----- src/3d/qgs3dmapscene.cpp | 4 ++ src/3d/qgs3dmapscene.h | 7 +++ src/3d/qgs3dmapsceneentity_p.h | 26 +++++++- src/3d/qgs3dutils.cpp | 25 ++++++++ src/3d/qgs3dutils.h | 7 +++ src/app/3d/qgs3dmapcanvaswidget.cpp | 19 ++++++ src/app/3d/qgs3dmapcanvaswidget.h | 4 ++ src/app/3d/qgs3doptions.cpp | 4 ++ src/ui/3d/qgs3doptionsbase.ui | 59 +++++++++++++++---- 12 files changed, 155 insertions(+), 51 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index dc131a15b6bd..531d0679370f 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -167,6 +167,13 @@ Emitted when the FPS counter is activated or deactivated Emitted when the viewed 2D extent seen by the 3D camera has changed .. versionadded:: 3.26 +%End + + void gpuMemoryLimitReached(); +%Docstring +Emitted when one of the entities reaches its GPU memory limit +and it is not possible to lower the GPU memory use by unloading +data that's not currently needed. %End public slots: diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 3e4f770a3382..b5a5341cd99b 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -17,13 +17,6 @@ #include #include -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -#include -typedef Qt3DRender::QBuffer Qt3DQBuffer; -#else -#include -typedef Qt3DCore::QBuffer Qt3DQBuffer; -#endif #include "qgs3dutils.h" #include "qgschunkboundsentity_p.h" @@ -226,9 +219,12 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneState &state ) int QgsChunkedEntity::unloadNodes() { - double usedGpuMemory = QgsChunkedEntity::calculateEntityGpuMemorySize( this ); + double usedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); if ( usedGpuMemory <= mGpuMemoryLimit ) + { + setHasReachedGpuMemoryLimit( false ); return 0; + } QgsDebugMsgLevel( QStringLiteral( "Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 ); @@ -247,7 +243,7 @@ int QgsChunkedEntity::unloadNodes() { QgsChunkListEntry *entryPrev = entry->prev; mReplacementQueue->takeEntry( entry ); - usedGpuMemory -= QgsChunkedEntity::calculateEntityGpuMemorySize( entry->chunk->entity() ); + usedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() ); mActiveNodes.removeOne( entry->chunk ); entry->chunk->unloadChunk(); // also deletes the entry ++unloaded; @@ -261,6 +257,7 @@ int QgsChunkedEntity::unloadNodes() if ( usedGpuMemory > mGpuMemoryLimit ) { + setHasReachedGpuMemoryLimit( true ); QgsDebugMsgLevel( QStringLiteral( "Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 ); } @@ -738,20 +735,6 @@ void QgsChunkedEntity::cancelActiveJobs() } } -double QgsChunkedEntity::calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ) -{ - long long usedGpuMemory = 0; - for ( Qt3DQBuffer *buffer : entity->findChildren() ) - { - usedGpuMemory += buffer->data().size(); - } - for ( Qt3DRender::QTexture2D *tex : entity->findChildren() ) - { - // TODO : lift the assumption that the texture is RGBA - usedGpuMemory += tex->width() * tex->height() * 4; - } - return usedGpuMemory / 1024.0 / 1024.0; -} QVector QgsChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const { diff --git a/src/3d/chunks/qgschunkedentity_p.h b/src/3d/chunks/qgschunkedentity_p.h index d0ca05a682dc..c2159a6b9b4a 100644 --- a/src/3d/chunks/qgschunkedentity_p.h +++ b/src/3d/chunks/qgschunkedentity_p.h @@ -93,20 +93,6 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity //! Returns the root node of the whole quadtree hierarchy of nodes QgsChunkNode *rootNode() const { return mRootNode; } - /** - * Sets the limit of the GPU memory used to render the entity - * \since QGIS 3.26 - */ - void setGpuMemoryLimit( double gpuMemoryLimit ) { mGpuMemoryLimit = gpuMemoryLimit; } - - /** - * Returns the limit of the GPU memory used to render the entity in megabytes - * \since QGIS 3.26 - */ - double gpuMemoryLimit() const { return mGpuMemoryLimit; } - - static double calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ); - /** * Checks if \a ray intersects the entity by using the specified parameters in \a context and returns information about the hits. * This method is typically used by map tools that need to identify the exact location on a 3d entity that the mouse cursor points at, @@ -182,7 +168,6 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity bool mIsValid = true; int mPrimitivesBudget = std::numeric_limits::max(); - double mGpuMemoryLimit = 500.0; // in megabytes }; /// @endcond diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 4775e7dcb3ae..596c0458210b 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -371,6 +371,8 @@ void Qgs3DMapScene::updateScene() for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) { entity->handleSceneUpdate( sceneState_( mEngine ) ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); } updateSceneState(); @@ -446,6 +448,8 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) { QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 ); entity->handleSceneUpdate( sceneState_( mEngine ) ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); } } diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index c9bf64d7d3b0..332875258f1d 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -230,6 +230,13 @@ class _3D_EXPORT Qgs3DMapScene : public QObject */ void viewed2DExtentFrom3DChanged( QVector extent ); + /** + * Emitted when one of the entities reaches its GPU memory limit + * and it is not possible to lower the GPU memory use by unloading + * data that's not currently needed. + */ + void gpuMemoryLimitReached(); + public slots: //! Updates the temporale entities void updateTemporal(); diff --git a/src/3d/qgs3dmapsceneentity_p.h b/src/3d/qgs3dmapsceneentity_p.h index 8413ad4bc041..65db6ff93fee 100644 --- a/src/3d/qgs3dmapsceneentity_p.h +++ b/src/3d/qgs3dmapsceneentity_p.h @@ -32,6 +32,7 @@ #include #include "qgsrange.h" +#include "qgssettings.h" #define SIP_NO_FILE @@ -48,7 +49,10 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Constructs a chunked entity Qgs3DMapSceneEntity( Qt3DCore::QNode *parent = nullptr ) : Qt3DCore::QEntity( parent ) - {} + { + const QgsSettings settings; + mGpuMemoryLimit = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); + } //! Records some bits about the scene (context for handleSceneUpdate() method) struct SceneState @@ -71,12 +75,32 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Returns the near to far plane range for the entity using the specified \a viewMatrix virtual QgsRange getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const { Q_UNUSED( viewMatrix ) return QgsRange( 1e9, 0 ); } + + //! Sets the limit of the GPU memory used to render the entity + void setGpuMemoryLimit( double gpuMemoryLimit ) { mGpuMemoryLimit = gpuMemoryLimit; } + + //! Returns the limit of the GPU memory used to render the entity in megabytes + double gpuMemoryLimit() const { return mGpuMemoryLimit; } + + //! Returns whether the entity has reached GPU memory limit + bool hasReachedGpuMemoryLimit() const { return mHasReachedGpuMemoryLimit; } + + protected: + //! Sets whether the GPU memory limit has been reached + void setHasReachedGpuMemoryLimit( bool reached ) { mHasReachedGpuMemoryLimit = reached; } + signals: //! Emitted when the number of pending jobs changes (some jobs have finished or some jobs have been just created) void pendingJobsCountChanged(); //! Emitted when a new 3D entity has been created. Other components can use that to do extra work void newEntityCreated( Qt3DCore::QEntity *entity ); + + protected: + //! Limit how much GPU memory this entity can use + double mGpuMemoryLimit = 500.0; // in megabytes + //! Whether the entity is currently over the GPU memory limit (used to report a warning to the user) + bool mHasReachedGpuMemoryLimit = false; }; /// @endcond diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 522a7d8f0f79..777331abab6e 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -48,6 +48,14 @@ #include #include +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +typedef Qt3DRender::QBuffer Qt3DQBuffer; +#else +#include +typedef Qt3DCore::QBuffer Qt3DQBuffer; +#endif + // declared here as Qgs3DTypes has no cpp file const char *Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG = "PROP_NAME_3D_RENDERER_FLAG"; @@ -143,6 +151,23 @@ QImage Qgs3DUtils::captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMa return resImage; } + +double Qgs3DUtils::calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ) +{ + long long usedGpuMemory = 0; + for ( Qt3DQBuffer *buffer : entity->findChildren() ) + { + usedGpuMemory += buffer->data().size(); + } + for ( Qt3DRender::QTexture2D *tex : entity->findChildren() ) + { + // TODO : lift the assumption that the texture is RGBA + usedGpuMemory += tex->width() * tex->height() * 4; + } + return usedGpuMemory / 1024.0 / 1024.0; +} + + bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings, Qgs3DMapSettings &mapSettings, int framesPerSecond, diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index a492bbc4c7ae..3504e5af6646 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -73,6 +73,13 @@ class _3D_EXPORT Qgs3DUtils */ static QImage captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ); + /** + * Calculates approximate usage of GPU memory by an entity + * \return GPU memory usage in megabytes + * \since QGIS 3.34 + */ + static double calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ); + /** * Captures 3D animation frames to the selected folder * diff --git a/src/app/3d/qgs3dmapcanvaswidget.cpp b/src/app/3d/qgs3dmapcanvaswidget.cpp index e8249889da87..1d27495f09b5 100644 --- a/src/app/3d/qgs3dmapcanvaswidget.cpp +++ b/src/app/3d/qgs3dmapcanvaswidget.cpp @@ -228,6 +228,9 @@ Qgs3DMapCanvasWidget::Qgs3DMapCanvasWidget( const QString &name, bool isDocked ) mAnimationWidget = new Qgs3DAnimationWidget( this ); mAnimationWidget->setVisible( false ); + mMessageBar = new QgsMessageBar( this ); + mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ); + QHBoxLayout *topLayout = new QHBoxLayout; topLayout->setContentsMargins( 0, 0, 0, 0 ); topLayout->setSpacing( style()->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ) ); @@ -251,6 +254,7 @@ Qgs3DMapCanvasWidget::Qgs3DMapCanvasWidget( const QString &name, bool isDocked ) layout->setContentsMargins( 0, 0, 0, 0 ); layout->setSpacing( 0 ); layout->addLayout( topLayout ); + layout->addWidget( mMessageBar ); layout->addWidget( mCanvas ); layout->addWidget( mAnimationWidget ); @@ -366,6 +370,7 @@ void Qgs3DMapCanvasWidget::setMapSettings( Qgs3DMapSettings *map ) mCanvas->setMap( map ); connect( mCanvas->scene(), &Qgs3DMapScene::totalPendingJobsCountChanged, this, &Qgs3DMapCanvasWidget::onTotalPendingJobsCountChanged ); + connect( mCanvas->scene(), &Qgs3DMapScene::gpuMemoryLimitReached, this, &Qgs3DMapCanvasWidget::onGpuMemoryLimitReached ); mAnimationWidget->setCameraController( mCanvas->scene()->cameraController() ); mAnimationWidget->setMap( map ); @@ -648,3 +653,17 @@ void Qgs3DMapCanvasWidget::onExtentChanged() mViewExtentHighlight->closePoints(); } } + +void Qgs3DMapCanvasWidget::onGpuMemoryLimitReached() +{ + // let's report this issue just once, rather than spamming user if this happens repeatedly + if ( mGpuMemoryLimitReachedReported ) + return; + + const QgsSettings settings; + double memLimit = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); + mMessageBar->pushMessage( tr( "A map layer has used all graphics memory allowed (%1 MB). " + "You may want to lower the amount of detail in the scene, or increase the limit in the options." ) + .arg( memLimit ), Qgis::MessageLevel::Warning ); + mGpuMemoryLimitReachedReported = true; +} diff --git a/src/app/3d/qgs3dmapcanvaswidget.h b/src/app/3d/qgs3dmapcanvaswidget.h index f9a5cbbf2ab6..5be3b9d45d56 100644 --- a/src/app/3d/qgs3dmapcanvaswidget.h +++ b/src/app/3d/qgs3dmapcanvaswidget.h @@ -37,6 +37,7 @@ class Qgs3DMapToolIdentify; class Qgs3DMapToolMeasureLine; class QgsMapCanvas; class QgsDockableWidgetHelper; +class QgsMessageBar; class QgsRubberBand; class APP_EXPORT Qgs3DMapCanvasWidget : public QWidget @@ -92,6 +93,7 @@ class APP_EXPORT Qgs3DMapCanvasWidget : public QWidget void onViewed2DExtentFrom3DChanged( QVector extent ); void onViewFrustumVisualizationEnabledChanged(); void onExtentChanged(); + void onGpuMemoryLimitReached(); private: QString mCanvasName; @@ -121,6 +123,8 @@ class APP_EXPORT Qgs3DMapCanvasWidget : public QWidget QObjectUniquePtr< QgsRubberBand > mViewFrustumHighlight; QObjectUniquePtr< QgsRubberBand > mViewExtentHighlight; QPointer mConfigureDialog; + QgsMessageBar *mMessageBar = nullptr; + bool mGpuMemoryLimitReachedReported = false; }; #endif // QGS3DMAPCANVASWIDGET_H diff --git a/src/app/3d/qgs3doptions.cpp b/src/app/3d/qgs3doptions.cpp index ce1fefd2b1ab..ad590bcd5701 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -55,6 +55,8 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent ) mCameraMovementSpeed->setValue( settings.value( QStringLiteral( "map3d/defaultMovementSpeed" ), 5, QgsSettings::App ).toDouble() ); spinCameraFieldOfView->setValue( settings.value( QStringLiteral( "map3d/defaultFieldOfView" ), 45, QgsSettings::App ).toInt() ); + + mGpuMemoryLimit->setValue( settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble() ); } void Qgs3DOptionsWidget::apply() @@ -65,6 +67,8 @@ void Qgs3DOptionsWidget::apply() settings.setValue( QStringLiteral( "map3d/defaultProjection" ), static_cast< Qt3DRender::QCameraLens::ProjectionType >( cboCameraProjectionType->currentData().toInt() ), QgsSettings::App ); settings.setValue( QStringLiteral( "map3d/defaultMovementSpeed" ), mCameraMovementSpeed->value(), QgsSettings::App ); settings.setValue( QStringLiteral( "map3d/defaultFieldOfView" ), spinCameraFieldOfView->value(), QgsSettings::App ); + + settings.setValue( QStringLiteral( "map3d/gpuMemoryLimit" ), mGpuMemoryLimit->value(), QgsSettings::App ); } diff --git a/src/ui/3d/qgs3doptionsbase.ui b/src/ui/3d/qgs3doptionsbase.ui index 0703459f6ed1..ed5964d29772 100644 --- a/src/ui/3d/qgs3doptionsbase.ui +++ b/src/ui/3d/qgs3doptionsbase.ui @@ -10,7 +10,7 @@ 0 0 677 - 298 + 383 @@ -47,7 +47,7 @@ 0 0 677 - 298 + 383 @@ -63,6 +63,19 @@ 0 + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -140,17 +153,39 @@ - - - Qt::Vertical - - - - 20 - 40 - + + + Graphics Memory - + + + + + Allowed memory per map layer + + + + + + + MB + + + 0 + + + 10.000000000000000 + + + 100000.000000000000000 + + + 100.000000000000000 + + + + + From 95e504f98e7df7edc290041cabcab1918381086b Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 27 Sep 2023 12:09:02 +0300 Subject: [PATCH 106/151] use center of the upper left pixel when writing world file (fix #41795) --- src/app/georeferencer/qgsgeorefmainwindow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/georeferencer/qgsgeorefmainwindow.cpp b/src/app/georeferencer/qgsgeorefmainwindow.cpp index 4992a892841a..e6752a1f2845 100644 --- a/src/app/georeferencer/qgsgeorefmainwindow.cpp +++ b/src/app/georeferencer/qgsgeorefmainwindow.cpp @@ -1730,13 +1730,15 @@ bool QgsGeoreferencerMainWindow::writeWorldFile( const QgsPointXY &origin, doubl pixelYSize *= std::cos( rotation ); } + // in world files we need to use center of the upper left pixel, not the corner + // see https://github.com/qgis/QGIS/issues/41795 QTextStream stream( &file ); stream << qgsDoubleToString( pixelXSize ) << Qt::endl << rotationX << Qt::endl << rotationY << Qt::endl << qgsDoubleToString( -pixelYSize ) << Qt::endl - << qgsDoubleToString( origin.x() ) << Qt::endl - << qgsDoubleToString( origin.y() ) << Qt::endl; + << qgsDoubleToString( origin.x() + pixelXSize / 2.0 ) << Qt::endl + << qgsDoubleToString( origin.y() - pixelYSize / 2.0 ) << Qt::endl; return true; } From e37e12aeb89b8c706b5921d3717c3966182d1a51 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Thu, 28 Sep 2023 08:44:18 +0300 Subject: [PATCH 107/151] add test for world file creation --- tests/src/app/testqgsgeoreferencer.cpp | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/src/app/testqgsgeoreferencer.cpp b/tests/src/app/testqgsgeoreferencer.cpp index ae437691bd18..689ee578e8de 100644 --- a/tests/src/app/testqgsgeoreferencer.cpp +++ b/tests/src/app/testqgsgeoreferencer.cpp @@ -51,6 +51,7 @@ class TestQgsGeoreferencer : public QObject void testListModel(); void testListModelCrs(); void testGdalCommands(); + void testWorldFile(); private: QgisApp *mQgisApp = nullptr; @@ -876,5 +877,34 @@ void TestQgsGeoreferencer::testGdalCommands() QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ) ); } +void TestQgsGeoreferencer::testWorldFile() +{ + QgsGeoreferencerMainWindow window; + window.openLayer( Qgis::LayerType::Raster, QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + QString worldFileName = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.wld" ); + + QVERIFY( window.writeWorldFile( QgsPointXY( 0, 0 ), 1.0, 1.0, 0 ) ); + + QFile file( worldFileName ); + QVERIFY( file.open( QIODevice::ReadOnly | QIODevice::Text ) ); + + QTextStream stream( &file ); + QVector values; + values.reserve( 6 ); + while ( !stream.atEnd() ) + { + values << stream.readLine().toDouble(); + } + file.close(); + QFile::remove( worldFileName ); + + QCOMPARE( values.at( 0 ), 1.0 ); + QCOMPARE( values.at( 1 ), 0 ); + QCOMPARE( values.at( 2 ), 0 ); + QCOMPARE( values.at( 3 ), -1.0 ); + QCOMPARE( values.at( 4 ), 0.5 ); // center of the origin pixel + QCOMPARE( values.at( 5 ), -0.5 ); +} + QGSTEST_MAIN( TestQgsGeoreferencer ) #include "testqgsgeoreferencer.moc" From b62347b9bcb5d824e90807e309e45c9c968741df Mon Sep 17 00:00:00 2001 From: Yoann Quenach de Quivillic Date: Mon, 25 Sep 2023 10:47:50 +0200 Subject: [PATCH 108/151] Fix #54683 - Map tips black scrollbars --- src/gui/qgsmaptip.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/qgsmaptip.cpp b/src/gui/qgsmaptip.cpp index 00bd2fac0ec1..887eb9a43f80 100644 --- a/src/gui/qgsmaptip.cpp +++ b/src/gui/qgsmaptip.cpp @@ -113,7 +113,12 @@ void QgsMapTip::showMapTip( QgsMapLayer *pLayer, { mWebView = new QgsWebView( pMapCanvas ); // Make the webwiew transparent - mWebView->setStyleSheet( QStringLiteral( "background:transparent;" ) ); + + // Setting the background color to 'transparent' does not play nice + // with webkit scrollbars, that are rendered as black rectangles (#54683) + QColor transparentColor = mWebView->palette().color( QPalette::Window ); + transparentColor.setAlpha( 0 ); + mWebView->setStyleSheet( QString( "background:%1;" ).arg( transparentColor.name( QColor::HexArgb ) ) ); #if WITH_QTWEBKIT From 9d837335a22119b4c8bcf04f85296803625ab91d Mon Sep 17 00:00:00 2001 From: Yoann Quenach de Quivillic Date: Mon, 25 Sep 2023 14:44:12 +0200 Subject: [PATCH 109/151] Tweak map tip max size --- src/gui/qgsmaptip.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/qgsmaptip.cpp b/src/gui/qgsmaptip.cpp index 887eb9a43f80..21f500ab52d6 100644 --- a/src/gui/qgsmaptip.cpp +++ b/src/gui/qgsmaptip.cpp @@ -174,9 +174,9 @@ void QgsMapTip::showMapTip( QgsMapLayer *pLayer, cursorOffset = static_cast< int >( std::ceil( scale * 32 ) ); } - // Ensures the map tip is never larger than half the map canvas minus the cursor size + margin (cursorOffset) - const int MAX_WIDTH = pMapCanvas->width() / 2 - cursorOffset; - const int MAX_HEIGHT = pMapCanvas->height() / 2; + // Ensures the map tip is never larger than the available space + const int MAX_WIDTH = std::max( pixelPosition.x(), pMapCanvas->width() - pixelPosition.x() ) - cursorOffset - 5; + const int MAX_HEIGHT = std::max( pixelPosition.y(), pMapCanvas->height() - pixelPosition.y() ) - 5; mWebView->setMaximumSize( MAX_WIDTH, MAX_HEIGHT ); From dbbbc0eade98c237597314555bc9dd909ac44666 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Sun, 24 Sep 2023 14:00:01 +0200 Subject: [PATCH 110/151] qgsphongmaterialwidget: Add an option to hide the opacity slider This will be used in the next commit. --- src/app/3d/qgsphongmaterialwidget.cpp | 33 ++++++++++++++++++++++++--- src/app/3d/qgsphongmaterialwidget.h | 9 +++++++- src/ui/3d/phongmaterialwidget.ui | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/app/3d/qgsphongmaterialwidget.cpp b/src/app/3d/qgsphongmaterialwidget.cpp index 84a3cc55188e..46552405fe4a 100644 --- a/src/app/3d/qgsphongmaterialwidget.cpp +++ b/src/app/3d/qgsphongmaterialwidget.cpp @@ -18,10 +18,13 @@ #include "qgsphongmaterialsettings.h" #include "qgis.h" -QgsPhongMaterialWidget::QgsPhongMaterialWidget( QWidget *parent ) +QgsPhongMaterialWidget::QgsPhongMaterialWidget( QWidget *parent, bool hasOpacity ) : QgsMaterialSettingsWidget( parent ) + , mHasOpacity( hasOpacity ) { setupUi( this ); + mOpacityWidget->setVisible( mHasOpacity ); + mLblOpacity->setVisible( mHasOpacity ); QgsPhongMaterialSettings defaultMaterial; setSettings( &defaultMaterial, nullptr ); @@ -33,7 +36,10 @@ QgsPhongMaterialWidget::QgsPhongMaterialWidget( QWidget *parent ) connect( mAmbientDataDefinedButton, &QgsPropertyOverrideButton::changed, this, &QgsPhongMaterialWidget::changed ); connect( mDiffuseDataDefinedButton, &QgsPropertyOverrideButton::changed, this, &QgsPhongMaterialWidget::changed ); connect( mSpecularDataDefinedButton, &QgsPropertyOverrideButton::changed, this, &QgsPhongMaterialWidget::changed ); - connect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsPhongMaterialWidget::changed ); + if ( mHasOpacity ) + { + connect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsPhongMaterialWidget::changed ); + } } QgsMaterialSettingsWidget *QgsPhongMaterialWidget::create() @@ -117,7 +123,8 @@ QgsAbstractMaterialSettings *QgsPhongMaterialWidget::settings() m->setAmbient( btnAmbient->color() ); m->setSpecular( btnSpecular->color() ); m->setShininess( spinShininess->value() ); - m->setOpacity( mOpacityWidget->opacity() ); + float opacity = mHasOpacity ? static_cast( mOpacityWidget->opacity() ) : 1.0f; + m->setOpacity( opacity ); mPropertyCollection.setProperty( QgsAbstractMaterialSettings::Diffuse, mDiffuseDataDefinedButton->toProperty() ); mPropertyCollection.setProperty( QgsAbstractMaterialSettings::Ambient, mAmbientDataDefinedButton->toProperty() ); @@ -126,3 +133,23 @@ QgsAbstractMaterialSettings *QgsPhongMaterialWidget::settings() return m.release(); } + +void QgsPhongMaterialWidget::setHasOpacity( const bool opacity ) +{ + if ( mHasOpacity == opacity ) + { + return; + } + + mHasOpacity = opacity; + mOpacityWidget->setVisible( mHasOpacity ); + mLblOpacity->setVisible( mHasOpacity ); + if ( mHasOpacity ) + { + connect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsPhongMaterialWidget::changed ); + } + else + { + disconnect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsPhongMaterialWidget::changed ); + } +} diff --git a/src/app/3d/qgsphongmaterialwidget.h b/src/app/3d/qgsphongmaterialwidget.h index e3ae7c4b47e9..82fc9b4e3738 100644 --- a/src/app/3d/qgsphongmaterialwidget.h +++ b/src/app/3d/qgsphongmaterialwidget.h @@ -28,8 +28,10 @@ class QgsPhongMaterialSettings; class QgsPhongMaterialWidget : public QgsMaterialSettingsWidget, private Ui::PhongMaterialWidget { Q_OBJECT + Q_PROPERTY( bool hasOpacity READ hasOpacity WRITE setHasOpacity ) + public: - explicit QgsPhongMaterialWidget( QWidget *parent = nullptr ); + explicit QgsPhongMaterialWidget( QWidget *parent = nullptr, bool hasOpacity = true ); static QgsMaterialSettingsWidget *create(); @@ -37,6 +39,11 @@ class QgsPhongMaterialWidget : public QgsMaterialSettingsWidget, private Ui::Pho void setSettings( const QgsAbstractMaterialSettings *settings, QgsVectorLayer *layer ) override; QgsAbstractMaterialSettings *settings() override; + bool hasOpacity() const { return mHasOpacity; } + void setHasOpacity( const bool opacity ); + + private: + bool mHasOpacity; //! whether to display the opacity slider }; #endif // QGSPHONGMATERIALWIDGET_H diff --git a/src/ui/3d/phongmaterialwidget.ui b/src/ui/3d/phongmaterialwidget.ui index 2f5da491c555..c0bb4e7643a6 100644 --- a/src/ui/3d/phongmaterialwidget.ui +++ b/src/ui/3d/phongmaterialwidget.ui @@ -124,7 +124,7 @@ - + Opacity From 116643eed6c7f548683abb7a81ea99b0f255cdef Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Sun, 24 Sep 2023 14:09:25 +0200 Subject: [PATCH 111/151] map3dconfigwidget: Hide the unsued terrain shading opacity option The terrain shading widget automatically got an opacity slider when `QgsPhongMaterialWidget` was updated to get transparency support. However, this parameter is not used by the terrain. This issue is fixed by hiding this slider. --- src/ui/3d/map3dconfigwidget.ui | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/3d/map3dconfigwidget.ui b/src/ui/3d/map3dconfigwidget.ui index 85929c8c9373..6bea22a8a2a4 100644 --- a/src/ui/3d/map3dconfigwidget.ui +++ b/src/ui/3d/map3dconfigwidget.ui @@ -447,7 +447,11 @@ 0 - + + + false + + From dce0a070813c41930a1bacb15d6a7396d89b2e91 Mon Sep 17 00:00:00 2001 From: aske-w <72822902+aske-w@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:01:00 +0200 Subject: [PATCH 112/151] Fix MD formatting in sec. 5.5 References to variables in paths without backticks (`) causes parts the paragraph to be interpreted as TeX math --- INSTALL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 4084c32c965a..84b6d028aeb3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -754,10 +754,10 @@ git clone git://github.com/qgis/QGIS.git ## 5.5. Configure the build CMake supports out of source build so we will create a 'build' dir for the -build process. OS X uses ${HOME}/Applications as a standard user app folder (it +build process. OS X uses `${HOME}/Applications` as a standard user app folder (it gives it the system app folder icon). If you have the correct permissions you -may want to build straight into your /Applications folder. The instructions -below assume you are building into a ${HOME}/Applications directory. +may want to build straight into your `/Applications` folder. The instructions +below assume you are building into a `${HOME}/Applications` directory. In a Terminal cd to the qgis source folder previously downloaded, then: From 3aba9514d9bb529b204f0d9b99351422145a5222 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 25 Sep 2023 13:56:28 +0300 Subject: [PATCH 113/151] append style name to the output file name when saving multiple styles (fix #46597) --- src/gui/vector/qgsvectorlayerproperties.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gui/vector/qgsvectorlayerproperties.cpp b/src/gui/vector/qgsvectorlayerproperties.cpp index 6cb01d0189d8..06395a1f160d 100644 --- a/src/gui/vector/qgsvectorlayerproperties.cpp +++ b/src/gui/vector/qgsvectorlayerproperties.cpp @@ -68,6 +68,7 @@ #include "qgsnative.h" #include "qgssubsetstringeditorproviderregistry.h" #include "qgsprovidersourcewidgetproviderregistry.h" +#include "qgsfileutils.h" #include "qgswebview.h" #include "qgswebframe.h" #if WITH_QTWEBKIT @@ -1286,14 +1287,16 @@ void QgsVectorLayerProperties::saveMultipleStylesAs() { QString message; const QString filePath { dlg.outputFilePath() }; - QString safePath { filePath }; + const QFileInfo fi { filePath }; + QString safePath { QString( filePath ).replace( fi.baseName(), + QStringLiteral( "%1_%2" ).arg( fi.baseName(), QgsFileUtils::stringToSafeFilename( styleName ) ) ) }; if ( styleIndex > 0 && stylesSelected.count( ) > 1 ) { int i = 1; while ( QFile::exists( safePath ) ) { - const QFileInfo fi { filePath }; - safePath = QString( filePath ).replace( '.' + fi.completeSuffix(), + const QFileInfo fi { safePath }; + safePath = QString( safePath ).replace( '.' + fi.completeSuffix(), QStringLiteral( "_%1.%2" ).arg( QString::number( i ), fi.completeSuffix() ) ); i++; } @@ -1332,6 +1335,7 @@ void QgsVectorLayerProperties::saveMultipleStylesAs() } else { + name += QStringLiteral( "_%1" ).arg( styleName ); QStringList ids, names, descriptions; mLayer->listStylesInDatabase( ids, names, descriptions, msgError ); int i = 1; From f107d51e9ea2aded423d2b04f49c8860dc29acab Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 10:25:36 +1000 Subject: [PATCH 114/151] Add clear value --- src/app/3d/qgs3doptions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/3d/qgs3doptions.cpp b/src/app/3d/qgs3doptions.cpp index ad590bcd5701..0582c16aba0a 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -56,6 +56,7 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent ) mCameraMovementSpeed->setValue( settings.value( QStringLiteral( "map3d/defaultMovementSpeed" ), 5, QgsSettings::App ).toDouble() ); spinCameraFieldOfView->setValue( settings.value( QStringLiteral( "map3d/defaultFieldOfView" ), 45, QgsSettings::App ).toInt() ); + mGpuMemoryLimit->setClearValue( 500 ); mGpuMemoryLimit->setValue( settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble() ); } From 3de675785a5a85b2e7cf0586b4f1733690246cca Mon Sep 17 00:00:00 2001 From: pathmapper Date: Mon, 25 Sep 2023 15:59:10 +0200 Subject: [PATCH 115/151] Use a precision of 7 for viewbox coordinates --- src/core/geocoding/qgsnominatimgeocoder.cpp | 5 +---- tests/src/python/test_qgsnominatimgeocoder.py | 7 +++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/geocoding/qgsnominatimgeocoder.cpp b/src/core/geocoding/qgsnominatimgeocoder.cpp index 445544419037..7d389c18ceff 100644 --- a/src/core/geocoding/qgsnominatimgeocoder.cpp +++ b/src/core/geocoding/qgsnominatimgeocoder.cpp @@ -152,10 +152,7 @@ QUrl QgsNominatimGeocoder::requestUrl( const QString &address, const QgsRectangl query.addQueryItem( QStringLiteral( "addressdetails" ), QStringLiteral( "1" ) ); if ( !bounds.isNull() && bounds.isFinite() ) { - query.addQueryItem( QStringLiteral( "viewbox" ), QStringLiteral( "%1,%2,%3,%4" ).arg( bounds.xMinimum() ) - .arg( bounds.yMinimum() ) - .arg( bounds.xMaximum() ) - .arg( bounds.yMaximum() ) ); + query.addQueryItem( QStringLiteral( "viewbox" ), bounds.toString( 7 ).replace( QStringLiteral( " : " ), QStringLiteral( "," ) ) ); } if ( !mCountryCodes.isEmpty() ) { diff --git a/tests/src/python/test_qgsnominatimgeocoder.py b/tests/src/python/test_qgsnominatimgeocoder.py index b84c1921b06f..188273466582 100644 --- a/tests/src/python/test_qgsnominatimgeocoder.py +++ b/tests/src/python/test_qgsnominatimgeocoder.py @@ -73,14 +73,17 @@ def test_basic(self): def test_url(self): geocoder = QgsNominatimGeocoder('') self.assertEqual(geocoder.requestUrl('20 green st, twaddlingham', QgsRectangle(3, 5, 6, 8)).toString(), - 'https://nominatim.qgis.org/search?format=json&addressdetails=1&viewbox=3,5,6,8&q=20 green st, twaddlingham') + 'https://nominatim.qgis.org/search?format=json&addressdetails=1&viewbox=3.0000000,5.0000000,6.0000000,8.0000000&q=20 green st, twaddlingham') + + self.assertEqual(geocoder.requestUrl('20 green st, twaddlingham', QgsRectangle(4.5112248, -9.01938e-06, 4.5112875, 9.01938e-06)).toString(), + 'https://nominatim.qgis.org/search?format=json&addressdetails=1&viewbox=4.5112248,-0.0000090,4.5112875,0.0000090&q=20 green st, twaddlingham') self.assertEqual(geocoder.requestUrl('20 green st, twaddlingham', QgsRectangle(float('-inf'), float('-inf'), float('inf'), float('inf'))).toString(), 'https://nominatim.qgis.org/search?format=json&addressdetails=1&q=20 green st, twaddlingham') geocoder = QgsNominatimGeocoder(countryCodes='ca,km', endpoint='https://my.server/search') self.assertEqual(geocoder.requestUrl('20 green st, twaddlingham', QgsRectangle(3, 5, 6, 8)).toString(), - 'https://my.server/search?format=json&addressdetails=1&viewbox=3,5,6,8&countrycodes=ca,km&q=20 green st, twaddlingham') + 'https://my.server/search?format=json&addressdetails=1&viewbox=3.0000000,5.0000000,6.0000000,8.0000000&countrycodes=ca,km&q=20 green st, twaddlingham') def test_json_to_result(self): geocoder = QgsNominatimGeocoder() From 784c844e30545cc628b240c6446a95799562d16e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 27 Sep 2023 11:51:40 +1000 Subject: [PATCH 116/151] Fix crash when certain symbol pages are open in style dock, eg categorized class symbol editors, and QGIS is closed or a new project opened The symbol ownership of QgsSymbolSelectorWidget is very messy, and we can't fix till 4.0. Workaround this by introducing a temporary API to transfer symbol ownership to the widget. --- .../symbology/qgssymbolselectordialog.sip.in | 2 + src/gui/qgssymbolbutton.cpp | 43 ++++++------------- src/gui/qgssymbolbutton.h | 4 +- .../qgscategorizedsymbolrendererwidget.cpp | 36 +++++----------- .../qgscategorizedsymbolrendererwidget.h | 4 +- .../qgsgraduatedsymbolrendererwidget.cpp | 27 +++--------- .../qgsgraduatedsymbolrendererwidget.h | 4 +- src/gui/symbology/qgssymbolselectordialog.cpp | 8 ++++ src/gui/symbology/qgssymbolselectordialog.h | 11 +++++ .../qgsvectortilebasicrendererwidget.cpp | 25 +++-------- .../qgsvectortilebasicrendererwidget.h | 4 +- 11 files changed, 68 insertions(+), 100 deletions(-) diff --git a/python/gui/auto_generated/symbology/qgssymbolselectordialog.sip.in b/python/gui/auto_generated/symbology/qgssymbolselectordialog.sip.in index 6a77ce12b684..3e99430b7a80 100644 --- a/python/gui/auto_generated/symbology/qgssymbolselectordialog.sip.in +++ b/python/gui/auto_generated/symbology/qgssymbolselectordialog.sip.in @@ -43,6 +43,8 @@ Symbol selector widget that can be used to select and build a symbol The ownership of the symbol is not transferred and must exist for the lifetime of the widget. %End + + QMenu *advancedMenu(); %Docstring Returns menu for "advanced" button - create it if doesn't exist and show the advanced button diff --git a/src/gui/qgssymbolbutton.cpp b/src/gui/qgssymbolbutton.cpp index 50e95f7a9e87..a14a8609a5d6 100644 --- a/src/gui/qgssymbolbutton.cpp +++ b/src/gui/qgssymbolbutton.cpp @@ -139,23 +139,23 @@ void QgsSymbolButton::showSettingsDialog() context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer.data() ) ); } - QgsSymbol *newSymbol = nullptr; + std::unique_ptr< QgsSymbol > newSymbol; if ( mSymbol ) { - newSymbol = mSymbol->clone(); + newSymbol.reset( mSymbol->clone() ); } else { switch ( mType ) { case Qgis::SymbolType::Marker: - newSymbol = QgsSymbol::defaultSymbol( Qgis::GeometryType::Point ); + newSymbol.reset( QgsSymbol::defaultSymbol( Qgis::GeometryType::Point ) ); break; case Qgis::SymbolType::Line: - newSymbol = QgsSymbol::defaultSymbol( Qgis::GeometryType::Line ); + newSymbol.reset( QgsSymbol::defaultSymbol( Qgis::GeometryType::Line ) ); break; case Qgis::SymbolType::Fill: - newSymbol = QgsSymbol::defaultSymbol( Qgis::GeometryType::Polygon ); + newSymbol.reset( QgsSymbol::defaultSymbol( Qgis::GeometryType::Polygon ) ); break; case Qgis::SymbolType::Hybrid: break; @@ -170,25 +170,20 @@ void QgsSymbolButton::showSettingsDialog() QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); if ( panel && panel->dockMode() ) { - QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, panel ); - d->setPanelTitle( mDialogTitle ); - d->setContext( symbolContext ); - connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget ); - connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector ); - panel->openPanel( d ); + QgsSymbolSelectorWidget *widget = QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::move( newSymbol ), QgsStyle::defaultStyle(), mLayer, panel ); + widget->setPanelTitle( mDialogTitle ); + widget->setContext( symbolContext ); + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolFromWidget( widget ); } ); + panel->openPanel( widget ); } else { - QgsSymbolSelectorDialog dialog( newSymbol, QgsStyle::defaultStyle(), mLayer, this ); + QgsSymbolSelectorDialog dialog( newSymbol.get(), QgsStyle::defaultStyle(), mLayer, this ); dialog.setWindowTitle( mDialogTitle ); dialog.setContext( symbolContext ); if ( dialog.exec() ) { - setSymbol( newSymbol ); - } - else - { - delete newSymbol; + setSymbol( newSymbol.release() ); } // reactivate button's window @@ -196,19 +191,9 @@ void QgsSymbolButton::showSettingsDialog() } } -void QgsSymbolButton::updateSymbolFromWidget() -{ - if ( QgsSymbolSelectorWidget *w = qobject_cast( sender() ) ) - setSymbol( w->symbol()->clone() ); -} - -void QgsSymbolButton::cleanUpSymbolSelector( QgsPanelWidget *container ) +void QgsSymbolButton::updateSymbolFromWidget( QgsSymbolSelectorWidget *widget ) { - QgsSymbolSelectorWidget *w = qobject_cast( container ); - if ( !w ) - return; - - delete w->symbol(); + setSymbol( widget->symbol()->clone() ); } QgsMapCanvas *QgsSymbolButton::mapCanvas() const diff --git a/src/gui/qgssymbolbutton.h b/src/gui/qgssymbolbutton.h index 2d2f2ee1d5d3..1e653562e9ed 100644 --- a/src/gui/qgssymbolbutton.h +++ b/src/gui/qgssymbolbutton.h @@ -30,6 +30,7 @@ class QgsPanelWidget; class QgsMessageBar; class QMimeData; class QgsSymbol; +class QgsSymbolSelectorWidget; /** * \ingroup gui @@ -304,8 +305,7 @@ class GUI_EXPORT QgsSymbolButton : public QToolButton private slots: void showSettingsDialog(); - void updateSymbolFromWidget(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); + void updateSymbolFromWidget( QgsSymbolSelectorWidget *widget ); /** * Creates the drop-down menu entries diff --git a/src/gui/symbology/qgscategorizedsymbolrendererwidget.cpp b/src/gui/symbology/qgscategorizedsymbolrendererwidget.cpp index 0acbed720dca..142d9f68f17e 100644 --- a/src/gui/symbology/qgscategorizedsymbolrendererwidget.cpp +++ b/src/gui/symbology/qgscategorizedsymbolrendererwidget.cpp @@ -816,13 +816,10 @@ void QgsCategorizedSymbolRendererWidget::changeCategorizedSymbol() std::unique_ptr newSymbol( mCategorizedSymbol->clone() ); if ( panel && panel->dockMode() ) { - // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the - // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel ); - dlg->setContext( mContext ); - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); + QgsSymbolSelectorWidget *widget = QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::move( newSymbol ), mStyle, mLayer, panel ); + widget->setContext( mContext ); + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } ); + openPanel( widget ); } else { @@ -873,12 +870,11 @@ void QgsCategorizedSymbolRendererWidget::changeCategorySymbol() QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); if ( panel && panel->dockMode() ) { - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol.release(), mStyle, mLayer, panel ); - dlg->setContext( mContext ); - dlg->setPanelTitle( category.label() ); - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); + QgsSymbolSelectorWidget *widget = QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::move( symbol ), mStyle, mLayer, panel ); + widget->setContext( mContext ); + widget->setPanelTitle( category.label() ); + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } ); + openPanel( widget ); } else { @@ -1274,19 +1270,9 @@ void QgsCategorizedSymbolRendererWidget::pasteSymbolToSelection() } } -void QgsCategorizedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) +void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ) { - QgsSymbolSelectorWidget *dlg = qobject_cast( container ); - if ( !dlg ) - return; - - delete dlg->symbol(); -} - -void QgsCategorizedSymbolRendererWidget::updateSymbolsFromWidget() -{ - QgsSymbolSelectorWidget *dlg = qobject_cast( sender() ); - mCategorizedSymbol.reset( dlg->symbol()->clone() ); + mCategorizedSymbol.reset( widget->symbol()->clone() ); applyChangeToSymbol(); } diff --git a/src/gui/symbology/qgscategorizedsymbolrendererwidget.h b/src/gui/symbology/qgscategorizedsymbolrendererwidget.h index fc4c60019b1d..fc03daa83f03 100644 --- a/src/gui/symbology/qgscategorizedsymbolrendererwidget.h +++ b/src/gui/symbology/qgscategorizedsymbolrendererwidget.h @@ -25,6 +25,7 @@ class QgsCategorizedSymbolRenderer; class QgsRendererCategory; +class QgsSymbolSelectorWidget; #include "ui_qgscategorizedsymbolrendererwidget.h" #include "qgis_gui.h" @@ -191,8 +192,7 @@ class GUI_EXPORT QgsCategorizedSymbolRendererWidget : public QgsRendererWidget, private slots: - void cleanUpSymbolSelector( QgsPanelWidget *container ); - void updateSymbolsFromWidget(); + void updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ); void updateSymbolsFromButton(); void dataDefinedSizeLegend(); diff --git a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp index 5eed51ec0cc4..67cc387363db 100644 --- a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp +++ b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.cpp @@ -962,19 +962,9 @@ void QgsGraduatedSymbolRendererWidget::setSymbolLevels( const QgsLegendSymbolLis emit widgetChanged(); } -void QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) +void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ) { - QgsSymbolSelectorWidget *dlg = qobject_cast( container ); - if ( !dlg ) - return; - - delete dlg->symbol(); -} - -void QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget() -{ - QgsSymbolSelectorWidget *dlg = qobject_cast( sender() ); - mGraduatedSymbol.reset( dlg->symbol()->clone() ); + mGraduatedSymbol.reset( widget->symbol()->clone() ); applyChangeToSymbol(); } @@ -1210,14 +1200,11 @@ void QgsGraduatedSymbolRendererWidget::changeRangeSymbol( int rangeIdx ) QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); if ( panel && panel->dockMode() ) { - // bit tricky here - the widget doesn't take ownership of the symbol. So we need it to last for the duration of the - // panel's existence. Accordingly, just kinda give it ownership here, and clean up in cleanUpSymbolSelector - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( newSymbol.release(), mStyle, mLayer, panel ); - dlg->setContext( mContext ); - dlg->setPanelTitle( range.label() ); - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsGraduatedSymbolRendererWidget::updateSymbolsFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsGraduatedSymbolRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); + QgsSymbolSelectorWidget *widget = QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::move( newSymbol ), mStyle, mLayer, panel ); + widget->setContext( mContext ); + widget->setPanelTitle( range.label() ); + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } ); + openPanel( widget ); } else { diff --git a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h index 211d838159bb..94acdeefd7f7 100644 --- a/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h +++ b/src/gui/symbology/qgsgraduatedsymbolrendererwidget.h @@ -31,6 +31,7 @@ #include "qgis_gui.h" +class QgsSymbolSelectorWidget; #ifndef SIP_RUN /// @cond PRIVATE @@ -144,8 +145,7 @@ class GUI_EXPORT QgsGraduatedSymbolRendererWidget : public QgsRendererWidget, pr void mSizeUnitWidget_changed(); void methodComboBox_currentIndexChanged( int ); void updateMethodParameters(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); - void updateSymbolsFromWidget(); + void updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ); void dataDefinedSizeLegend(); void changeGraduatedSymbol(); void selectionChanged( const QItemSelection &selected, const QItemSelection &deselected ); diff --git a/src/gui/symbology/qgssymbolselectordialog.cpp b/src/gui/symbology/qgssymbolselectordialog.cpp index b8c1da44fca3..dec8f4395a30 100644 --- a/src/gui/symbology/qgssymbolselectordialog.cpp +++ b/src/gui/symbology/qgssymbolselectordialog.cpp @@ -386,6 +386,14 @@ QgsSymbolSelectorWidget::QgsSymbolSelectorWidget( QgsSymbol *symbol, QgsStyle *s connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QList& layers ) > ( &QgsProject::layersWillBeRemoved ), this, &QgsSymbolSelectorWidget::layersAboutToBeRemoved ); } +QgsSymbolSelectorWidget *QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::unique_ptr symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent ) +{ + QgsSymbolSelectorWidget *widget = new QgsSymbolSelectorWidget( symbol.get(), style, vl, parent ); + // transfer ownership of symbol to widget, so that we are guaranteed it will last for the duration of the widget + widget->mOwnedSymbol = std::move( symbol ); + return widget; +} + QMenu *QgsSymbolSelectorWidget::advancedMenu() { if ( !mAdvancedMenu ) diff --git a/src/gui/symbology/qgssymbolselectordialog.h b/src/gui/symbology/qgssymbolselectordialog.h index 3ca23dc32448..b05496f79811 100644 --- a/src/gui/symbology/qgssymbolselectordialog.h +++ b/src/gui/symbology/qgssymbolselectordialog.h @@ -105,6 +105,16 @@ class GUI_EXPORT QgsSymbolSelectorWidget: public QgsPanelWidget, private Ui::Qgs */ QgsSymbolSelectorWidget( QgsSymbol *symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + // TODO QGIS 4.0 -- remove when normal constructor takes ownership + + /** + * Creates a QgsSymbolSelectorWidget which takes ownership of a symbol and maintains + * the ownership for the life of the widget. + * + * \note Not available in Python bindings. + */ + static QgsSymbolSelectorWidget *createWidgetWithSymbolOwnership( std::unique_ptr< QgsSymbol > symbol, QgsStyle *style, QgsVectorLayer *vl, QWidget *parent SIP_TRANSFERTHIS = nullptr ) SIP_SKIP; + //! Returns menu for "advanced" button - create it if doesn't exist and show the advanced button QMenu *advancedMenu(); @@ -258,6 +268,7 @@ class GUI_EXPORT QgsSymbolSelectorWidget: public QgsPanelWidget, private Ui::Qgs QgsStyle *mStyle = nullptr; QgsSymbol *mSymbol = nullptr; + std::unique_ptr< QgsSymbol > mOwnedSymbol; QMenu *mAdvancedMenu = nullptr; QAction *mLockColorAction = nullptr; QAction *mLockSelectionColorAction = nullptr; diff --git a/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp b/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp index b60ecb5293c4..3b49c006122d 100644 --- a/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp +++ b/src/gui/vectortile/qgsvectortilebasicrendererwidget.cpp @@ -504,12 +504,11 @@ void QgsVectorTileBasicRendererWidget::editStyleAtIndex( const QModelIndex &prox QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); if ( panel && panel->dockMode() ) { - QgsSymbolSelectorWidget *dlg = new QgsSymbolSelectorWidget( symbol.release(), QgsStyle::defaultStyle(), vectorLayer, panel ); - dlg->setContext( context ); - dlg->setPanelTitle( style.styleName() ); - connect( dlg, &QgsPanelWidget::widgetChanged, this, &QgsVectorTileBasicRendererWidget::updateSymbolsFromWidget ); - connect( dlg, &QgsPanelWidget::panelAccepted, this, &QgsVectorTileBasicRendererWidget::cleanUpSymbolSelector ); - openPanel( dlg ); + QgsSymbolSelectorWidget *widget = QgsSymbolSelectorWidget::createWidgetWithSymbolOwnership( std::move( symbol ), QgsStyle::defaultStyle(), vectorLayer, panel ); + widget->setContext( context ); + widget->setPanelTitle( style.styleName() ); + connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ] { updateSymbolsFromWidget( widget ); } ); + openPanel( widget ); } else { @@ -526,7 +525,7 @@ void QgsVectorTileBasicRendererWidget::editStyleAtIndex( const QModelIndex &prox } } -void QgsVectorTileBasicRendererWidget::updateSymbolsFromWidget() +void QgsVectorTileBasicRendererWidget::updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ) { const int index = mProxyModel->mapToSource( viewStyles->selectionModel()->currentIndex() ).row(); if ( index < 0 ) @@ -534,22 +533,12 @@ void QgsVectorTileBasicRendererWidget::updateSymbolsFromWidget() QgsVectorTileBasicRendererStyle style = mRenderer->style( index ); - QgsSymbolSelectorWidget *dlg = qobject_cast( sender() ); - style.setSymbol( dlg->symbol()->clone() ); + style.setSymbol( widget->symbol()->clone() ); mRenderer->setStyle( index, style ); emit widgetChanged(); } -void QgsVectorTileBasicRendererWidget::cleanUpSymbolSelector( QgsPanelWidget *container ) -{ - QgsSymbolSelectorWidget *dlg = qobject_cast( container ); - if ( !dlg ) - return; - - delete dlg->symbol(); -} - void QgsVectorTileBasicRendererWidget::removeStyle() { const QModelIndexList sel = viewStyles->selectionModel()->selectedIndexes(); diff --git a/src/gui/vectortile/qgsvectortilebasicrendererwidget.h b/src/gui/vectortile/qgsvectortilebasicrendererwidget.h index c80095a9a10a..2e87148eb894 100644 --- a/src/gui/vectortile/qgsvectortilebasicrendererwidget.h +++ b/src/gui/vectortile/qgsvectortilebasicrendererwidget.h @@ -33,6 +33,7 @@ class QgsVectorTileLayer; class QgsMapCanvas; class QgsMessageBar; class QgsVectorTileBasicRendererProxyModel; +class QgsSymbolSelectorWidget; /** * \ingroup gui @@ -59,8 +60,7 @@ class GUI_EXPORT QgsVectorTileBasicRendererWidget : public QgsMapLayerConfigWidg void editStyleAtIndex( const QModelIndex &index ); void removeStyle(); - void updateSymbolsFromWidget(); - void cleanUpSymbolSelector( QgsPanelWidget *container ); + void updateSymbolsFromWidget( QgsSymbolSelectorWidget *widget ); private: QPointer< QgsVectorTileLayer > mVTLayer; From 9337787bca02eee8ea588c34540e431e0bc4ed96 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 27 Sep 2023 14:02:22 +0300 Subject: [PATCH 117/151] enable Delete button if there is at least one user function (fix #51108) --- src/gui/qgsexpressionbuilderwidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index b79767ee55fd..1e57463c36fe 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -444,6 +444,7 @@ void QgsExpressionBuilderWidget::btnNewFile_pressed() if ( ok && !text.isEmpty() ) { newFunctionFile( text ); + btnRemoveFile->setEnabled( cmbFileNames->count() > 0 ); } } From a0edac855c993b2caf14b0a53ceda16243bbae1f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 17 Sep 2023 15:31:47 +0200 Subject: [PATCH 118/151] [GUI] VectorLayerSaveAsDialog: allow to select an existing FileGeodatabase (fixes #54566) This fix isn't totally satisfactory, because AFAICS there's no way in Qt FileDialog to both be able select an existing directory (.gdb) without entering into it, or select the parent of a new directory to be created. We'd need something between the GetFile or GetDirectory storage modes, although it is not clear how that could be implemented, without 2 separate buttons in the QtFileDialog (like "Select directory" and "Enter directory") The hack/fix here is to add to the (*.gdb *.GDB) filter an extra "gdb" file that matches the "gdb" file found in existing File Geodatabase. And remove it from the filename once it is selected. --- src/gui/ogr/qgsvectorlayersaveasdialog.cpp | 13 +++++++++++-- src/gui/qgsfilewidget.cpp | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index 352977b05948..d2c58cbf67db 100644 --- a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp +++ b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp @@ -432,14 +432,23 @@ void QgsVectorLayerSaveAsDialog::mFormatComboBox_currentIndexChanged( int idx ) Q_UNUSED( idx ) mFilename->setEnabled( true ); - mFilename->setFilter( QgsVectorFileWriter::filterForDriver( format() ) ); + QString filter = QgsVectorFileWriter::filterForDriver( format() ); + // A bit of hack to solve https://github.com/qgis/QGIS/issues/54566 + // to be able to select an existing File Geodatabase, we add in the filter + // the "gdb" file that is found in all File Geodatabase .gdb directory + // to allow the user to select it. We need to detect this particular case + // in QgsFileWidget::openFileDialog() to remove this gdb file from the + // selected filename + if ( format() == QLatin1String( "OpenFileGDB" ) || format() == QLatin1String( "FileGDB" ) ) + filter = QStringLiteral( "%1 (*.gdb *.GDB gdb)" ).arg( tr( "ESRI File Geodatabase" ) ); + mFilename->setFilter( filter ); // if output filename already defined we need to replace old suffix // to avoid double extensions like .gpkg.shp if ( !mFilename->filePath().isEmpty() ) { const thread_local QRegularExpression rx( "\\.(.*?)[\\s]" ); - const QString ext = rx.match( QgsVectorFileWriter::filterForDriver( format() ) ).captured( 1 ); + const QString ext = rx.match( filter ).captured( 1 ); if ( !ext.isEmpty() ) { QFileInfo fi( mFilename->filePath() ); diff --git a/src/gui/qgsfilewidget.cpp b/src/gui/qgsfilewidget.cpp index 9b21f54e2112..53e520520ca3 100644 --- a/src/gui/qgsfilewidget.cpp +++ b/src/gui/qgsfilewidget.cpp @@ -337,6 +337,18 @@ void QgsFileWidget::openFileDialog() // make sure filename ends with filter. This isn't automatically done by // getSaveFileName on some platforms (e.g. gnome) fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter ); + + // A bit of hack to solve https://github.com/qgis/QGIS/issues/54566 + // to be able to select an existing File Geodatabase, we add in the filter + // the "gdb" file that is found in all File Geodatabase .gdb directory + // to allow the user to select it. We now need to remove this gdb file + // (which became gdb.gdb due to above logic) from the selected filename + if ( mFilter.contains( QLatin1String( "(*.gdb *.GDB gdb)" ) ) && + ( fileName.endsWith( QLatin1String( "/gdb.gdb" ) ) || + fileName.endsWith( QLatin1String( "\\gdb.gdb" ) ) ) ) + { + fileName.chop( static_cast( strlen( "/gdb.gdb" ) ) ); + } } break; } From 03100afb93a7de1991c0186ffff8eed4f3cc268c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 15 Sep 2023 19:43:46 +0200 Subject: [PATCH 119/151] [OAPIF] Fix wrong extent when not advertized in collection metadata and storageCrs != CRS84, and fix potential infinite feature query when featureCount not advertized by server Fixes https://lists.osgeo.org/pipermail/qgis-user/2023-September/053400.html --- src/providers/wfs/oapif/qgsoapifprovider.cpp | 19 ++++++- tests/src/python/test_provider_oapif.py | 58 ++++++++++++++------ 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/providers/wfs/oapif/qgsoapifprovider.cpp b/src/providers/wfs/oapif/qgsoapifprovider.cpp index 070458b7802a..9a2e2644126f 100644 --- a/src/providers/wfs/oapif/qgsoapifprovider.cpp +++ b/src/providers/wfs/oapif/qgsoapifprovider.cpp @@ -267,6 +267,21 @@ bool QgsOapifProvider::init() if ( mShared->mCapabilityExtent.isNull() ) { mShared->mCapabilityExtent = itemsRequest.bbox(); + if ( !mShared->mCapabilityExtent.isNull() ) + { + QgsCoordinateReferenceSystem defaultCrs = + QgsCoordinateReferenceSystem::fromOgcWmsCrs( + QgsOapifProvider::OAPIF_PROVIDER_DEFAULT_CRS ); + if ( defaultCrs != mShared->mSourceCrs ) + { + QgsCoordinateTransform ct( defaultCrs, mShared->mSourceCrs, transformContext() ); + ct.setBallparkTransformsAreAppropriate( true ); + QgsDebugMsgLevel( "before ext:" + mShared->mCapabilityExtent.toString(), 4 ); + mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent ); + QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 ); + } + } + } mShared->mFields = itemsRequest.fields(); @@ -336,12 +351,14 @@ long long QgsOapifProvider::featureCount() const QgsFeature f; QgsFeatureRequest request; request.setNoAttributes(); + constexpr int MAX_FEATURES = 1000; + request.setLimit( MAX_FEATURES + 1 ); auto iter = getFeatures( request ); long long count = 0; bool countExact = true; while ( iter.nextFeature( f ) ) { - if ( count == 1000 ) // to avoid too long processing time + if ( count == MAX_FEATURES ) // to avoid too long processing time { countExact = false; break; diff --git a/tests/src/python/test_provider_oapif.py b/tests/src/python/test_provider_oapif.py index 7131b1f463a6..37edfcf68af7 100644 --- a/tests/src/python/test_provider_oapif.py +++ b/tests/src/python/test_provider_oapif.py @@ -143,6 +143,8 @@ def add_params(x, y): } } } + if bbox is None: + del collection["extent"] if storageCrs: collection["storageCrs"] = storageCrs if crsList: @@ -1268,32 +1270,27 @@ def testCRS2056(self): self.assertEqual(source.sourceCrs().authid(), 'OGC:CRS84') - def testFeatureCountFallback(self): + def testFeatureCountFallbackAndNoBboxInCollection(self): # On Windows we must make sure that any backslash in the path is # replaced by a forward slash so that QUrl can process it basetestpath = tempfile.mkdtemp().replace('\\', '/') endpoint = basetestpath + '/fake_qgis_http_endpoint_feature_count_fallback' - create_landing_page_api_collection(endpoint, storageCrs="http://www.opengis.net/def/crs/EPSG/0/2056") + create_landing_page_api_collection(endpoint, storageCrs="http://www.opengis.net/def/crs/EPSG/0/2056", bbox=None) items = { "type": "FeatureCollection", - "features": [ - {"type": "Feature", "id": "feat.1", - "properties": {"pk": 1, "cnt": 100, "name": "Orange", "name2": "oranGe", "num_char": "1", "dt": "2020-05-03 12:13:14", "date": "2020-05-03", "time": "12:13:14"}, - "geometry": {"type": "Point", "coordinates": [2510100, 1155050]}}, - {"type": "Feature", "id": "feat.2", - "properties": {"pk": 2, "cnt": 200, "name": "Apple", "name2": "Apple", "num_char": "2", "dt": "2020-05-04 12:14:14", "date": "2020-05-04", "time": "12:14:14"}, - "geometry": {"type": "Point", "coordinates": [2511250, 1154600]}}, - {"type": "Feature", "id": "feat.3", - "properties": {"pk": 4, "cnt": 400, "name": "Honey", "name2": "Honey", "num_char": "4", "dt": "2021-05-04 13:13:14", "date": "2021-05-04", "time": "13:13:14"}, - "geometry": {"type": "Point", "coordinates": [2511260, 1154610]}}, - {"type": "Feature", "id": "feat.4", - "properties": {"pk": 5, "cnt": -200, "name": None, "name2": "NuLl", "num_char": "5", "dt": "2020-05-04 12:13:14", "date": "2020-05-02", "time": "12:13:01"}, - "geometry": {"type": "Point", "coordinates": [2511270, 1154620]}} + "features": [], + "links": [ + # should not be hit + {"href": "http://" + endpoint + "/next_page", "rel": "next"} ] } + for i in range(10): + items["features"].append({"type": "Feature", "id": f"feat.{i}", + "properties": {}, + "geometry": {"type": "Point", "coordinates": [23, 63]}}) # first items with open(sanitize(endpoint, '/collections/mycollection/items?limit=1&' + ACCEPT_ITEMS), 'wb') as f: @@ -1304,15 +1301,44 @@ def testFeatureCountFallback(self): f.write(json.dumps(items).encode('UTF-8')) # real page + + items = { + "type": "FeatureCollection", + "features": [], + "links": [ + # should not be hit + {"href": "http://" + endpoint + "/next_page", "rel": "next"} + ] + } + for i in range(1001): + items["features"].append({"type": "Feature", "id": f"feat.{i}", + "properties": {}, + "geometry": None}) + with open(sanitize(endpoint, '/collections/mycollection/items?limit=1000&crs=http://www.opengis.net/def/crs/EPSG/0/2056&' + ACCEPT_ITEMS), 'wb') as f: f.write(json.dumps(items).encode('UTF-8')) # Create test layer + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='mycollection'", 'test', 'OAPIF') assert vl.isValid() source = vl.dataProvider() - self.assertEqual(source.featureCount(), 4) + # Extent got from first fetched features + reference = QgsGeometry.fromRect( + QgsRectangle(3415684, 3094884, + 3415684, 3094884)) + vl_extent = QgsGeometry.fromRect(vl.extent()) + assert QgsGeometry.compare(vl_extent.asPolygon()[0], reference.asPolygon()[0], + 10), f'Expected {reference.asWkt()}, got {vl_extent.asWkt()}' + + app_log = QgsApplication.messageLog() + # signals should be emitted by application log + app_spy = QSignalSpy(app_log.messageReceived) + + self.assertEqual(source.featureCount(), 1000) + + self.assertEqual(len(app_spy), 0, list(app_spy)) def testFeatureInsertionDeletion(self): From 39c492a53322d755208527eb25463d889df5bdef Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 16 Sep 2023 01:37:08 +0200 Subject: [PATCH 120/151] [OAPIF] Catch ct.transformBoundingBox() exceptions in QgsOapifProvider initialization --- src/providers/wfs/oapif/qgsoapifprovider.cpp | 24 ++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/providers/wfs/oapif/qgsoapifprovider.cpp b/src/providers/wfs/oapif/qgsoapifprovider.cpp index 9a2e2644126f..653432e01637 100644 --- a/src/providers/wfs/oapif/qgsoapifprovider.cpp +++ b/src/providers/wfs/oapif/qgsoapifprovider.cpp @@ -229,8 +229,16 @@ bool QgsOapifProvider::init() QgsCoordinateTransform ct( collectionRequest->collection().mBboxCrs, mShared->mSourceCrs, transformContext() ); ct.setBallparkTransformsAreAppropriate( true ); QgsDebugMsgLevel( "before ext:" + mShared->mCapabilityExtent.toString(), 4 ); - mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent ); - QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 ); + try + { + mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent ); + QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 ); + } + catch ( const QgsCsException &e ) + { + QgsMessageLog::logMessage( tr( "Cannot compute layer extent: %1" ).arg( e.what() ), tr( "OAPIF" ) ); + mShared->mCapabilityExtent = QgsRectangle(); + } } // Merge contact info from /api @@ -277,8 +285,16 @@ bool QgsOapifProvider::init() QgsCoordinateTransform ct( defaultCrs, mShared->mSourceCrs, transformContext() ); ct.setBallparkTransformsAreAppropriate( true ); QgsDebugMsgLevel( "before ext:" + mShared->mCapabilityExtent.toString(), 4 ); - mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent ); - QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 ); + try + { + mShared->mCapabilityExtent = ct.transformBoundingBox( mShared->mCapabilityExtent ); + QgsDebugMsgLevel( "after ext:" + mShared->mCapabilityExtent.toString(), 4 ); + } + catch ( const QgsCsException &e ) + { + QgsMessageLog::logMessage( tr( "Cannot compute layer extent: %1" ).arg( e.what() ), tr( "OAPIF" ) ); + mShared->mCapabilityExtent = QgsRectangle(); + } } } From 1a638ef2657d400bdd68987c7e628f336aec1a22 Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Thu, 28 Sep 2023 20:34:12 +0200 Subject: [PATCH 121/151] [GRASS] Fix QgsProcessingParameterNumber in grass7.txt --- python/plugins/grassprovider/grass7.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/plugins/grassprovider/grass7.txt b/python/plugins/grassprovider/grass7.txt index c9c05585b14d..8c4e83d6c53b 100644 --- a/python/plugins/grassprovider/grass7.txt +++ b/python/plugins/grassprovider/grass7.txt @@ -68,12 +68,12 @@ QgsProcessingParameterFile|[name of GRASS parameter]|[description of parameter t - A numerical value -QgsProcessingParameterNumber|[name of GRASS parameter]|[description of parameter to show]|QgsProcessingParameterNumber.Integer or QgsProcessingParameterNumber.Double|[min value]|[max value]|[default value]|[True/False, indicating if the parameter is optional or not] +QgsProcessingParameterNumber|[name of GRASS parameter]|[description of parameter to show]|QgsProcessingParameterNumber.Integer or QgsProcessingParameterNumber.Double|[default value]|[True/False, indicating if the parameter is optional or not]|[min value]|[max value] "None" can be used for both min and max values to indicate that there is no lower or upper limit. -Example: QgsProcessingParameterNumber|levels|levels|QgsProcessingParameterNumber.Integer|1|256|32|False +Example: QgsProcessingParameterNumber|levels|levels|QgsProcessingParameterNumber.Integer|32|False|1|256 - A numerical range From 8a3ee8aa086ac9974c72fc507a31d2d1ba9a3f0d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 27 Sep 2023 15:07:25 +0200 Subject: [PATCH 122/151] [OGR provider] Fix issue when writing a multi-part multipolygon in a shapefile with GDAL >= 3.7 (fixes #54537) --- src/core/providers/ogr/qgsogrprovider.cpp | 9 +++++++++ tests/src/python/test_provider_shapefile.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index 468c07f15212..a52f6441398c 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -1386,6 +1386,15 @@ OGRGeometryH QgsOgrProvider::ConvertGeometryIfNecessary( OGRGeometryH hGeom ) return OGR_G_ForceToMultiLineString( hGeom ); } + if ( flattenLayerGeomType == wkbPolygon && flattenGeomType == wkbMultiPolygon && + mGDALDriverName == QLatin1String( "ESRI Shapefile" ) ) + { + // Do not force multipolygon to polygon for shapefiles, otherwise it will + // cause issues with GDAL 3.7 that does honour the topological intent of + // multipolygon + return hGeom; + } + return OGR_G_ForceTo( hGeom, layerGeomType, nullptr ); } diff --git a/tests/src/python/test_provider_shapefile.py b/tests/src/python/test_provider_shapefile.py index 161c286de483..9d0f3011ca0a 100644 --- a/tests/src/python/test_provider_shapefile.py +++ b/tests/src/python/test_provider_shapefile.py @@ -1114,6 +1114,24 @@ def testLayersOnSameOGRLayerWithAndWithoutFilter(self): assert QgsGeometry.compare(vl3_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001), f'Expected {reference.asWkt()}, got {vl3_extent.asWkt()}' + def testWritingMultiPolygon(self): + """Test that a MultiPolygon written to a Shape Polygon layer doesn't get converted to Polygon""" + + tmpfile = os.path.join(self.basetestpath, 'testWritingMultiPolygon.shp') + ds = osgeo.ogr.GetDriverByName('ESRI Shapefile').CreateDataSource(tmpfile) + ds.CreateLayer('testWritingMultiPolygon', geom_type=osgeo.ogr.wkbPolygon) + ds = None + + vl = QgsVectorLayer(tmpfile, 'test') + f = QgsFeature() + f.setAttributes([200]) + wkt = "MultiPolygon (((0 0, 0 1, 1 1, 0 0)),((10 0, 10 1, 11 1, 10 0)))" + f.setGeometry(QgsGeometry.fromWkt(wkt)) + vl.dataProvider().addFeatures([f]) + + f = next(vl.getFeatures()) + self.assertEqual(f.geometry().constGet().asWkt(), wkt) + if __name__ == '__main__': unittest.main() From 8793e659a19b9d12d1bb37448637fe328014a5ef Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 11:31:17 +1000 Subject: [PATCH 123/151] Apply suggestions from code review --- python/plugins/processing/algs/qgis/RasterCalculator.py | 2 +- src/analysis/processing/qgsalgorithmrastercalculator.cpp | 4 ++-- .../processing/qgsalgorithmvirtualrastercalculator.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/plugins/processing/algs/qgis/RasterCalculator.py b/python/plugins/processing/algs/qgis/RasterCalculator.py index 2ea965b5c8fc..71f8c4172a0c 100644 --- a/python/plugins/processing/algs/qgis/RasterCalculator.py +++ b/python/plugins/processing/algs/qgis/RasterCalculator.py @@ -90,7 +90,7 @@ def clone(self): self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT, self.tr('Output'))) def flags(self): - return super().flags() | QgsProcessingAlgorithm.FlagDeprecated | QgsProcessingAlgorithm.FlagNotAvailableInStandaloneTool + return super().flags() | QgsProcessingAlgorithm.FlagDeprecated def name(self): return 'rastercalculator' diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp index 217fd650c728..fe5dc79a2085 100644 --- a/src/analysis/processing/qgsalgorithmrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -132,7 +132,7 @@ QVariantMap QgsRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap &p QVector< QgsRasterCalculatorEntry > entries; for ( QgsMapLayer *layer : mLayers ) { - QgsRasterLayer *rLayer = static_cast( layer ); + QgsRasterLayer *rLayer = qobject_cast( layer ); if ( !rLayer ) { continue; @@ -271,7 +271,7 @@ QVariantMap QgsRasterCalculatorModelerAlgorithm::processAlgorithm( const QVarian int n = 0; for ( QgsMapLayer *layer : mLayers ) { - QgsRasterLayer *rLayer = static_cast( layer ); + QgsRasterLayer *rLayer = qobject_cast( layer ); if ( !rLayer ) { continue; diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp index 2a773507e926..72723ded24d3 100644 --- a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp @@ -112,7 +112,7 @@ QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVarian for ( const QgsMapLayer *layer : layers ) { - const QgsRasterLayer *rLayer = static_cast( layer ); + const QgsRasterLayer *rLayer = qobject_cast( layer ); if ( !rLayer ) { continue; @@ -251,7 +251,7 @@ QVariantMap QgsVirtualRasterCalculatorModelerAlgorithm::processAlgorithm( const int n = 0; for ( const QgsMapLayer *layer : layers ) { - const QgsRasterLayer *rLayer = static_cast( layer ); + const QgsRasterLayer *rLayer = qobject_cast( layer ); if ( !rLayer ) { continue; From b235eeedc30a88d38b82308d4e8844bb51db0abf Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 26 Sep 2023 14:45:37 +0200 Subject: [PATCH 124/151] GEOMETRY: fix regression on split multipart Fix #54155 --- src/core/geometry/qgsgeos.cpp | 33 +++++++++++++-------- tests/src/core/geometry/testqgsgeometry.cpp | 10 +++++++ tests/src/python/test_qgsgeometry.py | 10 +++++++ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/core/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index 92b4b8641fd3..4e3a890f42a0 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -1042,12 +1042,12 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co QgsMultiCurve lines; //For each part - for ( int i = 0; i < multiCurve->numGeometries(); ++i ) + for ( int geometryIndex = 0; geometryIndex < multiCurve->numGeometries(); ++geometryIndex ) { - const QgsLineString *line = qgsgeometry_cast( multiCurve->geometryN( i ) ); + const QgsLineString *line = qgsgeometry_cast( multiCurve->geometryN( geometryIndex ) ); if ( !line ) { - const QgsCurve *curve = qgsgeometry_cast( multiCurve->geometryN( i ) ); + const QgsCurve *curve = qgsgeometry_cast( multiCurve->geometryN( geometryIndex ) ); line = curve->curveToLine(); } if ( !line ) @@ -1056,14 +1056,23 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co } // we gather the intersection points and their distance from previous node grouped by segment QMap< int, QVector< QPair< double, QgsPoint > > >pointMap; - for ( int p = 0; p < splitPoints->numGeometries(); ++p ) + for ( int splitPointIndex = 0; splitPointIndex < splitPoints->numGeometries(); ++splitPointIndex ) { - const QgsPoint *intersectionPoint = splitPoints->pointN( p ); + const QgsPoint *intersectionPoint = splitPoints->pointN( splitPointIndex ); + QgsPoint segmentPoint2D; QgsVertexId nextVertex; // With closestSegment we only get a 2D point so we need to interpolate if we // don't want to lose Z data line->closestSegment( *intersectionPoint, segmentPoint2D, nextVertex ); + + // The intersection might belong to another part, skip it + // Note: cannot test for equality because of Z + if ( intersectionPoint->x() != segmentPoint2D.x() || intersectionPoint->y() != segmentPoint2D.y() ) + { + continue; + } + const QgsLineString segment = QgsLineString( line->pointN( nextVertex.vertex - 1 ), line->pointN( nextVertex.vertex ) ); const double distance = segmentPoint2D.distance( line->pointN( nextVertex.vertex - 1 ) ); @@ -1089,25 +1098,25 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co QgsLineString newLine; int nVertices = line->numPoints(); QgsPoint splitPoint; - for ( int j = 0; j < nVertices; ++j ) + for ( int vertexIndex = 0; vertexIndex < nVertices; ++vertexIndex ) { - QgsPoint currentPoint = line->pointN( j ); + QgsPoint currentPoint = line->pointN( vertexIndex ); newLine.addVertex( currentPoint ); - if ( pointMap.contains( j ) ) + if ( pointMap.contains( vertexIndex ) ) { // For each intersecting point - for ( int k = 0; k < pointMap[ j ].size(); ++k ) + for ( int k = 0; k < pointMap[ vertexIndex ].size(); ++k ) { - splitPoint = pointMap[ j ][k].second; + splitPoint = pointMap[ vertexIndex ][k].second; if ( splitPoint == currentPoint ) { lines.addGeometry( newLine.clone() ); newLine = QgsLineString(); newLine.addVertex( currentPoint ); } - else if ( splitPoint == line->pointN( j + 1 ) ) + else if ( splitPoint == line->pointN( vertexIndex + 1 ) ) { - newLine.addVertex( line->pointN( j + 1 ) ); + newLine.addVertex( line->pointN( vertexIndex + 1 ) ); lines.addGeometry( newLine.clone() ); newLine = QgsLineString(); } diff --git a/tests/src/core/geometry/testqgsgeometry.cpp b/tests/src/core/geometry/testqgsgeometry.cpp index 0ed302b7d1fc..a5db7fed29da 100644 --- a/tests/src/core/geometry/testqgsgeometry.cpp +++ b/tests/src/core/geometry/testqgsgeometry.cpp @@ -2403,6 +2403,16 @@ void TestQgsGeometry::splitGeometry() QCOMPARE( g2.splitGeometry( QgsPointSequence() << QgsPoint( -63290.25259721936890855, -79165.28533450335089583 ) << QgsPoint( -63290.25259721936890855, -79160.28533450335089583 ), newGeoms, false, testPoints ), Qgis::GeometryOperationResult::Success ); QCOMPARE( newGeoms.count(), 1 ); QCOMPARE( newGeoms[0].asWkt( 17 ), QStringLiteral( "LineString (-63290.25259721937618451 -79162.78533450335089583, -63290.25259721936890855 -79162.78533450335089583)" ) ); + + // Should not split the first part - https://github.com/qgis/QGIS/issues/54155 + g2 = QgsGeometry::fromWkt( "MultiLinestring((0 1, 1 0),(0 2, 2 0))" ); + testPoints.clear(); + newGeoms.clear(); + QCOMPARE( g2.splitGeometry( QgsPointSequence() << QgsPoint( 0.8, 0.8 ) << QgsPoint( 1.2, 1.2 ), newGeoms, false, testPoints, false ), Qgis::GeometryOperationResult::Success ); + QCOMPARE( newGeoms.count(), 3 ); + QCOMPARE( newGeoms[0].asWkt( 0 ), QStringLiteral( "MultiLineString ((0 2, 1 1))" ) ); + QCOMPARE( newGeoms[1].asWkt( 0 ), QStringLiteral( "MultiLineString ((1 1, 2 0))" ) ); + QCOMPARE( newGeoms[2].asWkt( 0 ), QStringLiteral( "MultiLineString ((0 1, 1 0))" ) ); } void TestQgsGeometry::snappedToGrid() diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index dd11f0fb60b5..5f13a0eac704 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -7363,6 +7363,16 @@ def testSplitGeometry(self): self.assertGeometriesEqual(square, r2) self.assertGeometriesEqual(parts[0], r1) + multilinestring = QgsGeometry.fromWkt("MultiLinestring((0 1, 1 0),(0 2, 2 0))") + blade = QgsCompoundCurve() + blade.addCurve(QgsLineString([QgsPointXY(0.8, 0.8), QgsPointXY(1.2, 1.2)])) + result, splitted, _ = multilinestring.splitGeometry(blade, False, False, False) + self.assertEqual(result, Qgis.GeometryOperationResult.Success) + self.assertEqual(len(splitted), 3) + self.assertTrue(compareWkt(splitted[0].asWkt(), 'MultiLineString ((0 2, 1 1))')) + self.assertTrue(compareWkt(splitted[1].asWkt(), 'MultiLineString ((1 1, 2 0))')) + self.assertTrue(compareWkt(splitted[2].asWkt(), 'MultiLineString ((0 1, 1 0))')) + if __name__ == '__main__': unittest.main() From 93f4b0c4a3c0fe83a6bfe02374bfb33ef02e0b34 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 26 Sep 2023 15:07:23 +0200 Subject: [PATCH 125/151] spelling --- tests/src/python/test_qgsgeometry.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index 5f13a0eac704..6f154f6e80cd 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -7366,12 +7366,12 @@ def testSplitGeometry(self): multilinestring = QgsGeometry.fromWkt("MultiLinestring((0 1, 1 0),(0 2, 2 0))") blade = QgsCompoundCurve() blade.addCurve(QgsLineString([QgsPointXY(0.8, 0.8), QgsPointXY(1.2, 1.2)])) - result, splitted, _ = multilinestring.splitGeometry(blade, False, False, False) + result, parts, _ = multilinestring.splitGeometry(blade, False, False, False) self.assertEqual(result, Qgis.GeometryOperationResult.Success) - self.assertEqual(len(splitted), 3) - self.assertTrue(compareWkt(splitted[0].asWkt(), 'MultiLineString ((0 2, 1 1))')) - self.assertTrue(compareWkt(splitted[1].asWkt(), 'MultiLineString ((1 1, 2 0))')) - self.assertTrue(compareWkt(splitted[2].asWkt(), 'MultiLineString ((0 1, 1 0))')) + self.assertEqual(len(parts), 3) + self.assertTrue(compareWkt(parts[0].asWkt(), 'MultiLineString ((0 2, 1 1))')) + self.assertTrue(compareWkt(parts[1].asWkt(), 'MultiLineString ((1 1, 2 0))')) + self.assertTrue(compareWkt(parts[2].asWkt(), 'MultiLineString ((0 1, 1 0))')) if __name__ == '__main__': From 24f0cb649d445d448c344d172a13345950affcb4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 10:36:49 +1000 Subject: [PATCH 126/151] Update src/core/geometry/qgsgeos.cpp --- 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 4e3a890f42a0..6b33ef38300b 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -1068,7 +1068,7 @@ geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) co // The intersection might belong to another part, skip it // Note: cannot test for equality because of Z - if ( intersectionPoint->x() != segmentPoint2D.x() || intersectionPoint->y() != segmentPoint2D.y() ) + if ( !qgsDoubleNear( intersectionPoint->x(), segmentPoint2D.x() ) || !qgsDoubleNear( intersectionPoint->y(), segmentPoint2D.y() ) ) { continue; } From c29bfc710ccf4fd44b374c78a3bd191ec489c9d4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 09:44:48 +1000 Subject: [PATCH 127/151] Fix build warning --- src/core/geometry/qgsgeometry.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index f8af87190218..9c1dbed1f9e3 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3097,6 +3097,7 @@ int QgsGeometry::avoidIntersections( const QList &avoidInterse case Qgis::GeometryOperationResult::SplitCannotSplitPoint: return 4; } + return 4; } QgsGeometry QgsGeometry::makeValid( Qgis::MakeValidMethod method, bool keepCollapsed ) const From c14f01265c3e5f929421dfd958821e559f265586 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 09:33:06 +1000 Subject: [PATCH 128/151] Greatly speed up running qgis_process help usage Add a shortcut so that if we're just showing the usage documentation, avoid the whole startup costs of QgsApplication. Drops execution time from a couple of seconds to ~100ms. Refs #54563 --- src/process/main.cpp | 64 ++++++++++++++++++++++++++++++++++---- src/process/qgsprocess.cpp | 41 +++--------------------- src/process/qgsprocess.h | 4 +-- 3 files changed, 64 insertions(+), 45 deletions(-) diff --git a/src/process/main.cpp b/src/process/main.cpp index 8dafa298564c..a603ea7e2559 100644 --- a/src/process/main.cpp +++ b/src/process/main.cpp @@ -77,7 +77,63 @@ int main( int argc, char *argv[] ) #endif // _MSC_VER #endif // Q_OS_WIN + // a shortcut -- let's see if we are being called without any arguments, or just the usage argument. + // If so, let's skip the startup cost of a QCoreApplication/QgsApplication + bool hasHelpArgument = false; + for ( int i = 1; i < argc; ++i ) + { + const QString arg( argv[i] ); + if ( arg == QLatin1String( "--help" ) || arg == QLatin1String( "-h" ) ) + { + hasHelpArgument = true; + break; + } + } + if ( argc == 1 || hasHelpArgument ) + { + QgsProcessingExec::showUsage( QString( argv[ 0 ] ) ); + return 0; + } + QgsApplication app( argc, argv, false, QString(), QStringLiteral( "qgis_process" ) ); + + // Build a local QCoreApplication from arguments. This way, arguments are correctly parsed from their native locale + // It will use QString::fromLocal8Bit( argv ) under Unix and GetCommandLine() under Windows. + QStringList args = QCoreApplication::arguments(); + + const int jsonIndex = args.indexOf( QLatin1String( "--json" ) ); + bool useJson = false; + if ( jsonIndex >= 0 ) + { + useJson = true; + args.removeAt( jsonIndex ); + } + + const int verboseIndex = args.indexOf( QLatin1String( "--verbose" ) ); + QgsProcessingContext::LogLevel logLevel = QgsProcessingContext::DefaultLevel; + if ( verboseIndex >= 0 ) + { + logLevel = QgsProcessingContext::Verbose; + args.removeAt( verboseIndex ); + } + + const int noPythonIndex = args.indexOf( QLatin1String( "--no-python" ) ); + bool skipPython = false; + if ( noPythonIndex >= 0 ) + { + skipPython = true; + args.removeAt( noPythonIndex ); + } + + const QString command = args.value( 1 ); + if ( args.size() == 1 || command == QLatin1String( "--help" ) || command == QLatin1String( "-h" ) ) + { + // a shortcut -- if we are showing usage information, we don't need to initialise + // QgsApplication at all! + QgsProcessingExec::showUsage( args.at( 0 ) ); + return 0; + } + QString myPrefixPath; if ( myPrefixPath.isEmpty() ) { @@ -99,15 +155,11 @@ int main( int argc, char *argv[] ) ( void ) QgsApplication::resolvePkgPath(); // trigger storing of application path in QgsApplication - // Build a local QCoreApplication from arguments. This way, arguments are correctly parsed from their native locale - // It will use QString::fromLocal8Bit( argv ) under Unix and GetCommandLine() under Windows. - const QStringList args = QCoreApplication::arguments(); - QgsProcessingExec exec; int res = 0; - QTimer::singleShot( 0, &app, [&exec, args, &res] + QTimer::singleShot( 0, &app, [&exec, args, useJson, logLevel, skipPython, &res] { - res = exec.run( args ); + res = exec.run( args, useJson, logLevel, skipPython ); QgsApplication::exitQgis(); QCoreApplication::exit( res ); } ); diff --git a/src/process/qgsprocess.cpp b/src/process/qgsprocess.cpp index a111f7d9c4fe..2122f2384b47 100644 --- a/src/process/qgsprocess.cpp +++ b/src/process/qgsprocess.cpp @@ -232,9 +232,10 @@ QgsProcessingExec::QgsProcessingExec() } -int QgsProcessingExec::run( const QStringList &constArgs ) +int QgsProcessingExec::run( const QStringList &args, bool useJson, QgsProcessingContext::LogLevel logLevel, bool skipPython ) { - QStringList args = constArgs; + mSkipPython = skipPython; + QObject::connect( QgsApplication::messageLog(), static_cast < void ( QgsMessageLog::* )( const QString &message, const QString &tag, Qgis::MessageLevel level ) >( &QgsMessageLog::messageReceived ), QgsApplication::instance(), [ = ]( const QString & message, const QString &, Qgis::MessageLevel level ) { @@ -245,35 +246,6 @@ int QgsProcessingExec::run( const QStringList &constArgs ) } } ); - const int jsonIndex = args.indexOf( QLatin1String( "--json" ) ); - bool useJson = false; - if ( jsonIndex >= 0 ) - { - useJson = true; - args.removeAt( jsonIndex ); - } - - const int verboseIndex = args.indexOf( QLatin1String( "--verbose" ) ); - QgsProcessingContext::LogLevel logLevel = QgsProcessingContext::DefaultLevel; - if ( verboseIndex >= 0 ) - { - logLevel = QgsProcessingContext::Verbose; - args.removeAt( verboseIndex ); - } - - const int noPythonIndex = args.indexOf( QLatin1String( "--no-python" ) ); - if ( noPythonIndex >= 0 ) - { - mSkipPython = true; - args.removeAt( noPythonIndex ); - } - - if ( args.size() == 1 ) - { - showUsage( args.at( 0 ) ); - return 0; - } - // core providers QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) ); #ifdef HAVE_3D @@ -299,7 +271,7 @@ int QgsProcessingExec::run( const QStringList &constArgs ) } #endif - const QString command = args.at( 1 ); + const QString command = args.value( 1 ); if ( command == QLatin1String( "plugins" ) ) { if ( args.size() == 2 || ( args.size() == 3 && args.at( 2 ) == QLatin1String( "list" ) ) ) @@ -330,11 +302,6 @@ int QgsProcessingExec::run( const QStringList &constArgs ) std::cout << QgsCommandLineUtils::allVersions().toStdString(); return 0; } - else if ( command == QLatin1String( "--help" ) || command == QLatin1String( "-h" ) ) - { - showUsage( args.at( 0 ) ); - return 0; - } else if ( command == QLatin1String( "help" ) ) { if ( args.size() < 3 ) diff --git a/src/process/qgsprocess.h b/src/process/qgsprocess.h index 127877baf7e1..9a075ddca88f 100644 --- a/src/process/qgsprocess.h +++ b/src/process/qgsprocess.h @@ -69,11 +69,11 @@ class QgsProcessingExec public: QgsProcessingExec(); - int run( const QStringList &args ); + int run( const QStringList &args, bool useJson, QgsProcessingContext::LogLevel logLevel, bool skipPython ); + static void showUsage( const QString &appName ); private: - void showUsage( const QString &appName ); void loadPlugins(); void listAlgorithms( bool useJson ); void listPlugins( bool useJson, bool showLoaded ); From 0c60c47929bb41e803f8b17cbc9e1c77a8989935 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 09:43:08 +1000 Subject: [PATCH 129/151] Also shortcut execution of qgis_process --version --- src/process/main.cpp | 22 +++++++++++++++++++++- src/process/qgsprocess.cpp | 10 +++++----- src/process/qgsprocess.h | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/process/main.cpp b/src/process/main.cpp index a603ea7e2559..92bd2a3aa75e 100644 --- a/src/process/main.cpp +++ b/src/process/main.cpp @@ -77,23 +77,43 @@ int main( int argc, char *argv[] ) #endif // _MSC_VER #endif // Q_OS_WIN - // a shortcut -- let's see if we are being called without any arguments, or just the usage argument. + // a shortcut -- let's see if we are being called without any arguments, or just the usage/version argument. // If so, let's skip the startup cost of a QCoreApplication/QgsApplication bool hasHelpArgument = false; + bool hasVersionArgument = false; for ( int i = 1; i < argc; ++i ) { const QString arg( argv[i] ); + if ( arg == QLatin1String( "--json" ) + || arg == QLatin1String( "--verbose" ) + || arg == QLatin1String( "--no-python" ) ) + { + // ignore these arguments + continue; + } if ( arg == QLatin1String( "--help" ) || arg == QLatin1String( "-h" ) ) { hasHelpArgument = true; break; } + else if ( arg == QLatin1String( "--version" ) || arg == QLatin1String( "-v" ) ) + { + hasVersionArgument = true; + break; + } + break; } + if ( argc == 1 || hasHelpArgument ) { QgsProcessingExec::showUsage( QString( argv[ 0 ] ) ); return 0; } + else if ( hasVersionArgument ) + { + QgsProcessingExec::showVersionInformation(); + return 0; + } QgsApplication app( argc, argv, false, QString(), QStringLiteral( "qgis_process" ) ); diff --git a/src/process/qgsprocess.cpp b/src/process/qgsprocess.cpp index 2122f2384b47..61283b62649f 100644 --- a/src/process/qgsprocess.cpp +++ b/src/process/qgsprocess.cpp @@ -297,11 +297,6 @@ int QgsProcessingExec::run( const QStringList &args, bool useJson, QgsProcessing listAlgorithms( useJson ); return 0; } - else if ( command == QLatin1String( "--version" ) || command == QLatin1String( "-v" ) ) - { - std::cout << QgsCommandLineUtils::allVersions().toStdString(); - return 0; - } else if ( command == QLatin1String( "help" ) ) { if ( args.size() < 3 ) @@ -528,6 +523,11 @@ void QgsProcessingExec::showUsage( const QString &appName ) std::cout << msg.join( QString() ).toLocal8Bit().constData(); } +void QgsProcessingExec::showVersionInformation() +{ + std::cout << QgsCommandLineUtils::allVersions().toStdString(); +} + void QgsProcessingExec::loadPlugins() { #ifdef WITH_BINDINGS diff --git a/src/process/qgsprocess.h b/src/process/qgsprocess.h index 9a075ddca88f..3146fa152cce 100644 --- a/src/process/qgsprocess.h +++ b/src/process/qgsprocess.h @@ -71,6 +71,7 @@ class QgsProcessingExec QgsProcessingExec(); int run( const QStringList &args, bool useJson, QgsProcessingContext::LogLevel logLevel, bool skipPython ); static void showUsage( const QString &appName ); + static void showVersionInformation(); private: From 05a2301ea2760dcc712efe1bc194239b1d0c7550 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 09:51:38 +1000 Subject: [PATCH 130/151] Spelling --- src/process/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process/main.cpp b/src/process/main.cpp index 92bd2a3aa75e..00eec36299b5 100644 --- a/src/process/main.cpp +++ b/src/process/main.cpp @@ -148,7 +148,7 @@ int main( int argc, char *argv[] ) const QString command = args.value( 1 ); if ( args.size() == 1 || command == QLatin1String( "--help" ) || command == QLatin1String( "-h" ) ) { - // a shortcut -- if we are showing usage information, we don't need to initialise + // a shortcut -- if we are showing usage information, we don't need to initialize // QgsApplication at all! QgsProcessingExec::showUsage( args.at( 0 ) ); return 0; From 908eda362c691b0d4e8038131fedec90306ed92b Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 22 Sep 2023 15:33:52 +0200 Subject: [PATCH 131/151] Fix segfault when point is invalid --- src/analysis/processing/qgsalgorithmpointstopaths.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmpointstopaths.cpp b/src/analysis/processing/qgsalgorithmpointstopaths.cpp index b99f95fe7c2b..75ee1f134fa0 100644 --- a/src/analysis/processing/qgsalgorithmpointstopaths.cpp +++ b/src/analysis/processing/qgsalgorithmpointstopaths.cpp @@ -255,8 +255,11 @@ QVariantMap QgsPointsToPathsAlgorithm::processAlgorithm( const QVariantMap ¶ } else { - const QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( geom ) ); - allPoints[ groupValue ] << qMakePair( orderValue, point ); + const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >(geom); + if ( point ) + { + allPoints[ groupValue ] << qMakePair( orderValue, *point ); + } } } ++currentPoint; From 7f8faed828080c82b27b30d83efdcbc14ced129c Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 22 Sep 2023 15:35:35 +0200 Subject: [PATCH 132/151] Another safety check --- src/analysis/processing/qgsalgorithmpointstopaths.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmpointstopaths.cpp b/src/analysis/processing/qgsalgorithmpointstopaths.cpp index 75ee1f134fa0..0dd6c320bdbb 100644 --- a/src/analysis/processing/qgsalgorithmpointstopaths.cpp +++ b/src/analysis/processing/qgsalgorithmpointstopaths.cpp @@ -249,13 +249,16 @@ QVariantMap QgsPointsToPathsAlgorithm::processAlgorithm( const QVariantMap ¶ const QgsMultiPoint mp( *qgsgeometry_cast< const QgsMultiPoint * >( geom ) ); for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit ) { - const QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( *pit ) ); - allPoints[ groupValue ] << qMakePair( orderValue, point ); + const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( *pit ); + if ( point ) + { + allPoints[ groupValue ] << qMakePair( orderValue, *point ); + } } } else { - const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >(geom); + const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( geom ); if ( point ) { allPoints[ groupValue ] << qMakePair( orderValue, *point ); From 3d2bdedabf02b6149841ae158bef4e5f700fcea5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 10:01:22 +1000 Subject: [PATCH 133/151] Apply suggestions from code review --- src/analysis/processing/qgsalgorithmpointstopaths.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmpointstopaths.cpp b/src/analysis/processing/qgsalgorithmpointstopaths.cpp index 0dd6c320bdbb..66b43c8d2023 100644 --- a/src/analysis/processing/qgsalgorithmpointstopaths.cpp +++ b/src/analysis/processing/qgsalgorithmpointstopaths.cpp @@ -249,8 +249,7 @@ QVariantMap QgsPointsToPathsAlgorithm::processAlgorithm( const QVariantMap ¶ const QgsMultiPoint mp( *qgsgeometry_cast< const QgsMultiPoint * >( geom ) ); for ( auto pit = mp.const_parts_begin(); pit != mp.const_parts_end(); ++pit ) { - const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( *pit ); - if ( point ) + if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( *pit ) ) { allPoints[ groupValue ] << qMakePair( orderValue, *point ); } @@ -258,8 +257,7 @@ QVariantMap QgsPointsToPathsAlgorithm::processAlgorithm( const QVariantMap ¶ } else { - const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( geom ); - if ( point ) + if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( geom ) ) { allPoints[ groupValue ] << qMakePair( orderValue, *point ); } From 29b08c79b786fd34443771ac468b22c86dcb3583 Mon Sep 17 00:00:00 2001 From: Etienne Trimaille Date: Fri, 22 Sep 2023 12:04:45 +0200 Subject: [PATCH 134/151] Hide password instead of removing it --- python/core/auto_generated/qgsmaplayer.sip.in | 4 +++- src/core/project/qgsprojectbadlayerhandler.cpp | 2 +- src/core/qgsmaplayer.cpp | 4 ++-- src/core/qgsmaplayer.h | 3 ++- src/gui/qgscredentialdialog.cpp | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 055414ac1ce2..11fcc04d24aa 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -542,12 +542,14 @@ or other problem. Child classes set this flag when initialized. :return: ``True`` if the layer is valid and can be accessed %End - QString publicSource() const; + QString publicSource( bool hidePassword = false ) const; %Docstring Gets a version of the internal layer definition that has sensitive bits removed (for example, the password). This function should be used when displaying the source name for general viewing. +:param hidePassword: False, if the password should be removed or replaced by an arbitrary string, since QGIS 3.34 + .. seealso:: :py:func:`source` %End diff --git a/src/core/project/qgsprojectbadlayerhandler.cpp b/src/core/project/qgsprojectbadlayerhandler.cpp index 5388dfd00cdd..372f4c7a85b2 100644 --- a/src/core/project/qgsprojectbadlayerhandler.cpp +++ b/src/core/project/qgsprojectbadlayerhandler.cpp @@ -28,7 +28,7 @@ void QgsProjectBadLayerHandler::handleBadLayers( const QList &layers ) for ( const QDomNode &layer : layers ) { - QgsMessageLog::logMessage( QObject::tr( " * %1" ).arg( QgsDataSourceUri::removePassword( dataSource( layer ) ) ) ); + QgsMessageLog::logMessage( QObject::tr( " * %1" ).arg( QgsDataSourceUri::removePassword( dataSource( layer ), true ) ) ); } } diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index dbd371fffefa..0ab673864108 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -342,13 +342,13 @@ QString QgsMapLayer::metadataUrlFormat() const } } -QString QgsMapLayer::publicSource() const +QString QgsMapLayer::publicSource( bool hidePassword ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS // Redo this every time we're asked for it, as we don't know if // dataSource has changed. - QString safeName = QgsDataSourceUri::removePassword( mDataSource ); + QString safeName = QgsDataSourceUri::removePassword( mDataSource, hidePassword ); return safeName; } diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 1eddb06caffd..1b70ce307638 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -564,9 +564,10 @@ class CORE_EXPORT QgsMapLayer : public QObject * Gets a version of the internal layer definition that has sensitive * bits removed (for example, the password). This function should * be used when displaying the source name for general viewing. + * \param hidePassword False, if the password should be removed or replaced by an arbitrary string, since QGIS 3.34 * \see source() */ - QString publicSource() const; + QString publicSource( bool hidePassword = false ) const; /** * Returns the source for the layer. This source may contain usernames, passwords diff --git a/src/gui/qgscredentialdialog.cpp b/src/gui/qgscredentialdialog.cpp index 34f1d6039af5..0be098f40311 100644 --- a/src/gui/qgscredentialdialog.cpp +++ b/src/gui/qgscredentialdialog.cpp @@ -149,7 +149,7 @@ void QgsCredentialDialog::requestCredentials( const QString &realm, QString *use stackedWidget->setCurrentIndex( 0 ); mIgnoreButton->show(); chkbxPasswordHelperEnable->setChecked( QgsApplication::authManager()->passwordHelperEnabled() ); - labelRealm->setText( QgsDataSourceUri::removePassword( realm ) ); + labelRealm->setText( QgsDataSourceUri::removePassword( realm, true ) ); mRealm = realm; leUsername->setText( *username ); lePassword->setText( *password ); From 25fc01542aca575752c1bc40fdd8e74ffaad2b14 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Sep 2023 14:10:59 +1000 Subject: [PATCH 135/151] Port Join Attributes by Location (summary) to c++ Refs #53806 The crash here is coming from sip internals -- somewhere it is triggering a Python SystemError. I can't solve this one, so just port the algorithm to c++ and deal with the much improved performance instead. --- python/plugins/processing/algs/help/qgis.yaml | 7 - .../algs/qgis/QgisAlgorithmProvider.py | 2 - .../algs/qgis/SpatialJoinSummary.py | 373 ------------ src/analysis/CMakeLists.txt | 1 + .../processing/qgsalgorithmjoinbylocation.cpp | 29 +- .../processing/qgsalgorithmjoinbylocation.h | 17 +- .../qgsalgorithmjoinbylocationsummary.cpp | 570 ++++++++++++++++++ .../qgsalgorithmjoinbylocationsummary.h | 62 ++ .../processing/qgsnativealgorithms.cpp | 2 + 9 files changed, 665 insertions(+), 398 deletions(-) delete mode 100644 python/plugins/processing/algs/qgis/SpatialJoinSummary.py create mode 100644 src/analysis/processing/qgsalgorithmjoinbylocationsummary.cpp create mode 100644 src/analysis/processing/qgsalgorithmjoinbylocationsummary.h diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 1d878ef2b03f..5ce722aaa110 100644 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -104,13 +104,6 @@ qgis:idwinterpolation: > qgis:importintospatialite: > This algorithm imports a vector layer into a SpatiaLite database, creating a new table. -qgis:joinbylocationsummary: > - This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the input one, with additional attributes in its attribute table. - - The additional attributes and their values are taken from a second vector layer. A spatial criteria is applied to select the values from the second layer that are added to each feature from the first layer in the resulting one. - - The algorithm calculates a statistical summary for the values from matching features in the second layer (e.g. maximum value, mean value, etc). - qgis:knearestconcavehull: > This algorithm generates a concave hull polygon from a set of points. If the input layer is a line or polygon layer, it will use the nodes. diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py index 078c7dc74f79..1cad7268af64 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py @@ -71,7 +71,6 @@ from .SelectByExpression import SelectByExpression from .SetRasterStyle import SetRasterStyle from .SetVectorStyle import SetVectorStyle -from .SpatialJoinSummary import SpatialJoinSummary from .StatisticsByCategories import StatisticsByCategories from .TextToFloat import TextToFloat from .TinInterpolation import TinInterpolation @@ -136,7 +135,6 @@ def getAlgs(self): SelectByExpression(), SetRasterStyle(), SetVectorStyle(), - SpatialJoinSummary(), StatisticsByCategories(), TextToFloat(), TinInterpolation(), diff --git a/python/plugins/processing/algs/qgis/SpatialJoinSummary.py b/python/plugins/processing/algs/qgis/SpatialJoinSummary.py deleted file mode 100644 index 26a78aa91e16..000000000000 --- a/python/plugins/processing/algs/qgis/SpatialJoinSummary.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -*************************************************************************** - SpatialJoinSummary.py - --------------------- - Date : September 2017 - Copyright : (C) 2017 by Nyall Dawson - Email : nyall dot dawson 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. * -* * -*************************************************************************** -""" - -__author__ = 'Nyall Dawson' -__date__ = 'September 2017' -__copyright__ = '(C) 2017, Nyall Dawson' - -import os -import math - -from collections import defaultdict - -from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtCore import QVariant -from qgis.core import (NULL, - QgsApplication, - QgsField, - QgsFields, - QgsFeatureSink, - QgsFeatureRequest, - QgsGeometry, - QgsFeatureSource, - QgsCoordinateTransform, - QgsStatisticalSummary, - QgsDateTimeStatisticalSummary, - QgsStringStatisticalSummary, - QgsProcessing, - QgsProcessingUtils, - QgsProcessingException, - QgsProcessingParameterBoolean, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterEnum, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSink) - -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.tools import vector - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] - - -class SpatialJoinSummary(QgisAlgorithm): - INPUT = "INPUT" - JOIN = "JOIN" - PREDICATE = "PREDICATE" - JOIN_FIELDS = "JOIN_FIELDS" - SUMMARIES = "SUMMARIES" - DISCARD_NONMATCHING = "DISCARD_NONMATCHING" - OUTPUT = "OUTPUT" - - def group(self): - return self.tr('Vector general') - - def groupId(self): - return 'vectorgeneral' - - def __init__(self): - super().__init__() - - def icon(self): - return QgsApplication.getThemeIcon("/algorithms/mAlgorithmBasicStatistics.svg") - - def svgIconPath(self): - return QgsApplication.iconPath("/algorithms/mAlgorithmBasicStatistics.svg") - - def initAlgorithm(self, config=None): - self.predicates = ( - ('intersects', self.tr('intersect')), - ('contains', self.tr('contain')), - ('isEqual', self.tr('equal')), - ('touches', self.tr('touch')), - ('overlaps', self.tr('overlap')), - ('within', self.tr('are within')), - ('crosses', self.tr('cross'))) - - self.statistics = [ - ('count', self.tr('count')), - ('unique', self.tr('unique')), - ('min', self.tr('min')), - ('max', self.tr('max')), - ('range', self.tr('range')), - ('sum', self.tr('sum')), - ('mean', self.tr('mean')), - ('median', self.tr('median')), - ('stddev', self.tr('stddev')), - ('minority', self.tr('minority')), - ('majority', self.tr('majority')), - ('q1', self.tr('q1')), - ('q3', self.tr('q3')), - ('iqr', self.tr('iqr')), - ('empty', self.tr('empty')), - ('filled', self.tr('filled')), - ('min_length', self.tr('min_length')), - ('max_length', self.tr('max_length')), - ('mean_length', self.tr('mean_length'))] - - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Join to features in'), - [QgsProcessing.TypeVectorAnyGeometry])) - predicate = QgsProcessingParameterEnum(self.PREDICATE, - self.tr('Where the features'), - options=[p[1] for p in self.predicates], - allowMultiple=True, defaultValue=[0]) - predicate.setMetadata({ - 'widget_wrapper': { - 'useCheckBoxes': True, - 'columns': 2}}) - self.addParameter(predicate) - self.addParameter(QgsProcessingParameterFeatureSource(self.JOIN, - self.tr('By comparing to'), - [QgsProcessing.TypeVectorAnyGeometry])) - self.addParameter(QgsProcessingParameterField(self.JOIN_FIELDS, - self.tr('Fields to summarise (leave empty to use all fields)'), - parentLayerParameterName=self.JOIN, - allowMultiple=True, optional=True)) - self.addParameter(QgsProcessingParameterEnum(self.SUMMARIES, - self.tr( - 'Summaries to calculate (leave empty to use all available)'), - options=[p[1] for p in self.statistics], - allowMultiple=True, optional=True)) - self.addParameter(QgsProcessingParameterBoolean(self.DISCARD_NONMATCHING, - self.tr('Discard records which could not be joined'), - defaultValue=False)) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, - self.tr('Joined layer'))) - - def name(self): - return 'joinbylocationsummary' - - def displayName(self): - return self.tr('Join attributes by location (summary)') - - def tags(self): - return self.tr( - "summary,aggregate,join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial," - "stats,statistics,sum,maximum,minimum,mean,average,standard,deviation," - "count,distinct,unique,variance,median,quartile,range,majority,minority,histogram,distinct").split(',') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - if source is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) - - join_source = self.parameterAsSource(parameters, self.JOIN, context) - if join_source is None: - raise QgsProcessingException(self.invalidSourceError(parameters, self.JOIN)) - - if join_source.hasSpatialIndex() == QgsFeatureSource.SpatialIndexNotPresent: - feedback.reportError(self.tr("No spatial index exists for join layer, performance will be severely degraded")) - - join_fields = self.parameterAsFields(parameters, self.JOIN_FIELDS, context) - discard_nomatch = self.parameterAsBoolean(parameters, self.DISCARD_NONMATCHING, context) - summaries = [self.statistics[i][0] for i in - sorted(self.parameterAsEnums(parameters, self.SUMMARIES, context))] - - if not summaries: - # none selected, so use all - summaries = [s[0] for s in self.statistics] - - source_fields = source.fields() - fields_to_join = QgsFields() - join_field_indexes = [] - if not join_fields: - # no fields selected, use all - join_fields = [join_source.fields().at(i).name() for i in range(len(join_source.fields()))] - - def addFieldKeepType(original, stat): - """ - Adds a field to the output, keeping the same data type as the original - """ - field = QgsField(original) - field.setName(field.name() + '_' + stat) - fields_to_join.append(field) - - def addField(original, stat, type): - """ - Adds a field to the output, with a specified type - """ - field = QgsField(original) - field.setName(field.name() + '_' + stat) - field.setType(type) - if type == QVariant.Double: - field.setLength(20) - field.setPrecision(6) - fields_to_join.append(field) - - numeric_fields = ( - ('count', QVariant.Int, 'count'), - ('unique', QVariant.Int, 'variety'), - ('min', QVariant.Double, 'min'), - ('max', QVariant.Double, 'max'), - ('range', QVariant.Double, 'range'), - ('sum', QVariant.Double, 'sum'), - ('mean', QVariant.Double, 'mean'), - ('median', QVariant.Double, 'median'), - ('stddev', QVariant.Double, 'stDev'), - ('minority', QVariant.Double, 'minority'), - ('majority', QVariant.Double, 'majority'), - ('q1', QVariant.Double, 'firstQuartile'), - ('q3', QVariant.Double, 'thirdQuartile'), - ('iqr', QVariant.Double, 'interQuartileRange') - ) - - datetime_fields = ( - ('count', QVariant.Int, 'count'), - ('unique', QVariant.Int, 'countDistinct'), - ('empty', QVariant.Int, 'countMissing'), - ('filled', QVariant.Int), - ('min', None), - ('max', None) - ) - - string_fields = ( - ('count', QVariant.Int, 'count'), - ('unique', QVariant.Int, 'countDistinct'), - ('empty', QVariant.Int, 'countMissing'), - ('filled', QVariant.Int), - ('min', None, 'min'), - ('max', None, 'max'), - ('min_length', QVariant.Int, 'minLength'), - ('max_length', QVariant.Int, 'maxLength'), - ('mean_length', QVariant.Double, 'meanLength') - ) - - field_types = [] - for f in join_fields: - idx = join_source.fields().lookupField(f) - if idx >= 0: - join_field_indexes.append(idx) - - join_field = join_source.fields().at(idx) - if join_field.isNumeric(): - field_types.append('numeric') - field_list = numeric_fields - elif join_field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): - field_types.append('datetime') - field_list = datetime_fields - else: - field_types.append('string') - field_list = string_fields - - for f in field_list: - if f[0] in summaries: - if f[1] is not None: - addField(join_field, f[0], f[1]) - else: - addFieldKeepType(join_field, f[0]) - - out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - out_fields, source.wkbType(), source.sourceCrs()) - if sink is None: - raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) - - # do the join - predicates = [self.predicates[i][0] for i in self.parameterAsEnums(parameters, self.PREDICATE, context)] - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - - for current, f in enumerate(features): - if feedback.isCanceled(): - break - - if not f.hasGeometry(): - if not discard_nomatch: - # ensure consistent count of attributes - otherwise non matching - # features will have incorrect attribute length - # and provider may reject them - attrs = f.attributes() - if len(attrs) < len(out_fields): - attrs += [NULL] * (len(out_fields) - len(attrs)) - f.setAttributes(attrs) - sink.addFeature(f, QgsFeatureSink.FastInsert) - continue - - engine = None - - values = [] - - request = QgsFeatureRequest().setFilterRect(f.geometry().boundingBox()).setSubsetOfAttributes(join_field_indexes).setDestinationCrs(source.sourceCrs(), context.transformContext()) - for test_feat in join_source.getFeatures(request): - if feedback.isCanceled(): - break - - join_attributes = [] - for a in join_field_indexes: - join_attributes.append(test_feat[a]) - - if engine is None: - engine = QgsGeometry.createGeometryEngine(f.geometry().constGet()) - engine.prepareGeometry() - - for predicate in predicates: - if getattr(engine, predicate)(test_feat.geometry().constGet()): - values.append(join_attributes) - break - - feedback.setProgress(int(current * total)) - - if len(values) == 0: - if discard_nomatch: - continue - else: - # ensure consistent count of attributes - otherwise non matching - # features will have incorrect attribute length - # and provider may reject them - attrs = f.attributes() - if len(attrs) < len(out_fields): - attrs += [NULL] * (len(out_fields) - len(attrs)) - f.setAttributes(attrs) - sink.addFeature(f, QgsFeatureSink.FastInsert) - else: - attrs = f.attributes() - for i in range(len(join_field_indexes)): - attribute_values = [v[i] for v in values] - field_type = field_types[i] - if field_type == 'numeric': - stat = QgsStatisticalSummary() - for v in attribute_values: - stat.addVariant(v) - stat.finalize() - for s in numeric_fields: - if s[0] in summaries: - val = getattr(stat, s[2])() - attrs.append(val if not math.isnan(val) else NULL) - elif field_type == 'datetime': - stat = QgsDateTimeStatisticalSummary() - stat.calculate(attribute_values) - for s in datetime_fields: - if s[0] in summaries: - if s[0] == 'filled': - attrs.append(stat.count() - stat.countMissing()) - elif s[0] == 'min': - attrs.append(stat.statistic(QgsDateTimeStatisticalSummary.Min)) - elif s[0] == 'max': - attrs.append(stat.statistic(QgsDateTimeStatisticalSummary.Max)) - else: - attrs.append(getattr(stat, s[2])()) - else: - stat = QgsStringStatisticalSummary() - for v in attribute_values: - if v == NULL: - stat.addString('') - else: - stat.addString(str(v)) - stat.finalize() - for s in string_fields: - if s[0] in summaries: - if s[0] == 'filled': - attrs.append(stat.count() - stat.countMissing()) - else: - attrs.append(getattr(stat, s[2])()) - - f.setAttributes(attrs) - sink.addFeature(f, QgsFeatureSink.FastInsert) - - return {self.OUTPUT: dest_id} diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index cf0d76e3969c..d7b4aaa3c6fa 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -122,6 +122,7 @@ set(QGIS_ANALYSIS_SRCS processing/qgsalgorithmintersection.cpp processing/qgsalgorithmjoinbyattribute.cpp processing/qgsalgorithmjoinbylocation.cpp + processing/qgsalgorithmjoinbylocationsummary.cpp processing/qgsalgorithmjoinbynearest.cpp processing/qgsalgorithmjoinwithlines.cpp processing/qgsalgorithmkeepnbiggestparts.cpp diff --git a/src/analysis/processing/qgsalgorithmjoinbylocation.cpp b/src/analysis/processing/qgsalgorithmjoinbylocation.cpp index d77a14e54772..2654f4b02ae7 100644 --- a/src/analysis/processing/qgsalgorithmjoinbylocation.cpp +++ b/src/analysis/processing/qgsalgorithmjoinbylocation.cpp @@ -31,16 +31,7 @@ void QgsJoinByLocationAlgorithm::initAlgorithm( const QVariantMap & ) addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Join to features in" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) ); - QStringList predicates; - predicates << QObject::tr( "intersect" ) - << QObject::tr( "contain" ) - << QObject::tr( "equal" ) - << QObject::tr( "touch" ) - << QObject::tr( "overlap" ) - << QObject::tr( "are within" ) - << QObject::tr( "cross" ); - - std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "PREDICATE" ), QObject::tr( "Features they (geometric predicate)" ), predicates, true, 0 ); + std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "PREDICATE" ), QObject::tr( "Features they (geometric predicate)" ), translatedPredicates(), true, 0 ); QVariantMap predicateMetadata; QVariantMap widgetMetadata; widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true ); @@ -115,6 +106,16 @@ QgsJoinByLocationAlgorithm *QgsJoinByLocationAlgorithm::createInstance() const return new QgsJoinByLocationAlgorithm(); } +QStringList QgsJoinByLocationAlgorithm::translatedPredicates() +{ + return { QObject::tr( "intersect" ), + QObject::tr( "contain" ), + QObject::tr( "equal" ), + QObject::tr( "touch" ), + QObject::tr( "overlap" ), + QObject::tr( "are within" ), + QObject::tr( "cross" ) }; +} QVariantMap QgsJoinByLocationAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { @@ -226,11 +227,11 @@ QVariantMap QgsJoinByLocationAlgorithm::processAlgorithm( const QVariantMap &par return outputs; } -bool QgsJoinByLocationAlgorithm::featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature ) const +bool QgsJoinByLocationAlgorithm::featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature, const QList &predicates ) { const QgsAbstractGeometry *geom = feature.geometry().constGet(); bool ok = false; - for ( const int predicate : mPredicates ) + for ( const int predicate : predicates ) { switch ( predicate ) { @@ -462,7 +463,7 @@ bool QgsJoinByLocationAlgorithm::processFeatureFromJoinSource( QgsFeature &joinF joinAttributes.append( joinFeature.attribute( ix ) ); } } - if ( featureFilter( baseFeature, engine.get(), false ) ) + if ( featureFilter( baseFeature, engine.get(), false, mPredicates ) ) { if ( mJoinedFeatures ) { @@ -532,7 +533,7 @@ bool QgsJoinByLocationAlgorithm::processFeatureFromInputSource( QgsFeature &base engine->prepareGeometry(); } - if ( featureFilter( joinFeature, engine.get(), true ) ) + if ( featureFilter( joinFeature, engine.get(), true, mPredicates ) ) { switch ( mJoinMethod ) { diff --git a/src/analysis/processing/qgsalgorithmjoinbylocation.h b/src/analysis/processing/qgsalgorithmjoinbylocation.h index 764bc9d70d09..5dd8e0509cf0 100644 --- a/src/analysis/processing/qgsalgorithmjoinbylocation.h +++ b/src/analysis/processing/qgsalgorithmjoinbylocation.h @@ -48,11 +48,25 @@ class QgsJoinByLocationAlgorithm : public QgsProcessingAlgorithm QString shortDescription() const override; QgsJoinByLocationAlgorithm *createInstance() const override SIP_FACTORY; + /** + * Returns a translated list of predicate names, in the order expected by the algorithm's enum parameter. + */ + static QStringList translatedPredicates(); + + /** + * Sorts a list of predicates so that faster ones are tested first + */ + static void sortPredicates( QList &predicates ); + + /** + * Returns TRUE if \a feature satisfies any of the predicates. + */ + static bool featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature, const QList &predicates ); + protected: QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; bool processFeatureFromJoinSource( QgsFeature &joinFeature, QgsProcessingFeedback *feedback ); bool processFeatureFromInputSource( QgsFeature &inputFeature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); - bool featureFilter( const QgsFeature &feature, QgsGeometryEngine *engine, bool comparingToJoinedFeature ) const; private: @@ -76,7 +90,6 @@ class QgsJoinByLocationAlgorithm : public QgsProcessingAlgorithm JoinMethod mJoinMethod = OneToMany; QList mPredicates; - static void sortPredicates( QList &predicates ); }; ///@endcond PRIVATE diff --git a/src/analysis/processing/qgsalgorithmjoinbylocationsummary.cpp b/src/analysis/processing/qgsalgorithmjoinbylocationsummary.cpp new file mode 100644 index 000000000000..a1f9693c24d9 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmjoinbylocationsummary.cpp @@ -0,0 +1,570 @@ +/*************************************************************************** + qgsalgorithmjoinbylocationsummary.cpp + --------------------- + begin : September 2023 + copyright : (C) 2023 by Nyall Dawson + email : nyall dot dawson 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 "qgsalgorithmjoinbylocationsummary.h" +#include "qgsprocessing.h" +#include "qgsgeometryengine.h" +#include "qgsvectorlayer.h" +#include "qgsapplication.h" +#include "qgsfeature.h" +#include "qgsfeaturesource.h" +#include "qgsalgorithmjoinbylocation.h" + +///@cond PRIVATE + + +void QgsJoinByLocationSummaryAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), + QObject::tr( "Join to features in" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) ); + + std::unique_ptr< QgsProcessingParameterEnum > predicateParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "PREDICATE" ), QObject::tr( "Where the features" ), + QgsJoinByLocationAlgorithm::translatedPredicates(), true, 0 ); + QVariantMap predicateMetadata; + QVariantMap widgetMetadata; + widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true ); + widgetMetadata.insert( QStringLiteral( "columns" ), 2 ); + predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata ); + predicateParam->setMetadata( predicateMetadata ); + addParameter( predicateParam.release() ); + + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "JOIN" ), + QObject::tr( "By comparing to" ), QList< int > () << QgsProcessing::QgsProcessing::TypeVectorAnyGeometry ) ); + + addParameter( new QgsProcessingParameterField( QStringLiteral( "JOIN_FIELDS" ), + QObject::tr( "Fields to summarise (leave empty to use all fields)" ), + QVariant(), QStringLiteral( "JOIN" ), QgsProcessingParameterField::Any, true, true ) ); + + mAllSummaries << QObject::tr( "count" ) + << QObject::tr( "unique" ) + << QObject::tr( "min" ) + << QObject::tr( "max" ) + << QObject::tr( "range" ) + << QObject::tr( "sum" ) + << QObject::tr( "mean" ) + << QObject::tr( "median" ) + << QObject::tr( "stddev" ) + << QObject::tr( "minority" ) + << QObject::tr( "majority" ) + << QObject::tr( "q1" ) + << QObject::tr( "q3" ) + << QObject::tr( "iqr" ) + << QObject::tr( "empty" ) + << QObject::tr( "filled" ) + << QObject::tr( "min_length" ) + << QObject::tr( "max_length" ) + << QObject::tr( "mean_length" ); + + std::unique_ptr< QgsProcessingParameterEnum > summaryParam = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "SUMMARIES" ), QObject::tr( "Summaries to calculate (leave empty to use all available)" ), mAllSummaries, true, QVariant(), true ); + addParameter( summaryParam.release() ); + + addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ), + QObject::tr( "Discard records which could not be joined" ), + false ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ) ) ); +} + +QString QgsJoinByLocationSummaryAlgorithm::name() const +{ + return QStringLiteral( "joinbylocationsummary" ); +} + +QString QgsJoinByLocationSummaryAlgorithm::displayName() const +{ + return QObject::tr( "Join attributes by location (summary)" ); +} + +QStringList QgsJoinByLocationSummaryAlgorithm::tags() const +{ + return QObject::tr( "summary,aggregate,join,intersects,intersecting,touching,within,contains,overlaps,relation,spatial," + "stats,statistics,sum,maximum,minimum,mean,average,standard,deviation," + "count,distinct,unique,variance,median,quartile,range,majority,minority,histogram,distinct" ).split( ',' ); +} + +QString QgsJoinByLocationSummaryAlgorithm::group() const +{ + return QObject::tr( "Vector general" ); +} + +QString QgsJoinByLocationSummaryAlgorithm::groupId() const +{ + return QStringLiteral( "vectorgeneral" ); +} + +QString QgsJoinByLocationSummaryAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the input one, with additional attributes in its attribute table.\n\n" + "The additional attributes and their values are taken from a second vector layer. A spatial criteria is applied to select the values from the second layer that are added to each feature from the first layer in the resulting one.\n\n" + "The algorithm calculates a statistical summary for the values from matching features in the second layer( e.g. maximum value, mean value, etc )." ); +} + +QString QgsJoinByLocationSummaryAlgorithm::shortDescription() const +{ + return QObject::tr( "Calculate summaries of attributes from one vector layer to another by location." ); +} + +QIcon QgsJoinByLocationSummaryAlgorithm::icon() const +{ + return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmBasicStatistics.svg" ) ); +} + +QString QgsJoinByLocationSummaryAlgorithm::svgIconPath() const +{ + return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmBasicStatistics.svg" ) ); +} + +QgsJoinByLocationSummaryAlgorithm *QgsJoinByLocationSummaryAlgorithm::createInstance() const +{ + return new QgsJoinByLocationSummaryAlgorithm(); +} + +QVariantMap QgsJoinByLocationSummaryAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsProcessingFeatureSource > baseSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !baseSource ) + throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) ); + + std::unique_ptr< QgsProcessingFeatureSource > joinSource( parameterAsSource( parameters, QStringLiteral( "JOIN" ), context ) ); + if ( !joinSource ) + throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "JOIN" ) ) ); + + if ( joinSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent ) + feedback->reportError( QObject::tr( "No spatial index exists for join layer, performance will be severely degraded" ) ); + + QStringList joinedFieldNames = parameterAsStrings( parameters, QStringLiteral( "JOIN_FIELDS" ), context ); + + bool discardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context ); + + QList< int > summaries = parameterAsEnums( parameters, QStringLiteral( "SUMMARIES" ), context ); + if ( summaries.empty() ) + { + for ( int i = 0; i < mAllSummaries.size(); ++i ) + summaries << i; + } + + QgsFields sourceFields = baseSource->fields(); + QgsFields fieldsToJoin; + QList< int > joinFieldIndices; + if ( joinedFieldNames.empty() ) + { + // no fields selected, use all + for ( const QgsField &sourceField : joinSource->fields() ) + { + joinedFieldNames.append( sourceField.name() ); + } + } + + // Adds a field to the output, keeping the same data type as the original + auto addFieldKeepType = [&fieldsToJoin]( const QgsField & original, const QString & statistic ) + { + QgsField field = QgsField( original ); + field.setName( field.name() + '_' + statistic ); + fieldsToJoin.append( field ); + }; + + // Adds a field to the output, with a specified type + auto addFieldWithType = [&fieldsToJoin]( const QgsField & original, const QString & statistic, QVariant::Type type ) + { + QgsField field = QgsField( original ); + field.setName( field.name() + '_' + statistic ); + field.setType( type ); + if ( type == QVariant::Double ) + { + field.setLength( 20 ); + field.setPrecision( 6 ); + } + fieldsToJoin.append( field ); + }; + + enum class FieldType + { + Numeric, + DateTime, + String + }; + QList< FieldType > fieldTypes; + + struct FieldStatistic + { + FieldStatistic( int enumIndex, const QString &name, QVariant::Type type ) + : enumIndex( enumIndex ) + , name( name ) + , type( type ) + {} + + int enumIndex = 0; + QString name; + QVariant::Type type; + }; + static const QVector< FieldStatistic > sNumericStats + { + FieldStatistic( 0, QStringLiteral( "count" ), QVariant::LongLong ), + FieldStatistic( 1, QStringLiteral( "unique" ), QVariant::LongLong ), + FieldStatistic( 2, QStringLiteral( "min" ), QVariant::Double ), + FieldStatistic( 3, QStringLiteral( "max" ), QVariant::Double ), + FieldStatistic( 4, QStringLiteral( "range" ), QVariant::Double ), + FieldStatistic( 5, QStringLiteral( "sum" ), QVariant::Double ), + FieldStatistic( 6, QStringLiteral( "mean" ), QVariant::Double ), + FieldStatistic( 7, QStringLiteral( "median" ), QVariant::Double ), + FieldStatistic( 8, QStringLiteral( "stddev" ), QVariant::Double ), + FieldStatistic( 9, QStringLiteral( "minority" ), QVariant::Double ), + FieldStatistic( 10, QStringLiteral( "majority" ), QVariant::Double ), + FieldStatistic( 11, QStringLiteral( "q1" ), QVariant::Double ), + FieldStatistic( 12, QStringLiteral( "q3" ), QVariant::Double ), + FieldStatistic( 13, QStringLiteral( "iqr" ), QVariant::Double ), + }; + static const QVector< FieldStatistic > sDateTimeStats + { + FieldStatistic( 0, QStringLiteral( "count" ), QVariant::LongLong ), + FieldStatistic( 1, QStringLiteral( "unique" ), QVariant::LongLong ), + FieldStatistic( 14, QStringLiteral( "empty" ), QVariant::LongLong ), + FieldStatistic( 15, QStringLiteral( "filled" ), QVariant::LongLong ), + FieldStatistic( 2, QStringLiteral( "min" ), QVariant::Invalid ), + FieldStatistic( 3, QStringLiteral( "max" ), QVariant::Invalid ), + }; + static const QVector< FieldStatistic > sStringStats + { + FieldStatistic( 0, QStringLiteral( "count" ), QVariant::LongLong ), + FieldStatistic( 1, QStringLiteral( "unique" ), QVariant::LongLong ), + FieldStatistic( 14, QStringLiteral( "empty" ), QVariant::LongLong ), + FieldStatistic( 15, QStringLiteral( "filled" ), QVariant::LongLong ), + FieldStatistic( 2, QStringLiteral( "min" ), QVariant::Invalid ), + FieldStatistic( 3, QStringLiteral( "max" ), QVariant::Invalid ), + FieldStatistic( 16, QStringLiteral( "min_length" ), QVariant::Int ), + FieldStatistic( 17, QStringLiteral( "max_length" ), QVariant::Int ), + FieldStatistic( 18, QStringLiteral( "mean_length" ), QVariant::Double ), + }; + + for ( const QString &field : std::as_const( joinedFieldNames ) ) + { + const int fieldIndex = joinSource->fields().lookupField( field ); + if ( fieldIndex >= 0 ) + { + joinFieldIndices.append( fieldIndex ); + + const QgsField joinField = joinSource->fields().at( fieldIndex ); + QVector< FieldStatistic > statisticList; + if ( joinField.isNumeric() ) + { + fieldTypes.append( FieldType::Numeric ); + statisticList = sNumericStats; + } + else if ( joinField.type() == QVariant::Date + || joinField.type() == QVariant::Time + || joinField.type() == QVariant::DateTime ) + { + fieldTypes.append( FieldType::DateTime ); + statisticList = sDateTimeStats; + } + else + { + fieldTypes.append( FieldType::String ); + statisticList = sStringStats; + } + + for ( const FieldStatistic &statistic : std::as_const( statisticList ) ) + { + if ( summaries.contains( statistic.enumIndex ) ) + { + if ( statistic.type != QVariant::Invalid ) + addFieldWithType( joinField, statistic.name, statistic.type ); + else + addFieldKeepType( joinField, statistic.name ); + } + } + } + } + + const QgsFields outputFields = QgsProcessingUtils::combineFields( sourceFields, fieldsToJoin ); + + QString destId; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, destId, outputFields, + baseSource->wkbType(), baseSource->sourceCrs() ) ); + + if ( !sink ) + throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); + + + QList predicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context ); + QgsJoinByLocationAlgorithm::sortPredicates( predicates ); + + QgsFeatureIterator sourceIter = baseSource->getFeatures(); + QgsFeature f; + const double step = baseSource->featureCount() > 0 ? 100.0 / baseSource->featureCount() : 1; + long long i = 0; + while ( sourceIter.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + break; + + if ( !f.hasGeometry() ) + { + if ( !discardNonMatching ) + { + // ensure consistent count of attributes - otherwise non matching + // features will have incorrect attribute length + // and provider may reject them + f.resizeAttributes( outputFields.size() ); + sink->addFeature( f, QgsFeatureSink::FastInsert ); + } + continue; + } + + std::unique_ptr< QgsGeometryEngine > engine; + QVector< QVector< QVariant > > values; + + QgsFeatureRequest request; + request.setFilterRect( f.geometry().boundingBox() ); + request.setSubsetOfAttributes( joinFieldIndices ); + request.setDestinationCrs( baseSource->sourceCrs(), context.transformContext() ); + + QgsFeatureIterator joinIter = joinSource->getFeatures( request ); + QgsFeature testJoinFeature; + while ( joinIter.nextFeature( testJoinFeature ) ) + { + if ( feedback->isCanceled() ) + break; + + if ( !engine ) + { + engine.reset( QgsGeometry::createGeometryEngine( f.geometry().constGet() ) ); + engine->prepareGeometry(); + } + + if ( QgsJoinByLocationAlgorithm::featureFilter( testJoinFeature, engine.get(), true, predicates ) ) + { + QgsAttributes joinAttributes; + joinAttributes.reserve( joinFieldIndices.size() ); + for ( int joinIndex : std::as_const( joinFieldIndices ) ) + { + joinAttributes.append( testJoinFeature.attribute( joinIndex ) ); + } + values.append( joinAttributes ); + } + } + + i++; + feedback->setProgress( i * step ); + + if ( feedback->isCanceled() ) + break; + + if ( values.empty() ) + { + if ( discardNonMatching ) + { + continue; + } + else + { + // ensure consistent count of attributes - otherwise non matching + // features will have incorrect attribute length + // and provider may reject them + f.resizeAttributes( outputFields.size() ); + sink->addFeature( f, QgsFeatureSink::FastInsert ); + } + } + else + { + // calculate statistics + QgsAttributes outputAttributes = f.attributes(); + outputAttributes.reserve( outputFields.size() ); + for ( int fieldIndex = 0; fieldIndex < joinFieldIndices.size(); ++fieldIndex ) + { + const FieldType &fieldType = fieldTypes.at( fieldIndex ); + switch ( fieldType ) + { + case FieldType::Numeric: + { + QgsStatisticalSummary stat; + for ( const QVector< QVariant > &value : std::as_const( values ) ) + { + stat.addVariant( value.at( fieldIndex ) ); + } + stat.finalize(); + for ( const FieldStatistic &statistic : sNumericStats ) + { + if ( summaries.contains( statistic.enumIndex ) ) + { + QVariant val; + switch ( statistic.enumIndex ) + { + case 0: + val = stat.count(); + break; + case 1: + val = stat.variety(); + break; + case 2: + val = stat.min(); + break; + case 3: + val = stat.max(); + break; + case 4: + val = stat.range(); + break; + case 5: + val = stat.sum(); + break; + case 6: + val = stat.mean(); + break; + case 7: + val = stat.median(); + break; + case 8: + val = stat.stDev(); + break; + case 9: + val = stat.minority(); + break; + case 10: + val = stat.majority(); + break; + case 11: + val = stat.firstQuartile(); + break; + case 12: + val = stat.thirdQuartile(); + break; + case 13: + val = stat.interQuartileRange(); + break; + } + if ( val.isValid() && std::isnan( val.toDouble() ) ) + val = QVariant(); + outputAttributes.append( val ); + } + } + break; + } + + case FieldType::DateTime: + { + QgsDateTimeStatisticalSummary stat; + QVariantList inputValues; + inputValues.reserve( values.size() ); + for ( const QVector< QVariant > &value : std::as_const( values ) ) + { + inputValues << value.at( fieldIndex ); + } + stat.calculate( inputValues ); + for ( const FieldStatistic &statistic : sDateTimeStats ) + { + if ( summaries.contains( statistic.enumIndex ) ) + { + QVariant val; + switch ( statistic.enumIndex ) + { + case 0: + val = stat.count(); + break; + case 1: + val = stat.countDistinct(); + break; + case 2: + val = stat.min(); + break; + case 3: + val = stat.max(); + break; + case 14: + val = stat.countMissing(); + break; + case 15: + val = stat.count() - stat.countMissing(); + break; + } + outputAttributes.append( val ); + } + } + break; + } + + case FieldType::String: + { + QgsStringStatisticalSummary stat; + QVariantList inputValues; + inputValues.reserve( values.size() ); + for ( const QVector< QVariant > &value : std::as_const( values ) ) + { + if ( value.at( fieldIndex ).isNull() ) + stat.addString( QString() ); + else + stat.addString( value.at( fieldIndex ).toString() ); + } + stat.finalize(); + for ( const FieldStatistic &statistic : sStringStats ) + { + if ( summaries.contains( statistic.enumIndex ) ) + { + QVariant val; + switch ( statistic.enumIndex ) + { + case 0: + val = stat.count(); + break; + case 1: + val = stat.countDistinct(); + break; + case 2: + val = stat.min(); + break; + case 3: + val = stat.max(); + break; + case 14: + val = stat.countMissing(); + break; + case 15: + val = stat.count() - stat.countMissing(); + break; + case 16: + val = stat.minLength(); + break; + case 17: + val = stat.maxLength(); + break; + case 18: + val = stat.meanLength(); + break; + } + outputAttributes.append( val ); + } + } + break; + } + } + } + + f.setAttributes( outputAttributes ); + sink->addFeature( f, QgsFeatureSink::FastInsert ); + } + } + + sink.reset(); + + QVariantMap results; + results.insert( QStringLiteral( "OUTPUT" ), destId ); + return results; +} + +///@endcond + + + diff --git a/src/analysis/processing/qgsalgorithmjoinbylocationsummary.h b/src/analysis/processing/qgsalgorithmjoinbylocationsummary.h new file mode 100644 index 000000000000..9f0a8197d835 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmjoinbylocationsummary.h @@ -0,0 +1,62 @@ +/*************************************************************************** + qgsalgorithmjoinbylocationsummary.h + --------------------- + begin : September 2023 + copyright : (C) 2023 by Nyall Dawson + email : nyall dot dawson 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. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMJOINBYLOCATIONSUMMARY_H +#define QGSALGORITHMJOINBYLOCATIONSUMMARY_H + +#define SIP_NO_FILE + +#include "qgis.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native join by location (summary) algorithm + */ +class QgsJoinByLocationSummaryAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsJoinByLocationSummaryAlgorithm() = default; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QString shortDescription() const override; + QIcon icon() const override; + QString svgIconPath() const override; + QgsJoinByLocationSummaryAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + QStringList mAllSummaries; + +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMJOINBYLOCATIONSUMMARY_H + + diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 6e64d82366b2..c59c90626055 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -102,6 +102,7 @@ #include "qgsalgorithmhillshade.h" #include "qgsalgorithmjoinbyattribute.h" #include "qgsalgorithmjoinbylocation.h" +#include "qgsalgorithmjoinbylocationsummary.h" #include "qgsalgorithmjoinbynearest.h" #include "qgsalgorithmjoinwithlines.h" #include "qgsalgorithmimportphotos.h" @@ -377,6 +378,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsIntersectionAlgorithm() ); addAlgorithm( new QgsJoinByAttributeAlgorithm() ); addAlgorithm( new QgsJoinByLocationAlgorithm() ); + addAlgorithm( new QgsJoinByLocationSummaryAlgorithm() ); addAlgorithm( new QgsJoinByNearestAlgorithm() ); addAlgorithm( new QgsJoinWithLinesAlgorithm() ); addAlgorithm( new QgsKeepNBiggestPartsAlgorithm() ); From f53b31869252bb9610e7077992698ca666497b16 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Thu, 28 Sep 2023 06:44:04 +0200 Subject: [PATCH 136/151] Update docs and description for layout map item grid elements --- .../auto_generated/layout/qgslayoutitemmapgrid.sip.in | 10 +++++----- src/core/layout/qgslayoutitemmapgrid.h | 10 +++++----- src/ui/layout/qgslayoutmapgridwidgetbase.ui | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in index 19479a0ed3b4..d0e380bfe4c2 100644 --- a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in @@ -961,7 +961,7 @@ Sets the annotation length calculation mode. TickLengthMode rotatedAnnotationsLengthMode() const; %Docstring -Returns the grid frame style. +Returns the annotation length calculation mode. .. seealso:: :py:func:`setRotatedAnnotationsLengthMode` @@ -970,7 +970,7 @@ Returns the grid frame style. void setRotatedAnnotationsMinimumAngle( const double angle ); %Docstring -Sets the ``minimum`` angle (in degrees) below which annotated are not drawn. +Sets the ``minimum`` angle (in degrees) below which annotations are not drawn. .. seealso:: :py:func:`rotatedAnnotationsMinimumAngle` @@ -979,7 +979,7 @@ Sets the ``minimum`` angle (in degrees) below which annotated are not drawn. double rotatedAnnotationsMinimumAngle() const; %Docstring -Gets the ``minimum`` angle (in degrees) below which annotated are not drawn. +Gets the ``minimum`` angle (in degrees) below which annotations are not drawn. .. seealso:: :py:func:`setRotatedAnnotationsMinimumAngle` @@ -988,7 +988,7 @@ Gets the ``minimum`` angle (in degrees) below which annotated are not drawn. void setRotatedAnnotationsMarginToCorner( const double margin ); %Docstring -Sets the ``margin`` to corners (in canvas units) below which outwards facing ticks are not drawn. +Sets the ``margin`` to corners (in canvas units) below which outwards facing annotations are not drawn. .. seealso:: :py:func:`rotatedAnnotationsMarginToCorner` @@ -997,7 +997,7 @@ Sets the ``margin`` to corners (in canvas units) below which outwards facing tic double rotatedAnnotationsMarginToCorner() const; %Docstring -Gets the ``margin`` to corners (in canvas units) below which outwards facing ticks are not drawn. +Gets the ``margin`` to corners (in canvas units) below which outwards facing annotations are not drawn. .. seealso:: :py:func:`setRotatedAnnotationsMarginToCorner` diff --git a/src/core/layout/qgslayoutitemmapgrid.h b/src/core/layout/qgslayoutitemmapgrid.h index fdfb605f19a5..b400b33f6790 100644 --- a/src/core/layout/qgslayoutitemmapgrid.h +++ b/src/core/layout/qgslayoutitemmapgrid.h @@ -889,35 +889,35 @@ class CORE_EXPORT QgsLayoutItemMapGrid : public QgsLayoutItemMapItem void setRotatedAnnotationsLengthMode( const TickLengthMode mode ) { mRotatedAnnotationsLengthMode = mode; } /** - * Returns the grid frame style. + * Returns the annotation length calculation mode. * \see setRotatedAnnotationsLengthMode() * \since QGIS 3.16 */ TickLengthMode rotatedAnnotationsLengthMode() const { return mRotatedAnnotationsLengthMode; } /** - * Sets the \a minimum angle (in degrees) below which annotated are not drawn. + * Sets the \a minimum angle (in degrees) below which annotations are not drawn. * \see rotatedAnnotationsMinimumAngle() * \since QGIS 3.16 */ void setRotatedAnnotationsMinimumAngle( const double angle ) { mRotatedAnnotationsMinimumAngle = angle; } /** - * Gets the \a minimum angle (in degrees) below which annotated are not drawn. + * Gets the \a minimum angle (in degrees) below which annotations are not drawn. * \see setRotatedAnnotationsMinimumAngle() * \since QGIS 3.16 */ double rotatedAnnotationsMinimumAngle() const { return mRotatedAnnotationsMinimumAngle; } /** - * Sets the \a margin to corners (in canvas units) below which outwards facing ticks are not drawn. + * Sets the \a margin to corners (in canvas units) below which outwards facing annotations are not drawn. * \see rotatedAnnotationsMarginToCorner() * \since QGIS 3.16 */ void setRotatedAnnotationsMarginToCorner( const double margin ) { mRotatedAnnotationsMarginToCorner = margin; } /** - * Gets the \a margin to corners (in canvas units) below which outwards facing ticks are not drawn. + * Gets the \a margin to corners (in canvas units) below which outwards facing annotations are not drawn. * \see setRotatedAnnotationsMarginToCorner() * \since QGIS 3.16 */ diff --git a/src/ui/layout/qgslayoutmapgridwidgetbase.ui b/src/ui/layout/qgslayoutmapgridwidgetbase.ui index 21251747b9ad..f390972e0962 100644 --- a/src/ui/layout/qgslayoutmapgridwidgetbase.ui +++ b/src/ui/layout/qgslayoutmapgridwidgetbase.ui @@ -1008,7 +1008,7 @@ - Determines how the ticks length is defined when rotated. + Determines how the annotations position is defined when the grid is rotated. @@ -1022,7 +1022,7 @@ - Grid lines intersecting the border below this threshold will be ignored. + Annotations of grid lines intersecting the border below this threshold will be ignored. ° From e85392bd1af6459fd0fbf16d84cf4f78f5fad681 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Thu, 28 Sep 2023 06:44:53 +0200 Subject: [PATCH 137/151] Misc typo/misnaming dialog fixes --- src/core/qgslegendrenderer.h | 2 +- src/gui/qgsexpressionlineedit.cpp | 2 +- src/gui/qgsfieldexpressionwidget.cpp | 2 +- src/gui/qgsprojectionselectionwidget.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/qgslegendrenderer.h b/src/core/qgslegendrenderer.h index 626312b7182f..4ef58ae0a305 100644 --- a/src/core/qgslegendrenderer.h +++ b/src/core/qgslegendrenderer.h @@ -166,7 +166,7 @@ class CORE_EXPORT QgsLegendRenderer }; /** - * An component group is an indivisible set of legend components (i.e. it is indivisible into more columns). + * A component group is an indivisible set of legend components (i.e. it is indivisible into more columns). * * A group may consist of one or more component(s), depending on the layer splitting mode: * diff --git a/src/gui/qgsexpressionlineedit.cpp b/src/gui/qgsexpressionlineedit.cpp index 2fe98377a0f8..a23485168e35 100644 --- a/src/gui/qgsexpressionlineedit.cpp +++ b/src/gui/qgsexpressionlineedit.cpp @@ -31,7 +31,7 @@ QgsExpressionLineEdit::QgsExpressionLineEdit( QWidget *parent ) : QWidget( parent ) - , mExpressionDialogTitle( tr( "Expression Dialog" ) ) + , mExpressionDialogTitle( tr( "Expression String Builder" ) ) { mButton = new QToolButton(); mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); diff --git a/src/gui/qgsfieldexpressionwidget.cpp b/src/gui/qgsfieldexpressionwidget.cpp index d8b90ad39c89..49bb9c46f30c 100644 --- a/src/gui/qgsfieldexpressionwidget.cpp +++ b/src/gui/qgsfieldexpressionwidget.cpp @@ -31,7 +31,7 @@ QgsFieldExpressionWidget::QgsFieldExpressionWidget( QWidget *parent ) : QWidget( parent ) - , mExpressionDialogTitle( tr( "Expression Dialog" ) ) + , mExpressionDialogTitle( tr( "Expression String Builder" ) ) , mDistanceArea( nullptr ) { diff --git a/src/gui/qgsprojectionselectionwidget.cpp b/src/gui/qgsprojectionselectionwidget.cpp index b4033b36cb9b..8a0d7ff55722 100644 --- a/src/gui/qgsprojectionselectionwidget.cpp +++ b/src/gui/qgsprojectionselectionwidget.cpp @@ -26,7 +26,7 @@ QgsProjectionSelectionWidget::QgsProjectionSelectionWidget( QWidget *parent ) : QWidget( parent ) - , mDialogTitle( tr( "Select CRS" ) ) + , mDialogTitle( tr( "Coordinate Reference System Selector" ) ) { mCrsComboBox = new QgsHighlightableComboBox( this ); From 136cf92a8076feac33e87666bb5ff16acf353e88 Mon Sep 17 00:00:00 2001 From: Harrissou Sant-anna Date: Fri, 29 Sep 2023 09:04:02 +0200 Subject: [PATCH 138/151] Expression string builder --> Expression builder --- src/gui/qgsexpressionbuilderdialog.cpp | 2 +- src/gui/qgsexpressionbuilderdialog.h | 2 +- src/gui/qgsexpressionbuilderwidget.cpp | 2 +- src/gui/qgsexpressionbuilderwidget.h | 2 +- src/gui/qgsexpressionlineedit.cpp | 2 +- src/gui/qgsfieldexpressionwidget.cpp | 2 +- src/ui/qgsexpressionbuilderdialogbase.ui | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/qgsexpressionbuilderdialog.cpp b/src/gui/qgsexpressionbuilderdialog.cpp index b2d656441da7..aba6bbabb48d 100644 --- a/src/gui/qgsexpressionbuilderdialog.cpp +++ b/src/gui/qgsexpressionbuilderdialog.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgisexpressionbuilderdialog.h - A generic expression string builder dialog. + qgisexpressionbuilderdialog.h - A generic expression builder dialog. -------------------------------------- Date : 29-May-2011 Copyright : (C) 2011 by Nathan Woodrow diff --git a/src/gui/qgsexpressionbuilderdialog.h b/src/gui/qgsexpressionbuilderdialog.h index df7ee404ab96..4a435c1ffe0b 100644 --- a/src/gui/qgsexpressionbuilderdialog.h +++ b/src/gui/qgsexpressionbuilderdialog.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgisexpressionbuilderdialog.h - A generic expression string builder dialog. + qgisexpressionbuilderdialog.h - A generic expression builder dialog. -------------------------------------- Date : 29-May-2011 Copyright : (C) 2011 by Nathan Woodrow diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 1e57463c36fe..f698dfbdc860 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgisexpressionbuilderwidget.cpp - A generic expression string builder widget. + qgisexpressionbuilderwidget.cpp - A generic expression builder widget. -------------------------------------- Date : 29-May-2011 Copyright : (C) 2011 by Nathan Woodrow diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index acc13afd322d..df4987f15b1d 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgisexpressionbuilderwidget.h - A generic expression string builder widget. + qgisexpressionbuilderwidget.h - A generic expression builder widget. -------------------------------------- Date : 29-May-2011 Copyright : (C) 2011 by Nathan Woodrow diff --git a/src/gui/qgsexpressionlineedit.cpp b/src/gui/qgsexpressionlineedit.cpp index a23485168e35..2eaeba2e79b2 100644 --- a/src/gui/qgsexpressionlineedit.cpp +++ b/src/gui/qgsexpressionlineedit.cpp @@ -31,7 +31,7 @@ QgsExpressionLineEdit::QgsExpressionLineEdit( QWidget *parent ) : QWidget( parent ) - , mExpressionDialogTitle( tr( "Expression String Builder" ) ) + , mExpressionDialogTitle( tr( "Expression Builder" ) ) { mButton = new QToolButton(); mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); diff --git a/src/gui/qgsfieldexpressionwidget.cpp b/src/gui/qgsfieldexpressionwidget.cpp index 49bb9c46f30c..3422ec9a2b1c 100644 --- a/src/gui/qgsfieldexpressionwidget.cpp +++ b/src/gui/qgsfieldexpressionwidget.cpp @@ -31,7 +31,7 @@ QgsFieldExpressionWidget::QgsFieldExpressionWidget( QWidget *parent ) : QWidget( parent ) - , mExpressionDialogTitle( tr( "Expression String Builder" ) ) + , mExpressionDialogTitle( tr( "Expression Builder" ) ) , mDistanceArea( nullptr ) { diff --git a/src/ui/qgsexpressionbuilderdialogbase.ui b/src/ui/qgsexpressionbuilderdialogbase.ui index 265fe76c7669..4836b423fb3c 100644 --- a/src/ui/qgsexpressionbuilderdialogbase.ui +++ b/src/ui/qgsexpressionbuilderdialogbase.ui @@ -11,7 +11,7 @@ - Expression String Builder + Expression Builder From cc1528476b5824086507b3d9c4844954f0d29b37 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 26 Sep 2023 14:53:07 +0200 Subject: [PATCH 139/151] Deactivate progress bar when no nodes --- src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp index 50b894579f96..b5f561253ce4 100644 --- a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp +++ b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp @@ -470,6 +470,10 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera } double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); + if ( nodes.empty() ) + { + return false; + } const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span(); const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel; From 5620ed79b1aee3699826e02e84cb0fc53b50f88d Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 29 Sep 2023 15:23:10 +0200 Subject: [PATCH 140/151] Update tests --- tests/src/python/test_qgspointcloudlayerprofilegenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/python/test_qgspointcloudlayerprofilegenerator.py b/tests/src/python/test_qgspointcloudlayerprofilegenerator.py index bad06e4ded0b..7dc6b4a7a989 100644 --- a/tests/src/python/test_qgspointcloudlayerprofilegenerator.py +++ b/tests/src/python/test_qgspointcloudlayerprofilegenerator.py @@ -66,9 +66,9 @@ def testProfileGeneration(self): req.setCrs(pcl.crs()) # zero tolerance => no points generator = pcl.createProfileGenerator(req) - self.assertTrue(generator.generateProfile()) + self.assertFalse(generator.generateProfile()) results = generator.takeResults() - self.assertFalse(results.distanceToHeightMap()) + self.assertTrue(results is None) req.setTolerance(0.05) generator = pcl.createProfileGenerator(req) From ed64f37b51bdf4b5024d2251b4fe5020de14f50d Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 25 Sep 2023 13:11:24 +0200 Subject: [PATCH 141/151] SPATIALITE: fix insert incompatible geometry types Fix #54662 Logic shamelessly copied from potgres provider. --- .../spatialite/qgsspatialiteprovider.cpp | 126 +++++++++++++++++- .../spatialite/qgsspatialiteprovider.h | 3 + tests/src/python/test_provider_spatialite.py | 29 +++- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index e01cc913c86c..4a6ffef8caf0 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -18,6 +18,10 @@ email : a.furieri@lqt.it #include "qgsfeature.h" #include "qgsfields.h" #include "qgsgeometry.h" +#include "qgsgeometryfactory.h" +#include "qgsgeometrycollection.h" +#include "qgscompoundcurve.h" +#include "qgscircularstring.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" #include "qgslogger.h" @@ -127,6 +131,125 @@ bool QgsSpatiaLiteProvider::convertField( QgsField &field ) return true; } +QgsGeometry QgsSpatiaLiteProvider::convertToProviderType( const QgsGeometry &geom ) const +{ + if ( geom.isNull() ) + { + return QgsGeometry(); + } + + const QgsAbstractGeometry *geometry = geom.constGet(); + if ( !geometry ) + { + return QgsGeometry(); + } + + const Qgis::WkbType providerGeomType = wkbType(); + + //geom is already in the provider geometry type + if ( geometry->wkbType() == providerGeomType ) + { + return QgsGeometry(); + } + + std::unique_ptr< QgsAbstractGeometry > outputGeom; + + //convert compoundcurve to circularstring (possible if compoundcurve consists of one circular string) + if ( QgsWkbTypes::flatType( providerGeomType ) == Qgis::WkbType::CircularString ) + { + QgsCompoundCurve *compoundCurve = qgsgeometry_cast( geometry ); + if ( compoundCurve ) + { + if ( compoundCurve->nCurves() == 1 ) + { + const QgsCircularString *circularString = qgsgeometry_cast( compoundCurve->curveAt( 0 ) ); + if ( circularString ) + { + outputGeom.reset( circularString->clone() ); + } + } + } + } + + //convert to curved type if necessary + if ( !QgsWkbTypes::isCurvedType( geometry->wkbType() ) && QgsWkbTypes::isCurvedType( providerGeomType ) ) + { + QgsAbstractGeometry *curveGeom = outputGeom ? outputGeom->toCurveType() : geometry->toCurveType(); + if ( curveGeom ) + { + outputGeom.reset( curveGeom ); + } + } + + //convert to linear type from curved type + if ( QgsWkbTypes::isCurvedType( geometry->wkbType() ) && !QgsWkbTypes::isCurvedType( providerGeomType ) ) + { + QgsAbstractGeometry *segmentizedGeom = outputGeom ? outputGeom->segmentize() : geometry->segmentize(); + if ( segmentizedGeom ) + { + outputGeom.reset( segmentizedGeom ); + } + } + + //convert to multitype if necessary + if ( QgsWkbTypes::isMultiType( providerGeomType ) && !QgsWkbTypes::isMultiType( geometry->wkbType() ) ) + { + std::unique_ptr< QgsAbstractGeometry > collGeom( QgsGeometryFactory::geomFromWkbType( providerGeomType ) ); + QgsGeometryCollection *geomCollection = qgsgeometry_cast( collGeom.get() ); + if ( geomCollection ) + { + if ( geomCollection->addGeometry( outputGeom ? outputGeom->clone() : geometry->clone() ) ) + { + outputGeom.reset( collGeom.release() ); + } + } + } + + //convert to single type if there's a single part of compatible type + if ( !QgsWkbTypes::isMultiType( providerGeomType ) && QgsWkbTypes::isMultiType( geometry->wkbType() ) ) + { + const QgsGeometryCollection *collection = qgsgeometry_cast( geometry ); + if ( collection ) + { + if ( collection->numGeometries() == 1 ) + { + const QgsAbstractGeometry *firstGeom = collection->geometryN( 0 ); + if ( firstGeom && firstGeom->wkbType() == providerGeomType ) + { + outputGeom.reset( firstGeom->clone() ); + } + } + } + } + + //set z/m types + if ( QgsWkbTypes::hasZ( providerGeomType ) ) + { + if ( !outputGeom ) + { + outputGeom.reset( geometry->clone() ); + } + outputGeom->addZValue(); + } + + if ( QgsWkbTypes::hasM( providerGeomType ) ) + { + if ( !outputGeom ) + { + outputGeom.reset( geometry->clone() ); + } + outputGeom->addMValue(); + } + + if ( outputGeom ) + { + return QgsGeometry( outputGeom.release() ); + } + + return QgsGeometry(); + +} + Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString &uri, const QgsFields &fields, @@ -4230,7 +4353,8 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { unsigned char *wkb = nullptr; int wkb_size; - QByteArray featureWkb = feature->geometry().asWkb(); + const QgsGeometry convertedGeom( convertToProviderType( feature->geometry() ) ); + const QByteArray featureWkb{ !convertedGeom.isNull() ? convertedGeom.asWkb() : feature->geometry().asWkb() }; convertFromGeosWKB( reinterpret_cast( featureWkb.constData() ), featureWkb.length(), &wkb, &wkb_size, nDims ); diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index eb2d1379f50f..0c6430a2f01c 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -218,6 +218,9 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider //! Convert a QgsField to work with SL static bool convertField( QgsField &field ); + //! Convert a geometry to a compatible type, borrowed from Posgres provider. + QgsGeometry convertToProviderType( const QgsGeometry &geom ) const; + QString geomParam() const; //! Gets SpatiaLite version string diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index 8be1afc209c1..b76dbc9960c4 100644 --- a/tests/src/python/test_provider_spatialite.py +++ b/tests/src/python/test_provider_spatialite.py @@ -45,7 +45,7 @@ from qgis.utils import spatialite_connect from providertestbase import ProviderTestCase -from utilities import unitTestDataPath +from utilities import unitTestDataPath, compareWkt # Pass no_exit=True: for some reason this crashes sometimes on exit on Travis start_app(True) @@ -1885,6 +1885,33 @@ def test_absolute_relative_uri(self): self.assertEqual(meta.absoluteToRelativeUri(absolute_uri, context), relative_uri) self.assertEqual(meta.relativeToAbsoluteUri(relative_uri, context), absolute_uri) + def testRegression54622Multisurface(self): + + con = spatialite_connect(self.dbname, isolation_level=None) + cur = con.cursor() + cur.execute("BEGIN") + sql = sql = """CREATE TABLE table54622 ( + _id INTEGER PRIMARY KEY AUTOINCREMENT)""" + cur.execute(sql) + sql = "SELECT AddGeometryColumn('table54622', 'geometry', 25832, 'MULTIPOLYGON', 'XY', 0)" + cur.execute(sql) + cur.execute("COMMIT") + con.close() + + layer = QgsVectorLayer( + 'dbname=\'{}\' table="table54622" (geometry) sql='.format(self.dbname), 'test', 'spatialite') + + self.assertTrue(layer.isValid()) + feature = QgsFeature(layer.fields()) + feature.setGeometry(QgsGeometry.fromWkt('MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((-0.886 0.135,-0.886 -0.038,-0.448 -0.070,-0.427 0.144,-0.886 0.135))))')) + self.assertTrue(layer.dataProvider().addFeatures([feature])) + + layer = QgsVectorLayer( + 'dbname=\'{}\' table="table54622" (geometry) sql='.format(self.dbname), 'test', 'spatialite') + feature = next(layer.getFeatures()) + self.assertFalse(feature.geometry().isNull()) + self.assertTrue(compareWkt(feature.geometry().asWkt(), 'MultiPolygon (((-0.886 0.135, -0.886 -0.038, -0.448 -0.070, -0.426 0.143, -0.886 0.135)))', 0.01)) + if __name__ == '__main__': unittest.main() From 30cd5124484b258f9054ad1e41487c87c415ab25 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 28 Sep 2023 13:15:03 +0200 Subject: [PATCH 142/151] Address pr comments --- .../spatialite/qgsspatialiteprovider.cpp | 123 +----------------- .../spatialite/qgsspatialiteprovider.h | 3 - tests/src/python/test_provider_spatialite.py | 24 +++- 3 files changed, 21 insertions(+), 129 deletions(-) diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 4a6ffef8caf0..593f48220065 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -131,124 +131,6 @@ bool QgsSpatiaLiteProvider::convertField( QgsField &field ) return true; } -QgsGeometry QgsSpatiaLiteProvider::convertToProviderType( const QgsGeometry &geom ) const -{ - if ( geom.isNull() ) - { - return QgsGeometry(); - } - - const QgsAbstractGeometry *geometry = geom.constGet(); - if ( !geometry ) - { - return QgsGeometry(); - } - - const Qgis::WkbType providerGeomType = wkbType(); - - //geom is already in the provider geometry type - if ( geometry->wkbType() == providerGeomType ) - { - return QgsGeometry(); - } - - std::unique_ptr< QgsAbstractGeometry > outputGeom; - - //convert compoundcurve to circularstring (possible if compoundcurve consists of one circular string) - if ( QgsWkbTypes::flatType( providerGeomType ) == Qgis::WkbType::CircularString ) - { - QgsCompoundCurve *compoundCurve = qgsgeometry_cast( geometry ); - if ( compoundCurve ) - { - if ( compoundCurve->nCurves() == 1 ) - { - const QgsCircularString *circularString = qgsgeometry_cast( compoundCurve->curveAt( 0 ) ); - if ( circularString ) - { - outputGeom.reset( circularString->clone() ); - } - } - } - } - - //convert to curved type if necessary - if ( !QgsWkbTypes::isCurvedType( geometry->wkbType() ) && QgsWkbTypes::isCurvedType( providerGeomType ) ) - { - QgsAbstractGeometry *curveGeom = outputGeom ? outputGeom->toCurveType() : geometry->toCurveType(); - if ( curveGeom ) - { - outputGeom.reset( curveGeom ); - } - } - - //convert to linear type from curved type - if ( QgsWkbTypes::isCurvedType( geometry->wkbType() ) && !QgsWkbTypes::isCurvedType( providerGeomType ) ) - { - QgsAbstractGeometry *segmentizedGeom = outputGeom ? outputGeom->segmentize() : geometry->segmentize(); - if ( segmentizedGeom ) - { - outputGeom.reset( segmentizedGeom ); - } - } - - //convert to multitype if necessary - if ( QgsWkbTypes::isMultiType( providerGeomType ) && !QgsWkbTypes::isMultiType( geometry->wkbType() ) ) - { - std::unique_ptr< QgsAbstractGeometry > collGeom( QgsGeometryFactory::geomFromWkbType( providerGeomType ) ); - QgsGeometryCollection *geomCollection = qgsgeometry_cast( collGeom.get() ); - if ( geomCollection ) - { - if ( geomCollection->addGeometry( outputGeom ? outputGeom->clone() : geometry->clone() ) ) - { - outputGeom.reset( collGeom.release() ); - } - } - } - - //convert to single type if there's a single part of compatible type - if ( !QgsWkbTypes::isMultiType( providerGeomType ) && QgsWkbTypes::isMultiType( geometry->wkbType() ) ) - { - const QgsGeometryCollection *collection = qgsgeometry_cast( geometry ); - if ( collection ) - { - if ( collection->numGeometries() == 1 ) - { - const QgsAbstractGeometry *firstGeom = collection->geometryN( 0 ); - if ( firstGeom && firstGeom->wkbType() == providerGeomType ) - { - outputGeom.reset( firstGeom->clone() ); - } - } - } - } - - //set z/m types - if ( QgsWkbTypes::hasZ( providerGeomType ) ) - { - if ( !outputGeom ) - { - outputGeom.reset( geometry->clone() ); - } - outputGeom->addZValue(); - } - - if ( QgsWkbTypes::hasM( providerGeomType ) ) - { - if ( !outputGeom ) - { - outputGeom.reset( geometry->clone() ); - } - outputGeom->addMValue(); - } - - if ( outputGeom ) - { - return QgsGeometry( outputGeom.release() ); - } - - return QgsGeometry(); - -} Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString &uri, @@ -4471,7 +4353,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) } else { - // some unexpected error occurred + // some unecompxpected error occurred const char *err = sqlite3_errmsg( sqliteHandle( ) ); errMsg = ( char * ) sqlite3_malloc( ( int ) strlen( err ) + 1 ); strcpy( errMsg, err ); @@ -4964,7 +4846,8 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry // binding GEOMETRY to Prepared Statement unsigned char *wkb = nullptr; int wkb_size; - QByteArray iterWkb = iter->asWkb(); + const QgsGeometry convertedGeom( convertToProviderType( *iter ) ); + const QByteArray iterWkb{ !convertedGeom.isNull() ? convertedGeom.asWkb() : iter->asWkb() }; convertFromGeosWKB( reinterpret_cast( iterWkb.constData() ), iterWkb.length(), &wkb, &wkb_size, nDims ); if ( !wkb ) sqlite3_bind_null( stmt, 1 ); diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index 0c6430a2f01c..eb2d1379f50f 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -218,9 +218,6 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider //! Convert a QgsField to work with SL static bool convertField( QgsField &field ); - //! Convert a geometry to a compatible type, borrowed from Posgres provider. - QgsGeometry convertToProviderType( const QgsGeometry &geom ) const; - QString geomParam() const; //! Gets SpatiaLite version string diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index b76dbc9960c4..3d07ef3d8cbb 100644 --- a/tests/src/python/test_provider_spatialite.py +++ b/tests/src/python/test_provider_spatialite.py @@ -1898,19 +1898,31 @@ def testRegression54622Multisurface(self): cur.execute("COMMIT") con.close() + def _check_feature(): + layer = QgsVectorLayer( + 'dbname=\'{}\' table="table54622" (geometry) sql='.format(self.dbname), 'test', 'spatialite') + feature = next(layer.getFeatures()) + self.assertFalse(feature.geometry().isNull()) + self.assertTrue(compareWkt(feature.geometry().asWkt(), 'MultiPolygon (((-0.886 0.135, -0.886 -0.038, -0.448 -0.070, -0.426 0.143, -0.886 0.135)))', 0.01)) + layer = QgsVectorLayer( 'dbname=\'{}\' table="table54622" (geometry) sql='.format(self.dbname), 'test', 'spatialite') self.assertTrue(layer.isValid()) feature = QgsFeature(layer.fields()) - feature.setGeometry(QgsGeometry.fromWkt('MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((-0.886 0.135,-0.886 -0.038,-0.448 -0.070,-0.427 0.144,-0.886 0.135))))')) + geom = QgsGeometry.fromWkt('MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE((-0.886 0.135,-0.886 -0.038,-0.448 -0.070,-0.427 0.144,-0.886 0.135))))') + feature.setGeometry(geom) self.assertTrue(layer.dataProvider().addFeatures([feature])) - layer = QgsVectorLayer( - 'dbname=\'{}\' table="table54622" (geometry) sql='.format(self.dbname), 'test', 'spatialite') - feature = next(layer.getFeatures()) - self.assertFalse(feature.geometry().isNull()) - self.assertTrue(compareWkt(feature.geometry().asWkt(), 'MultiPolygon (((-0.886 0.135, -0.886 -0.038, -0.448 -0.070, -0.426 0.143, -0.886 0.135)))', 0.01)) + _check_feature() + + self.assertTrue(layer.dataProvider().changeFeatures({}, {feature.id(): geom})) + + _check_feature() + + self.assertTrue(layer.dataProvider().changeGeometryValues({feature.id(): geom})) + + _check_feature() if __name__ == '__main__': From d462078894b8edd7312a2eab78b96ec13b807498 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 28 Sep 2023 13:16:53 +0200 Subject: [PATCH 143/151] remove imports --- src/providers/spatialite/qgsspatialiteprovider.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 593f48220065..4e8987a5ded1 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -18,10 +18,6 @@ email : a.furieri@lqt.it #include "qgsfeature.h" #include "qgsfields.h" #include "qgsgeometry.h" -#include "qgsgeometryfactory.h" -#include "qgsgeometrycollection.h" -#include "qgscompoundcurve.h" -#include "qgscircularstring.h" #include "qgsrectangle.h" #include "qgscoordinatereferencesystem.h" #include "qgslogger.h" From d98fb4d2cf682a448a48f6e1abf6d65f742d1538 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 28 Sep 2023 13:18:09 +0200 Subject: [PATCH 144/151] typo --- src/providers/spatialite/qgsspatialiteprovider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 4e8987a5ded1..dc2b49e0566d 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -4349,7 +4349,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) } else { - // some unecompxpected error occurred + // some unexpected error occurred const char *err = sqlite3_errmsg( sqliteHandle( ) ); errMsg = ( char * ) sqlite3_malloc( ( int ) strlen( err ) + 1 ); strcpy( errMsg, err ); From 3cac4c0b0be793a3eb18a9b8a51d4ed5f9fa5de2 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Sat, 30 Sep 2023 09:34:34 +0200 Subject: [PATCH 145/151] Make convertToProviderType static to bypass thread check --- .../vector/qgsvectordataprovider.sip.in | 14 ++++ src/core/vector/qgsvectordataprovider.cpp | 78 ++++++++++--------- src/core/vector/qgsvectordataprovider.h | 9 +++ .../postgres/qgspostgresprovider.cpp | 2 +- .../spatialite/qgsspatialiteprovider.cpp | 2 +- 5 files changed, 67 insertions(+), 38 deletions(-) diff --git a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index c0f06e3156bb..3e828ae30e44 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -703,6 +703,10 @@ For general debug information use :py:func:`QgsMessageLog.logMessage()` instead. %Docstring Converts the geometry to the provider type if possible / necessary +.. note:: + + The default implementation simply calls the static version of this function. + :return: the converted geometry or ``None`` if no conversion was necessary or possible %End @@ -721,6 +725,16 @@ Gets this providers encoding .. versionadded:: 3.0 %End + static QgsGeometry convertToProviderType( const QgsGeometry &geometry, Qgis::WkbType providerGeometryType ); +%Docstring +Converts the ``geometry`` to the provider geometry type ``providerGeometryType`` if possible / necessary + +:return: the converted geometry or ``None`` if no conversion was necessary or possible + +.. versionadded:: 3.34 +%End + + }; QFlags operator|(QgsVectorDataProvider::Capability f1, QFlags f2); diff --git a/src/core/vector/qgsvectordataprovider.cpp b/src/core/vector/qgsvectordataprovider.cpp index 234327063de0..bad47cdfe948 100644 --- a/src/core/vector/qgsvectordataprovider.cpp +++ b/src/core/vector/qgsvectordataprovider.cpp @@ -898,21 +898,42 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - if ( geom.isNull() ) + // Call the static version + return QgsVectorDataProvider::convertToProviderType( geom, wkbType() ); + +} + +void QgsVectorDataProvider::setNativeTypes( const QList &nativeTypes ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + mNativeTypes = nativeTypes; +} + +QTextCodec *QgsVectorDataProvider::textEncoding() const +{ + // non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this + QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL + + return mEncoding; +} + +QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geometry, Qgis::WkbType providerGeometryType ) +{ + if ( geometry.isNull() ) { return QgsGeometry(); } - const QgsAbstractGeometry *geometry = geom.constGet(); - if ( !geometry ) + const QgsAbstractGeometry *convertedGeometry = geometry.constGet(); + if ( !convertedGeometry ) { return QgsGeometry(); } - const Qgis::WkbType providerGeomType = wkbType(); //geom is already in the provider geometry type - if ( geometry->wkbType() == providerGeomType ) + if ( convertedGeometry->wkbType() == providerGeometryType ) { return QgsGeometry(); } @@ -920,9 +941,9 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo std::unique_ptr< QgsAbstractGeometry > outputGeom; //convert compoundcurve to circularstring (possible if compoundcurve consists of one circular string) - if ( QgsWkbTypes::flatType( providerGeomType ) == Qgis::WkbType::CircularString ) + if ( QgsWkbTypes::flatType( providerGeometryType ) == Qgis::WkbType::CircularString ) { - QgsCompoundCurve *compoundCurve = qgsgeometry_cast( geometry ); + QgsCompoundCurve *compoundCurve = qgsgeometry_cast( convertedGeometry ); if ( compoundCurve ) { if ( compoundCurve->nCurves() == 1 ) @@ -937,9 +958,9 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo } //convert to curved type if necessary - if ( !QgsWkbTypes::isCurvedType( geometry->wkbType() ) && QgsWkbTypes::isCurvedType( providerGeomType ) ) + if ( !QgsWkbTypes::isCurvedType( convertedGeometry->wkbType() ) && QgsWkbTypes::isCurvedType( providerGeometryType ) ) { - QgsAbstractGeometry *curveGeom = outputGeom ? outputGeom->toCurveType() : geometry->toCurveType(); + QgsAbstractGeometry *curveGeom = outputGeom ? outputGeom->toCurveType() : convertedGeometry->toCurveType(); if ( curveGeom ) { outputGeom.reset( curveGeom ); @@ -947,9 +968,9 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo } //convert to linear type from curved type - if ( QgsWkbTypes::isCurvedType( geometry->wkbType() ) && !QgsWkbTypes::isCurvedType( providerGeomType ) ) + if ( QgsWkbTypes::isCurvedType( convertedGeometry->wkbType() ) && !QgsWkbTypes::isCurvedType( providerGeometryType ) ) { - QgsAbstractGeometry *segmentizedGeom = outputGeom ? outputGeom->segmentize() : geometry->segmentize(); + QgsAbstractGeometry *segmentizedGeom = outputGeom ? outputGeom->segmentize() : convertedGeometry->segmentize(); if ( segmentizedGeom ) { outputGeom.reset( segmentizedGeom ); @@ -957,13 +978,13 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo } //convert to multitype if necessary - if ( QgsWkbTypes::isMultiType( providerGeomType ) && !QgsWkbTypes::isMultiType( geometry->wkbType() ) ) + if ( QgsWkbTypes::isMultiType( providerGeometryType ) && !QgsWkbTypes::isMultiType( convertedGeometry->wkbType() ) ) { - std::unique_ptr< QgsAbstractGeometry > collGeom( QgsGeometryFactory::geomFromWkbType( providerGeomType ) ); + std::unique_ptr< QgsAbstractGeometry > collGeom( QgsGeometryFactory::geomFromWkbType( providerGeometryType ) ); QgsGeometryCollection *geomCollection = qgsgeometry_cast( collGeom.get() ); if ( geomCollection ) { - if ( geomCollection->addGeometry( outputGeom ? outputGeom->clone() : geometry->clone() ) ) + if ( geomCollection->addGeometry( outputGeom ? outputGeom->clone() : convertedGeometry->clone() ) ) { outputGeom.reset( collGeom.release() ); } @@ -971,15 +992,15 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo } //convert to single type if there's a single part of compatible type - if ( !QgsWkbTypes::isMultiType( providerGeomType ) && QgsWkbTypes::isMultiType( geometry->wkbType() ) ) + if ( !QgsWkbTypes::isMultiType( providerGeometryType ) && QgsWkbTypes::isMultiType( convertedGeometry->wkbType() ) ) { - const QgsGeometryCollection *collection = qgsgeometry_cast( geometry ); + const QgsGeometryCollection *collection = qgsgeometry_cast( convertedGeometry ); if ( collection ) { if ( collection->numGeometries() == 1 ) { const QgsAbstractGeometry *firstGeom = collection->geometryN( 0 ); - if ( firstGeom && firstGeom->wkbType() == providerGeomType ) + if ( firstGeom && firstGeom->wkbType() == providerGeometryType ) { outputGeom.reset( firstGeom->clone() ); } @@ -988,20 +1009,20 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo } //set z/m types - if ( QgsWkbTypes::hasZ( providerGeomType ) ) + if ( QgsWkbTypes::hasZ( providerGeometryType ) ) { if ( !outputGeom ) { - outputGeom.reset( geometry->clone() ); + outputGeom.reset( convertedGeometry->clone() ); } outputGeom->addZValue(); } - if ( QgsWkbTypes::hasM( providerGeomType ) ) + if ( QgsWkbTypes::hasM( providerGeometryType ) ) { if ( !outputGeom ) { - outputGeom.reset( geometry->clone() ); + outputGeom.reset( convertedGeometry->clone() ); } outputGeom->addMValue(); } @@ -1014,21 +1035,6 @@ QgsGeometry QgsVectorDataProvider::convertToProviderType( const QgsGeometry &geo return QgsGeometry(); } -void QgsVectorDataProvider::setNativeTypes( const QList &nativeTypes ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - mNativeTypes = nativeTypes; -} - -QTextCodec *QgsVectorDataProvider::textEncoding() const -{ - // non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this - QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL - - return mEncoding; -} - bool QgsVectorDataProvider::cancelReload() { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/vector/qgsvectordataprovider.h b/src/core/vector/qgsvectordataprovider.h index b50a06f618f3..28890386c448 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -680,6 +680,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat /** * Converts the geometry to the provider type if possible / necessary + * \note The default implementation simply calls the static version of this function. * \returns the converted geometry or NULLPTR if no conversion was necessary or possible */ QgsGeometry convertToProviderType( const QgsGeometry &geom ) const; @@ -699,6 +700,14 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat */ QTextCodec *textEncoding() const; + /** + * Converts the \a geometry to the provider geometry type \a providerGeometryType if possible / necessary + * \returns the converted geometry or NULLPTR if no conversion was necessary or possible + * \since QGIS 3.34 + */ + static QgsGeometry convertToProviderType( const QgsGeometry &geometry, Qgis::WkbType providerGeometryType ); + + private: mutable bool mCacheMinMaxDirty = true; mutable QMap mCacheMinValues, mCacheMaxValues; diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 3ca8ff05fd99..030b9a114f53 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -3312,7 +3312,7 @@ void QgsPostgresProvider::appendGeomParam( const QgsGeometry &geom, QStringList QString param; - QgsGeometry convertedGeom( convertToProviderType( geom ) ); + const QgsGeometry convertedGeom( convertToProviderType( geom, wkbType() ) ); QByteArray wkb( !convertedGeom.isNull() ? convertedGeom.asWkb() : geom.asWkb() ); const unsigned char *buf = reinterpret_cast< const unsigned char * >( wkb.constData() ); int wkbSize = wkb.length(); diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index dc2b49e0566d..126a145523c1 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -4231,7 +4231,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { unsigned char *wkb = nullptr; int wkb_size; - const QgsGeometry convertedGeom( convertToProviderType( feature->geometry() ) ); + const QgsGeometry convertedGeom( QgsVectorDataProvider::convertToProviderType( feature->geometry(), wkbType() ) ); const QByteArray featureWkb{ !convertedGeom.isNull() ? convertedGeom.asWkb() : feature->geometry().asWkb() }; convertFromGeosWKB( reinterpret_cast( featureWkb.constData() ), featureWkb.length(), From 7bd457b2bdd8cdd1dcfe247f70b70e89b7f073cd Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Sat, 30 Sep 2023 10:02:02 +0200 Subject: [PATCH 146/151] doxy --- .../core/auto_generated/vector/qgsvectordataprovider.sip.in | 4 ++-- src/core/vector/qgsvectordataprovider.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index 3e828ae30e44..733976f4aeae 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -703,11 +703,11 @@ For general debug information use :py:func:`QgsMessageLog.logMessage()` instead. %Docstring Converts the geometry to the provider type if possible / necessary +:return: the converted geometry or ``None`` if no conversion was necessary or possible + .. note:: The default implementation simply calls the static version of this function. - -:return: the converted geometry or ``None`` if no conversion was necessary or possible %End void setNativeTypes( const QList &nativeTypes ); diff --git a/src/core/vector/qgsvectordataprovider.h b/src/core/vector/qgsvectordataprovider.h index 28890386c448..4d6727f74000 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -680,8 +680,8 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat /** * Converts the geometry to the provider type if possible / necessary - * \note The default implementation simply calls the static version of this function. * \returns the converted geometry or NULLPTR if no conversion was necessary or possible + * \note The default implementation simply calls the static version of this function. */ QgsGeometry convertToProviderType( const QgsGeometry &geom ) const; From 43591ef612b2473f42d30c81d2c3affa21d2b21f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 27 Sep 2023 13:16:20 +1000 Subject: [PATCH 147/151] Don't set z range for scenes which are near global - the value won't represent anything --- .../tiledscene/qgscesiumtilesdataprovider.cpp | 38 ++++++++++------ .../src/python/test_qgscesium3dtileslayer.py | 44 +++++++++++++++++++ 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/src/core/tiledscene/qgscesiumtilesdataprovider.cpp b/src/core/tiledscene/qgscesiumtilesdataprovider.cpp index 0a5bd88c9a99..936e7185b0da 100644 --- a/src/core/tiledscene/qgscesiumtilesdataprovider.cpp +++ b/src/core/tiledscene/qgscesiumtilesdataprovider.cpp @@ -733,6 +733,16 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c } } + mIndex = QgsTiledSceneIndex( + new QgsCesiumTiledSceneIndex( + mTileset, + rootUrl, + authCfg, + headers, + transformContext + ) + ); + // parse root { const auto &root = mTileset[ "root" ]; @@ -765,7 +775,11 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c { mBoundingVolume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( rootRegion ) ); - mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + // only set z range for datasets which aren't too large (ie global datasets) + if ( !mIndex.rootTile().boundingVolume().box().isNull() ) + { + mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + } mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) ); mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ); @@ -795,7 +809,11 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext ); ct.setBallparkTransformsAreAppropriate( true ); const QgsBox3D rootRegion = mBoundingVolume.bounds( ct ); - mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + // only set z range for datasets which aren't too large (ie global datasets) + if ( !mIndex.rootTile().boundingVolume().box().isNull() ) + { + mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + } std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) ); mExtent = extent2D->boundingBox(); @@ -830,7 +848,11 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext ); ct.setBallparkTransformsAreAppropriate( true ); const QgsBox3D rootRegion = mBoundingVolume.bounds( ct ); - mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + // only set z range for datasets which aren't too large (ie global datasets) + if ( !mIndex.rootTile().boundingVolume().box().isNull() ) + { + mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); + } std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) ); mExtent = extent2D->boundingBox(); @@ -854,16 +876,6 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c layerExtent.setSpatialExtents( {spatialExtent } ); mLayerMetadata.setExtent( layerExtent ); } - - mIndex = QgsTiledSceneIndex( - new QgsCesiumTiledSceneIndex( - mTileset, - rootUrl, - authCfg, - headers, - transformContext - ) - ); } } diff --git a/tests/src/python/test_qgscesium3dtileslayer.py b/tests/src/python/test_qgscesium3dtileslayer.py index 930f2850cb53..6e47858c762d 100644 --- a/tests/src/python/test_qgscesium3dtileslayer.py +++ b/tests/src/python/test_qgscesium3dtileslayer.py @@ -1613,6 +1613,50 @@ def test_gltf_up_axis(self): root_tile = index.rootTile() self.assertEqual(root_tile.metadata(), {'gltfUpAxis': Qgis.Axis.Z}) + def test_large_dataset(self): + """ + Test a near-global dataset + """ + with tempfile.TemporaryDirectory() as temp_dir: + tmp_file = os.path.join(temp_dir, "tileset.json") + with open(tmp_file, "wt", encoding="utf-8") as f: + f.write( + """{ + "asset": { + "version": "1.0", + "tilesetVersion": "0" + }, + "geometricError": 154134.67955991725, + "root": { + "geometricError": 77067.33977995862, + "boundingVolume": { + "region": [ + -3.1415925942485985, + -1.4599681618940228, + 3.141545370875028, + 1.4403465997204372, + -385.0565011513918, + 5967.300616082603 + ] + }, + "content": null, + "children": [], + "refine": "ADD" + } +}""" + ) + layer = QgsTiledSceneLayer(tmp_file, "my layer", "cesiumtiles") + self.assertTrue(layer.dataProvider().isValid()) + + index = layer.dataProvider().index() + self.assertTrue(index.isValid()) + + root_tile = index.rootTile() + # large (near global) datasets should have no bounding volume + self.assertTrue(root_tile.boundingVolume().box().isNull()) + + self.assertTrue(layer.dataProvider().zRange().isInfinite()) + if __name__ == "__main__": unittest.main() From b5d0a4f40d9e68d8057d4d4dcbdb2a834aa41ee6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 27 Sep 2023 13:22:00 +1000 Subject: [PATCH 148/151] Consider z scale and offset when calculating z range for tiled scene layers --- .../qgstiledscenelayerelevationproperties.cpp | 6 +- .../test_qgstiledsceneelevationproperties.py | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/core/tiledscene/qgstiledscenelayerelevationproperties.cpp b/src/core/tiledscene/qgstiledscenelayerelevationproperties.cpp index 5d202fd8b826..535d8b501bc8 100644 --- a/src/core/tiledscene/qgstiledscenelayerelevationproperties.cpp +++ b/src/core/tiledscene/qgstiledscenelayerelevationproperties.cpp @@ -67,7 +67,11 @@ QgsDoubleRange QgsTiledSceneLayerElevationProperties::calculateZRange( QgsMapLay { if ( QgsTiledSceneDataProvider *dp = tiledSceneLayer->dataProvider() ) { - return dp->zRange(); + const QgsDoubleRange providerRange = dp->zRange(); + if ( providerRange.isInfinite() || providerRange.isEmpty() ) + return QgsDoubleRange(); + + return QgsDoubleRange( dp->zRange().lower() * mZScale + mZOffset, dp->zRange().upper() * mZScale + mZOffset ); } } diff --git a/tests/src/python/test_qgstiledsceneelevationproperties.py b/tests/src/python/test_qgstiledsceneelevationproperties.py index a85b1269f66c..3949e2eea02d 100644 --- a/tests/src/python/test_qgstiledsceneelevationproperties.py +++ b/tests/src/python/test_qgstiledsceneelevationproperties.py @@ -10,6 +10,9 @@ __copyright__ = 'Copyright 2023, The QGIS Project' import qgis # NOQA +import tempfile +import os + from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtTest import QSignalSpy from qgis.PyQt.QtXml import QDomDocument @@ -55,6 +58,59 @@ def testBasic(self): self.assertEqual(props2.zScale(), 2) self.assertEqual(props2.zOffset(), 0.5) + def testCalculateZRange(self): + with tempfile.TemporaryDirectory() as temp_dir: + tmp_file = os.path.join(temp_dir, "tileset.json") + with open(tmp_file, "wt", encoding="utf-8") as f: + f.write( + """ +{ + "asset": { + "version": "1.1", + "tilesetVersion": "e575c6f1" + }, + "geometricError": 100, + "root": { + "boundingVolume": { + "region": [ + -1.3197209591796106, + 0.6988424218, + -1.3196390408203893, + 0.6989055782, + 1.2, + 67.00999999999999 + ] + }, + "geometricError": 100, + "refine": "ADD", + "children": [] + } +}""" + ) + + layer = QgsTiledSceneLayer(tmp_file, "my layer", "cesiumtiles") + self.assertTrue(layer.dataProvider().isValid()) + self.assertEqual(layer.dataProvider().zRange().lower(), 1.2) + self.assertEqual(layer.dataProvider().zRange().upper(), 67.00999999999999) + + props = QgsTiledSceneLayerElevationProperties(layer) + self.assertEqual(props.zScale(), 1.0) + self.assertEqual(props.zOffset(), 0.0) + + z_range = props.calculateZRange(layer) + self.assertEqual(z_range.lower(), 1.2) + self.assertEqual(z_range.upper(), 67.00999999999999) + + props.setZOffset(10) + z_range = props.calculateZRange(layer) + self.assertEqual(z_range.lower(), 11.2) + self.assertEqual(z_range.upper(), 77.00999999999999) + + props.setZScale(2) + z_range = props.calculateZRange(layer) + self.assertEqual(z_range.lower(), 12.4) + self.assertEqual(z_range.upper(), 144.01999999999998) + if __name__ == '__main__': unittest.main() From 6b6102c5d583fe47ae0810c18e9e250ce247ca8e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 27 Sep 2023 13:22:19 +1000 Subject: [PATCH 149/151] Consider tiled scene layers when calculating 3d map scene z ranges --- src/3d/qgs3dmapscene.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 596c0458210b..26d50a6ad0c3 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -1128,11 +1128,21 @@ QgsDoubleRange Qgs3DMapScene::elevationRange() const } break; } + case Qgis::LayerType::TiledScene: + { + QgsTiledSceneLayer *sceneLayer = qobject_cast< QgsTiledSceneLayer *>( layer ); + const QgsDoubleRange zRange = sceneLayer->elevationProperties()->calculateZRange( sceneLayer ); + if ( !zRange.isInfinite() && !zRange.isEmpty() ) + { + yMin = std::min( yMin, zRange.lower() ); + yMax = std::max( yMax, zRange.upper() ); + } + break; + } case Qgis::LayerType::Annotation: case Qgis::LayerType::Group: case Qgis::LayerType::Plugin: case Qgis::LayerType::Raster: - case Qgis::LayerType::TiledScene: case Qgis::LayerType::Vector: case Qgis::LayerType::VectorTile: break; From a0c3d67911743adb43da0102b609ee8bede1e980 Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Fri, 22 Sep 2023 16:56:46 +0200 Subject: [PATCH 150/151] Don't install CMakeLists.txt --- python/plugins/processing/gui/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/gui/CMakeLists.txt b/python/plugins/processing/gui/CMakeLists.txt index 7acffdaf4d4e..88d2bdbfc3a3 100644 --- a/python/plugins/processing/gui/CMakeLists.txt +++ b/python/plugins/processing/gui/CMakeLists.txt @@ -1,4 +1,4 @@ file(GLOB PY_FILES *.py) -file(GLOB OTHER_FILES *.txt) +file(GLOB OTHER_FILES algnames.txt) PLUGIN_INSTALL(processing gui ${PY_FILES} ${OTHER_FILES}) From 982fea528779e98672f024975d26e899a96c205e Mon Sep 17 00:00:00 2001 From: "Juergen E. Fischer" Date: Mon, 2 Oct 2023 11:15:33 +0200 Subject: [PATCH 151/151] debian packaging: update to latest oracle client --- INSTALL.md | 36 +++++++++++++++++++++++++++++++++--- debian/control.in | 4 ++-- debian/rules | 8 ++++---- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 84b6d028aeb3..a421174a0177 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -19,6 +19,7 @@ Building QGIS from source - step by step * [3.8.1. Compiling with 3D on old Debian based distributions](#381-compiling-with-3d-on-old-debian-based-distributions) * [3.9. Building different branches](#39-building-different-branches) * [3.10. Building Debian packages](#310-building-debian-packages) + * [3.10.1. Building packages with oracle support](#3101-building-packages-with-oracle-support) * [3.11. On Fedora Linux](#311-on-fedora-linux) * [3.11.1. Install build dependencies](#3111-install-build-dependencies) * [3.11.2. Suggested system tweaks](#3112-suggested-system-tweaks) @@ -405,20 +406,27 @@ Instead of creating a personal installation as in the previous step you can also create debian package. This is done from the QGIS root directory, where you'll find a debian directory. -First you need to install the [build dependencies](#33-install-build-dependencies) -and setup a changelog entry for your distribution. For example for Debian Bookworm: +First setup a changelog entry for your distribution. For example for Debian Bookworm: ```bash dch -l ~bookworm --force-distribution --distribution bookworm "bookworm build" ``` +You also need to install the [build dependencies](#33-install-build-dependencies). +Alternatively use: + +```bash +debian/rules templates +sudo mk-build-deps -i +``` + The QGIS packages will be created with: ```bash dpkg-buildpackage -us -uc -b ``` -**Note:** Install `devscripts` to get `dch`. +**Note:** Install `devscripts` to get `dch` and `mk-build-deps`. **Note:** If you have `libqgis1-dev` installed, you need to remove it first using `dpkg -r libqgis1-dev`. Otherwise `dpkg-buildpackage` will complain about a @@ -436,6 +444,28 @@ Install them using `dpkg`. E.g.: sudo debi ``` +### 3.10.1. Building packages with Oracle support + +To build packages with Oracle support you need the Oracle libraries (currently +21.11) as additional build dependencies: + +```bash +curl -JLO https://download.oracle.com/otn_software/linux/instantclient/2111000/oracle-instantclient-devel-21.11.0.0.0-1.el8.x86_64.rpm +curl -JLO https://download.oracle.com/otn_software/linux/instantclient/2111000/oracle-instantclient-basiclite-21.11.0.0.0-1.el8.x86_64.rpm +sudo apt install alien +fakeroot alien oracle-instantclient-devel-21.11.0.0.0-1.el8.x86_64.rpm oracle-instantclient-basiclite-21.11.0.0.0-1.el8.x86_64.rpm +sudo dpkg -i oracle-instantclient-devel_21.11.0.0.0-2_amd64.deb oracle-instantclient-basiclite_21.11.0.0.0-2_amd64.deb +``` + +(if the client version changes it's necessary to adapt `ORACLE_INCLUDEDIR` and `ORACLE_LIBDIR` accordingly) + +The packaging files enable Oracle support if the distribution contains "-oracle": + +```bash +dch -l ~sid~oracle --force-distribution --distribution sid-oracle "sid build with oracle" │ +dpkg-buildpackage -us -uc -b +``` + ## 3.11. On Fedora Linux We assume that you have the source code of QGIS ready and created a diff --git a/debian/control.in b/debian/control.in index 708c83ea3477..81aebbcf07ad 100644 --- a/debian/control.in +++ b/debian/control.in @@ -40,8 +40,8 @@ Build-Depends: ninja-build, ocl-icd-opencl-dev, opencl-headers, -#oracle# oracle-instantclient12.1-basiclite, -#oracle# oracle-instantclient12.1-devel, +#oracle# oracle-instantclient-basiclite, +#oracle# oracle-instantclient-devel, pkg-config, pyqt5-dev-tools, pyqt5-dev, diff --git a/debian/rules b/debian/rules index 8d598fe73b25..8ac1354dff23 100755 --- a/debian/rules +++ b/debian/rules @@ -136,12 +136,12 @@ endif ifneq (,$(WITH_ORACLE)) ifeq ($(DEB_BUILD_ARCH),amd64) - ORACLE_INCLUDEDIR=/usr/include/oracle/18.5/client64/ - ORACLE_LIBDIR=/usr/lib/oracle/18.5/client64/lib/ + ORACLE_INCLUDEDIR=/usr/include/oracle/21/client64/ + ORACLE_LIBDIR=/usr/lib/oracle/21/client64/lib/ endif ifeq ($(DEB_BUILD_ARCH),i386) - ORACLE_INCLUDEDIR=/usr/include/oracle/18.5/client/ - ORACLE_LIBDIR=/usr/lib/oracle/18.5/client/lib/ + ORACLE_INCLUDEDIR=/usr/include/oracle/21/client/ + ORACLE_LIBDIR=/usr/lib/oracle/21/client/lib/ endif CMAKE_OPTS += \ -DWITH_ORACLE=TRUE \