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/CMakeLists.txt b/CMakeLists.txt index 359434bf5a3d..efedfa7db5db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,10 +358,10 @@ if(WITH_CORE) endif() # required - find_package(Proj) - find_package(GEOS) - find_package(GDAL) - find_package(Expat REQUIRED) + find_package(Proj REQUIRED) + find_package(GEOS REQUIRED) + find_package(GDAL REQUIRED) + find_package(EXPAT REQUIRED) find_package(Spatialindex REQUIRED) find_package(LibZip REQUIRED) @@ -403,10 +403,6 @@ 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}") - endif() - if (POSTGRES_FOUND) # following variable is used in qgsconfig.h set (HAVE_POSTGRESQL TRUE) diff --git a/INSTALL.md b/INSTALL.md index 4084c32c965a..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 @@ -754,10 +784,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: 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/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/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/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/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/cmake/FindSpatiaLite.cmake b/cmake/FindSpatiaLite.cmake index 86d03e190a46..74f46e3dde91 100644 --- a/cmake/FindSpatiaLite.cmake +++ b/cmake/FindSpatiaLite.cmake @@ -7,73 +7,89 @@ # 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 - -# 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) - - 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) +# and adds the following target +# +# spatialite::spatialite -ELSE (SPATIALITE_FOUND) +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_search_module(PC_SPATIALITE IMPORTED_TARGET spatialite) +endif() - IF (SPATIALITE_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find SpatiaLite. Include: ${SPATIALITE_INCLUDE_DIR} Library: ${SPATIALITE_LIBRARY}") - ENDIF (SPATIALITE_FIND_REQUIRED) +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 -ENDIF (SPATIALITE_FOUND) + # 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/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 \ 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/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, 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/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 \ 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 \ diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index c3e1da710db2..531d0679370f 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 @@ -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/python/CMakeLists.txt b/python/CMakeLists.txt index d9547e05c557..443daec68244 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -73,8 +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} ${QTKEYCHAIN_INCLUDE_DIR} diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 30ffa5bd5ef5..611b4bbfd8a1 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 @@ -3759,7 +3762,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 (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 QgsVectorFileWriter.SymbologyExport = Qgis.FeatureSymbologyExport diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 0fb7b60e15dc..3a53b66397d2 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,25 @@ 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 ); + +%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/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/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/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index ace323980b68..838e4e3aadce 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 @@ -2162,6 +2163,7 @@ The development version { Qgis, PointCloud, + RasterCalculator, }; enum class FeatureSymbologyExport diff --git a/python/core/auto_generated/qgsdatasourceuri.sip.in b/python/core/auto_generated/qgsdatasourceuri.sip.in index 7b4a9a199417..e0e6387b2716 100644 --- a/python/core/auto_generated/qgsdatasourceuri.sip.in +++ b/python/core/auto_generated/qgsdatasourceuri.sip.in @@ -167,9 +167,14 @@ 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. + +: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/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/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/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/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index c0f06e3156bb..733976f4aeae 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -704,6 +704,10 @@ For general debug information use :py:func:`QgsMessageLog.logMessage()` instead. 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. %End void setNativeTypes( const QList &nativeTypes ); @@ -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/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/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 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/RasterCalculator.py b/python/plugins/processing/algs/qgis/RasterCalculator.py index 9367b263fa9c..71f8c4172a0c 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 + def name(self): return 'rastercalculator' 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/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}) 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() 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/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:* diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 5ff5043fe7cb..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 ); } @@ -286,18 +283,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 ); } @@ -745,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/mesh/qgsmesh3dentity_p.cpp b/src/3d/mesh/qgsmesh3dentity_p.cpp index d617952d58aa..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 ); } @@ -102,4 +102,3 @@ void QgsMesh3DTerrainTileEntity::applyMaterial() QgsMesh3DMaterial::ZValue ); addComponent( material ); } - diff --git a/src/3d/mesh/qgsmesh3dgeometry_p.cpp b/src/3d/mesh/qgsmesh3dgeometry_p.cpp index 68fcbaff3b80..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, - double verticalSacle, + const QgsRectangle &extent, + double verticalScale, Qt3DCore::QNode *parent ) - : QgsMesh3DGeometry( triangularMesh, origin, verticalSacle, 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 835dcb83f037..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,7 +260,8 @@ class QgsMeshTerrain3DGeometry: public QgsMesh3DGeometry //! Constructs a mesh layer geometry from triangular mesh. explicit QgsMeshTerrain3DGeometry( const QgsTriangularMesh &triangularMesh, const QgsVector3D &origin, - double verticalSacle, + const QgsRectangle &extent, + double verticalScale, QNode *parent ); }; diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 8ad686f25f50..26d50a6ad0c3 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 ); @@ -367,6 +371,8 @@ void Qgs3DMapScene::updateScene() for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) { entity->handleSceneUpdate( sceneState_( mEngine ) ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); } updateSceneState(); @@ -404,19 +410,21 @@ 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 ); - + // 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 + 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; @@ -440,6 +448,8 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) { QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 ); entity->handleSceneUpdate( sceneState_( mEngine ) ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); } } @@ -761,7 +771,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] { @@ -771,7 +782,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] { @@ -784,7 +796,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 ) ) @@ -804,7 +817,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 ) { @@ -1063,7 +1077,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; @@ -1088,12 +1103,49 @@ 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::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::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..332875258f1d 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 */ @@ -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/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/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 6cdd41e14518..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, @@ -846,3 +871,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..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 * @@ -274,6 +281,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/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 ); } 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 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 ); } } 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/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 47c8cfdfed62..c88f67c86049 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 @@ -168,6 +169,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 @@ -237,6 +239,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 @@ -458,16 +461,11 @@ 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) 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 +575,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/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/qgsalgorithmpointstopaths.cpp b/src/analysis/processing/qgsalgorithmpointstopaths.cpp index b99f95fe7c2b..66b43c8d2023 100644 --- a/src/analysis/processing/qgsalgorithmpointstopaths.cpp +++ b/src/analysis/processing/qgsalgorithmpointstopaths.cpp @@ -249,14 +249,18 @@ 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 ); + if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( *pit ) ) + { + allPoints[ groupValue ] << qMakePair( orderValue, *point ); + } } } else { - const QgsPoint point( *qgsgeometry_cast< const QgsPoint * >( geom ) ); - allPoints[ groupValue ] << qMakePair( orderValue, point ); + if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( geom ) ) + { + allPoints[ groupValue ] << qMakePair( orderValue, *point ); + } } } ++currentPoint; diff --git a/src/analysis/processing/qgsalgorithmrastercalculator.cpp b/src/analysis/processing/qgsalgorithmrastercalculator.cpp new file mode 100644 index 000000000000..fe5dc79a2085 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmrastercalculator.cpp @@ -0,0 +1,362 @@ +/*************************************************************************** + 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" +#include "qgsrastercalculator.h" + +///@cond PRIVATE + +QgsProcessingAlgorithm::Flags QgsRasterCalculatorAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagHideFromModeler; +} + +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( "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() ); + 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" ) ) ); +} + +bool QgsRasterCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context ); + + for ( const QgsMapLayer *layer : std::as_const( layers ) ) + { + + QgsMapLayer *clonedLayer { layer->clone() }; + clonedLayer->moveToThread( nullptr ); + 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 ) +{ + 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; + for ( QgsMapLayer *layer : mLayers ) + { + QgsRasterLayer *rLayer = qobject_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() != 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; +} + +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 = qobject_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 new file mode 100644 index 000000000000..1521e17cba42 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmrastercalculator.h @@ -0,0 +1,90 @@ +/*************************************************************************** + 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" ) ); } + 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; + QgsRasterCalculatorAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + 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/qgsalgorithmsplitvectorlayer.cpp b/src/analysis/processing/qgsalgorithmsplitvectorlayer.cpp index ab11f535ba91..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 @@ -81,7 +82,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 +140,13 @@ QVariantMap QgsSplitVectorLayerAlgorithm::processAlgorithm( const QVariantMap &p } else { - fileName = QStringLiteral( "%1%2.%3" ).arg( baseName ).arg( ( *it ).toString() ).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 ) ); 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 ) ) { diff --git a/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp new file mode 100644 index 000000000000..72723ded24d3 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.cpp @@ -0,0 +1,338 @@ +/*************************************************************************** + 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 | FlagHideFromModeler; +} + +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( "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() ); + 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" ) ) ); +} + +QVariantMap QgsVirtualRasterCalculatorAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_UNUSED( feedback ); + + const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), 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 = qobject_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() != 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; +} + +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 = qobject_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 new file mode 100644 index 000000000000..a73b5347e0d8 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmvirtualrastercalculator.h @@ -0,0 +1,87 @@ +/*************************************************************************** + 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; + + 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/qgsalgorithmxyztiles.cpp b/src/analysis/processing/qgsalgorithmxyztiles.cpp index ebf7c07e05d1..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 ); } @@ -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) diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 6e64d82366b2..f2f2dc4c6de3 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" @@ -152,6 +153,7 @@ #include "qgsalgorithmrandompointsinpolygons.h" #include "qgsalgorithmrandompointsonlines.h" #include "qgsalgorithmrandomraster.h" +#include "qgsalgorithmrastercalculator.h" #include "qgsalgorithmrasterdtmslopebasedfilter.h" #include "qgsalgorithmrasterfrequencybycomparisonoperator.h" #include "qgsalgorithmrasterlayerproperties.h" @@ -221,6 +223,7 @@ #include "qgsalgorithmunion.h" #include "qgsalgorithmuniquevalueindex.h" #include "qgsalgorithmvectorize.h" +#include "qgsalgorithmvirtualrastercalculator.h" #include "qgsalgorithmwedgebuffers.h" #include "qgsalgorithmwritevectortiles.h" #include "qgsalgorithmxyztiles.h" @@ -377,6 +380,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() ); @@ -440,6 +444,8 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsRandomPointsOnLinesAlgorithm() ); addAlgorithm( new QgsRandomPoissonRasterAlgorithm() ); addAlgorithm( new QgsRandomUniformRasterAlgorithm() ); + addAlgorithm( new QgsRasterCalculatorAlgorithm() ); + addAlgorithm( new QgsRasterCalculatorModelerAlgorithm() ); addAlgorithm( new QgsRasterDtmSlopeBasedFilterAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByEqualOperatorAlgorithm() ); addAlgorithm( new QgsRasterFrequencyByGreaterThanOperatorAlgorithm() ); @@ -521,6 +527,8 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsTruncateTableAlgorithm() ); 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/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..0582c16aba0a 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -55,6 +55,9 @@ 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() ); } void Qgs3DOptionsWidget::apply() @@ -65,6 +68,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/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/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 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; } 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..66bdaea0fed3 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 ); @@ -4471,7 +4472,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, true ); + } + mProjectLoadingProxyTask = new QgsProxyProgressTask( tr( "Loading “%1”" ).arg( name ) ); QgsApplication::taskManager()->addTask( mProjectLoadingProxyTask ); } @@ -10221,7 +10227,7 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer ) } if ( avoidIntersectionsLayers.size() > 0 ) { - geom.avoidIntersections( avoidIntersectionsLayers ); + geom.avoidIntersectionsV2( avoidIntersectionsLayers ); } // count collapsed geometries @@ -10612,6 +10618,7 @@ void QgisApp::pasteStyle( QgsMapLayer *destinationLayer, QgsMapLayer::StyleCateg mLayerTreeView->refreshLayerSymbology( selectionLayer->id() ); selectionLayer->triggerRepaint(); + QgsProject::instance()->setDirty( true ); } } } 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/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/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, [ = ] diff --git a/src/app/qgsrecentprojectsitemsmodel.cpp b/src/app/qgsrecentprojectsitemsmodel.cpp index c6ca699ac0cb..d5ca5596c776 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,18 +89,25 @@ 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(); } } - 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 ); @@ -112,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 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 ) diff --git a/src/app/vertextool/qgsvertextool.cpp b/src/app/vertextool/qgsvertextool.cpp index 0dbc33b57b70..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 ) @@ -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 ) { @@ -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/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/CMakeLists.txt b/src/core/CMakeLists.txt index db1c050f154b..869428249e98 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2247,12 +2247,8 @@ 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 - ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} @@ -2262,12 +2258,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} @@ -2423,16 +2413,16 @@ target_link_libraries(qgis_core ${OPTIONAL_QTWEBKIT} ${QCA_LIBRARY} ${QTKEYCHAIN_LIBRARY} - ${PROJ_LIBRARY} - ${GEOS_LIBRARY} - ${GDAL_LIBRARY} + GEOS::geos_c + GDAL::GDAL ${SPATIALINDEX_LIBRARY} - ${EXPAT_LIBRARY} + EXPAT::EXPAT ${SQLITE3_LIBRARY} ${LIBZIP_LIBRARY} ${Protobuf_LITE_LIBRARY} ${ZLIB_LIBRARIES} ${EXIV2_LIBRARY} + PROJ::proj ) if (WITH_DRACO) @@ -2444,7 +2434,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/core/elevation/qgsabstractprofilesurfacegenerator.cpp b/src/core/elevation/qgsabstractprofilesurfacegenerator.cpp index f9b35b9c7170..adf5ddeb1594 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(); } 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/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 153af6fbb07b..9c1dbed1f9e3 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,46 @@ 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: + + // these should never happen + 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: + return 4; + } + 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..3ce2ddad1ede 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -2550,9 +2550,24 @@ 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 */ - 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 >() ) ) SIP_DEPRECATED; + + /** + * 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/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index 015c8d75e330..6b33ef38300b 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() ) ); } @@ -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 ( !qgsDoubleNear( intersectionPoint->x(), segmentPoint2D.x() ) || !qgsDoubleNear( 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/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 ); } 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/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 ) { 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; diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index ca42c0157bc8..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; } @@ -5878,6 +5883,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 +5936,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 +6054,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/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" ) ) diff --git a/src/core/proj/qgscoordinatetransform.cpp b/src/core/proj/qgscoordinatetransform.cpp index 9c998ee5f6e7..2bca58ad5798 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 ) { - 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 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/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index d2cac79987c4..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 ); } @@ -2235,7 +2244,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/src/core/providers/ogr/qgsogrproviderutils.cpp b/src/core/providers/ogr/qgsogrproviderutils.cpp index 7b1c515a4643..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 ); @@ -1553,7 +1554,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 +2297,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() ) 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; diff --git a/src/core/qgis.h b/src/core/qgis.h index b48b63e24c37..592a9b29f4bb 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 ) @@ -3765,6 +3766,7 @@ class CORE_EXPORT Qgis { Qgis, //!< Native QGIS expression PointCloud, //!< Point cloud expression + RasterCalculator, //!< Raster calculator expression (since QGIS 3.34) }; Q_ENUM( ExpressionType ) diff --git a/src/core/qgsdatasourceuri.cpp b/src/core/qgsdatasourceuri.cpp index 35ee70408dfc..bc80a4f2cf29 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,75 @@ 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:" ) ) ) + { + // postgresql://user:pwd@... + regexp.setPattern( QStringLiteral( "/.*@" ) ); + const QString matched = regexp.match( aUri ).captured(); + + QString anonymised = matched; + 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( "@" ) ); + } + + safeName.replace( regexp, anonymised ); } else if ( aUri.contains( QLatin1String( "SDE:" ) ) ) { diff --git a/src/core/qgsdatasourceuri.h b/src/core/qgsdatasourceuri.h index b973f7e03ed5..a67c215b7707 100644 --- a/src/core/qgsdatasourceuri.h +++ b/src/core/qgsdatasourceuri.h @@ -187,8 +187,13 @@ 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 ); + static QString removePassword( const QString &aUri, bool hide = false ); /** * Returns any associated authentication configuration ID stored in the URI. 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 ); 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/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/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/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/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/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/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/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/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..4d6727f74000 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -681,6 +681,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat /** * Converts the geometry to the provider type if possible / necessary * \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; @@ -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/core/vector/qgsvectorlayertools.cpp b/src/core/vector/qgsvectorlayertools.cpp index 99aba7f309b1..96637c6aba71 100644 --- a/src/core/vector/qgsvectorlayertools.cpp +++ b/src/core/vector/qgsvectorlayertools.cpp @@ -48,10 +48,26 @@ 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 ) { 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 ) @@ -69,34 +85,28 @@ bool QgsVectorLayerTools::copyMoveFeatures( QgsVectorLayer *layer, QgsFeatureReq else { newFeature = QgsVectorLayerUtils::createFeature( layer, f.geometry(), f.attributes().toMap() ); - } - - // 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 ) ); + QgsDebugError( QStringLiteral( "Could not add new feature. Original copied feature id: %1" ).arg( f.id() ) ); } else { fidList.insert( newFeature.id() ); - if ( topologicalEditing ) + } + } + + // translate + if ( newFeature.hasGeometry() ) + { + QgsGeometry geom = newFeature.geometry(); + if ( topologicalEditing ) + { + if ( topologicalLayer ) { - if ( topologicalLayer ) - { - topologicalLayer->addTopologicalPoints( geom ); - } - layer->addTopologicalPoints( geom ); + topologicalLayer->addTopologicalPoints( geom ); } + layer->addTopologicalPoints( geom ); } } else diff --git a/src/crssync/CMakeLists.txt b/src/crssync/CMakeLists.txt index 94d2fc9d7c4a..278272161a98 100644 --- a/src/crssync/CMakeLists.txt +++ b/src/crssync/CMakeLists.txt @@ -10,8 +10,6 @@ else () target_link_libraries(crssync qgis_core - ${PROJ_LIBRARY} - ${GDAL_LIBRARY} ) if(MSVC AND NOT USING_NMAKE) 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/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 { diff --git a/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp b/src/gui/maptools/qgsmaptoolcapturelayergeometry.cpp index c32230b9fc63..7fd9f7f0ca1a 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; @@ -57,8 +58,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 ); } diff --git a/src/gui/ogr/qgsvectorlayersaveasdialog.cpp b/src/gui/ogr/qgsvectorlayersaveasdialog.cpp index aac62b2c08f0..d2c58cbf67db 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() ) ); @@ -431,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() ); @@ -681,7 +691,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 ); + } } } @@ -948,7 +969,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; } } @@ -1007,7 +1029,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; } } @@ -1064,12 +1087,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 ) 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, diff --git a/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp new file mode 100644 index 000000000000..b4ae5f88f642 --- /dev/null +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.cpp @@ -0,0 +1,322 @@ +/*************************************************************************** + 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 +#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( const QVariantList &layers ) +{ + mLayers = layers; +} + +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( const QVariantList &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( 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 QVariant &layer : mLayers ) + { + QListWidgetItem *item = new QListWidgetItem( layer.toString(), mLayersList ); + mLayersList->addItem( item ); + } +} + +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( QStringLiteral( "%1@1" ).arg( item->text() ) ) ); +} + +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..cdd250b8e272 --- /dev/null +++ b/src/gui/processing/qgsprocessingrastercalculatorexpressionlineedit.h @@ -0,0 +1,173 @@ +/*************************************************************************** + 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( const QVariantList &layers ); + + /** + * 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; + QVariantList 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( const QVariantList &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 ); + + //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: + //! Populate the layer list + void populateLayers(); + + static QString quoteBandEntry( const QString &layerName ); + + QVariantList mLayers; + const QString mInitialText; +}; + +///@endcond +#endif // QGSPROCESSINGRASTERCALCULATOREXPRESSIONLINEEDIT_H diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 09342263b71f..395fa9a8a16f 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,21 @@ QWidget *QgsProcessingExpressionWidgetWrapper::createWidget() return mPointCloudExpLineEdit; } + if ( expParam->expressionType() == Qgis::ExpressionType::RasterCalculator ) + { + 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 ); + } ); + return mRasterCalculatorExpLineEdit; + } + // native QGIS expression if ( expParam->metadata().value( QStringLiteral( "inlineEditor" ) ).toBool() ) { @@ -2428,6 +2474,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 +2514,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 +2528,8 @@ QVariant QgsProcessingExpressionWidgetWrapper::widgetValue() const return mExpLineEdit->expression(); else if ( mPointCloudExpLineEdit ) return mPointCloudExpLineEdit->expression(); + else if ( mRasterCalculatorExpLineEdit ) + return mRasterCalculatorExpLineEdit->expression(); else return QVariant(); } @@ -4412,6 +4486,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 +4574,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 +4876,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/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; 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 ); 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 b79767ee55fd..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 @@ -444,6 +444,7 @@ void QgsExpressionBuilderWidget::btnNewFile_pressed() if ( ok && !text.isEmpty() ) { newFunctionFile( text ); + btnRemoveFile->setEnabled( cmbFileNames->count() > 0 ); } } 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 2fe98377a0f8..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 Dialog" ) ) + , 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 d8b90ad39c89..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 Dialog" ) ) + , mExpressionDialogTitle( tr( "Expression Builder" ) ) , mDistanceArea( nullptr ) { 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; } 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; diff --git a/src/gui/qgsmaptip.cpp b/src/gui/qgsmaptip.cpp index 00bd2fac0ec1..21f500ab52d6 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 @@ -169,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 ); 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 ); 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/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/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 ); } 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/vector/qgsvectorlayerproperties.cpp b/src/gui/vector/qgsvectorlayerproperties.cpp index 392d713fbed9..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 @@ -120,6 +121,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 +557,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 +941,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 +970,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() @@ -1275,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++; } @@ -1321,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; 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; 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; 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/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] ) { diff --git a/src/plugins/grass/CMakeLists.txt b/src/plugins/grass/CMakeLists.txt index a19a21c87919..8bfcccd01f78 100644 --- a/src/plugins/grass/CMakeLists.txt +++ b/src/plugins/grass/CMakeLists.txt @@ -81,9 +81,6 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${POSTGRES_INCLUDE_DIR} ) 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/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/process/CMakeLists.txt b/src/process/CMakeLists.txt index c4691d843172..c1947294c69c 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -42,9 +42,7 @@ target_link_libraries(qgis_process qgis_core qgis_analysis ${QT_VERSION_BASE}::Core - ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} ) if (WITH_3D) diff --git a/src/process/main.cpp b/src/process/main.cpp index 8dafa298564c..00eec36299b5 100644 --- a/src/process/main.cpp +++ b/src/process/main.cpp @@ -77,7 +77,83 @@ 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/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" ) ); + + // 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 initialize + // QgsApplication at all! + QgsProcessingExec::showUsage( args.at( 0 ) ); + return 0; + } + QString myPrefixPath; if ( myPrefixPath.isEmpty() ) { @@ -99,15 +175,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..61283b62649f 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" ) ) ) @@ -325,16 +297,6 @@ int QgsProcessingExec::run( const QStringList &constArgs ) listAlgorithms( useJson ); return 0; } - else if ( command == QLatin1String( "--version" ) || command == QLatin1String( "-v" ) ) - { - 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 ) @@ -561,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 127877baf7e1..3146fa152cce 100644 --- a/src/process/qgsprocess.h +++ b/src/process/qgsprocess.h @@ -69,11 +69,12 @@ 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 ); + static void showVersionInformation(); private: - void showUsage( const QString &appName ); void loadPlugins(); void listAlgorithms( bool useJson ); void listPlugins( bool useJson, bool showLoaded ); 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() ) 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/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/src/providers/grass/CMakeLists.txt b/src/providers/grass/CMakeLists.txt index d4902fcf0a80..943a95958cf0 100644 --- a/src/providers/grass/CMakeLists.txt +++ b/src/providers/grass/CMakeLists.txt @@ -8,9 +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 +125,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 +137,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 +213,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 +233,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 +241,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 +260,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 +269,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 +288,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/hana/CMakeLists.txt b/src/providers/hana/CMakeLists.txt index 9295c55fd538..898386b6eac1 100644 --- a/src/providers/hana/CMakeLists.txt +++ b/src/providers/hana/CMakeLists.txt @@ -67,8 +67,6 @@ include_directories( ) include_directories (SYSTEM - ${PROJ_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} ${ODBC_INCLUDE_DIRS} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} 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/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/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/postgres/qgspgnewconnection.cpp b/src/providers/postgres/qgspgnewconnection.cpp index 3d906b001455..76fa0600721f 100644 --- a/src/providers/postgres/qgspgnewconnection.cpp +++ b/src/providers/postgres/qgspgnewconnection.cpp @@ -163,20 +163,10 @@ 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() ); - 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" ); @@ -192,7 +182,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/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index cd3f0fa0481a..030b9a114f53 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" ) ) @@ -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(); @@ -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/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/" ); 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/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index e01cc913c86c..126a145523c1 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -128,6 +128,7 @@ bool QgsSpatiaLiteProvider::convertField( QgsField &field ) } + Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString &uri, const QgsFields &fields, Qgis::WkbType wkbType, @@ -4230,7 +4231,8 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { unsigned char *wkb = nullptr; int wkb_size; - QByteArray featureWkb = feature->geometry().asWkb(); + 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(), &wkb, &wkb_size, nDims ); @@ -4840,7 +4842,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/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/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 ) ); } 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/providers/wfs/oapif/qgsoapifprovider.cpp b/src/providers/wfs/oapif/qgsoapifprovider.cpp index 070458b7802a..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 @@ -267,6 +275,29 @@ 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 ); + 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(); + } + } + } + } mShared->mFields = itemsRequest.fields(); @@ -336,12 +367,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/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> 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/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/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/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/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 + + 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 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 + + + + + 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 + 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. ° 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 + + + + + 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 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 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 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/3d/sandbox/CMakeLists.txt b/tests/src/3d/sandbox/CMakeLists.txt index 72afff6cdb5b..b12558e898db 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} ) @@ -35,9 +34,7 @@ target_link_libraries(qgis_3d_sandbox ${QT_VERSION_BASE}::Svg ${QT_VERSION_BASE}::Network ${QT_VERSION_BASE}::Test - ${PROJ_LIBRARY} ${GEOS_LIBRARY} - ${GDAL_LIBRARY} ${QWT_LIBRARY} qgis_core qgis_3d diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 947f4dad0cdb..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() { // ============================================= @@ -1750,7 +1796,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 +1849,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 ); diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 21a470ffc2b6..991d67d4c6e9 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() @@ -6861,6 +6866,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 ) ); @@ -11648,10 +11668,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 ); 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/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" 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" 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/core/testqgsdatasourceuri.cpp b/tests/src/core/testqgsdatasourceuri.cpp index 1ce49617da65..5bd578ca43d3 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,17 @@ void TestQgsDataSourceUri::checkParameterKeys() QVERIFY( uri.parameterKeys().contains( QLatin1String( "bar" ) ) ); } +void TestQgsDataSourceUri::checkRemovePassword() +{ + 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 ) #include "testqgsdatasourceuri.moc" 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 ); } diff --git a/tests/src/providers/grass/CMakeLists.txt b/tests/src/providers/grass/CMakeLists.txt index 7546f3c8c428..95110d5daeaf 100644 --- a/tests/src/providers/grass/CMakeLists.txt +++ b/tests/src/providers/grass/CMakeLists.txt @@ -1,13 +1,8 @@ include_directories( ${CMAKE_SOURCE_DIR}/src/providers/grass ) -include_directories(BEFORE SYSTEM - ${PROJ_INCLUDE_DIR} - ${GDAL_INCLUDE_DIR} -) include_directories(SYSTEM ${POSTGRES_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ) @@ -29,9 +24,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} - ${GDAL_LIBRARY} qgis_core qgis_test qgisgrass${grass_build_version} 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() 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/src/python/test_provider_oapif.py b/tests/src/python/test_provider_oapif.py index a70a752060f9..37edfcf68af7 100644 --- a/tests/src/python/test_provider_oapif.py +++ b/tests/src/python/test_provider_oapif.py @@ -16,7 +16,9 @@ import shutil import tempfile -from qgis.PyQt.QtCore import QCoreApplication, QDateTime, Qt +from osgeo import gdal + +from qgis.PyQt.QtCore import QCoreApplication, QDateTime, Qt, QVariant from qgis.PyQt.QtTest import QSignalSpy from qgis.core import ( QgsApplication, @@ -141,6 +143,8 @@ def add_params(x, y): } } } + if bbox is None: + del collection["extent"] if storageCrs: collection["storageCrs"] = storageCrs if crsList: @@ -1266,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: @@ -1302,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): @@ -1644,6 +1672,50 @@ def testFeatureAttributeChangePatch(self): values = [f['cnt'] for f in vl.getFeatures()] self.assertEqual(values, [200]) + # GDAL 3.5.0 is required since it is the first version that tags "complex" + # fields as OFSTJSON + @unittest.skipIf(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(3, 5, 0), "GDAL 3.5.0 required") + def testFeatureComplexAttribute(self): + + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_testFeatureComplexAttribute' + create_landing_page_api_collection(endpoint) + + # first items + first_items = { + "type": "FeatureCollection", + "features": [ + {"type": "Feature", "id": "feat.1", "properties": {"center": { + "type": "Point", + "coordinates": [ + 6.50, + 51.80 + ] + }}, + "geometry": {"type": "Point", "coordinates": [66.33, -70.332]}} + ] + } + with open(sanitize(endpoint, '/collections/mycollection/items?limit=10&' + ACCEPT_ITEMS), 'wb') as f: + f.write(json.dumps(first_items).encode('UTF-8')) + + # real page + with open(sanitize(endpoint, '/collections/mycollection/items?limit=1000&' + ACCEPT_ITEMS), 'wb') as f: + f.write(json.dumps(first_items).encode('UTF-8')) + + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='mycollection' restrictToRequestBBOX=1", 'test', + 'OAPIF') + self.assertTrue(vl.isValid()) + + self.assertEqual(vl.fields().field("center").type(), QVariant.Map) + + # First time we getFeatures(): comes directly from the GeoJSON layer + values = [f["center"] for f in vl.getFeatures()] + self.assertEqual(values, [{'coordinates': [6.5, 51.8], 'type': 'Point'}]) + + # Now, that comes from the Spatialite cache, through + # serialization and deserialization + values = [f["center"] for f in vl.getFeatures()] + self.assertEqual(values, [{'coordinates': [6.5, 51.8], 'type': 'Point'}]) + if __name__ == '__main__': unittest.main() diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index 7d374c9955b9..f3ae9da33dbf 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') @@ -2760,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() 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): 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() diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index 8be1afc209c1..3d07ef3d8cbb 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,45 @@ 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() + + 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()) + 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])) + + _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__': unittest.main() 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() diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index dd11f0fb60b5..6f154f6e80cd 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, parts, _ = multilinestring.splitGeometry(blade, False, False, False) + self.assertEqual(result, Qgis.GeometryOperationResult.Success) + 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__': unittest.main() 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/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() 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) 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() 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() 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/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() 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) diff --git a/tests/src/quickgui/CMakeLists.txt b/tests/src/quickgui/CMakeLists.txt index 2e27caff95d8..5b7a2cb651cb 100644 --- a/tests/src/quickgui/CMakeLists.txt +++ b/tests/src/quickgui/CMakeLists.txt @@ -16,14 +16,8 @@ include_directories( ) include_directories(SYSTEM - ${PROJ_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${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..d61939d5a48c 100644 --- a/tests/src/quickgui/app/CMakeLists.txt +++ b/tests/src/quickgui/app/CMakeLists.txt @@ -21,14 +21,8 @@ include_directories( ) include_directories(SYSTEM - ${GDAL_INCLUDE_DIR} - ${PROJ_INCLUDE_DIR} ${LIBZIP_INCLUDE_DIRS} ${SPATIALINDEX_INCLUDE_DIR} - ${GEOS_INCLUDE_DIR} - ${EXPAT_INCLUDE_DIR} - ${SQLITE3_INCLUDE_DIR} - ${SPATIALITE_INCLUDE_DIR} ${QCA_INCLUDE_DIR} ${QTKEYCHAIN_INCLUDE_DIR} ) 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 000000000000..1722d09e4999 Binary files /dev/null and b/tests/testdata/control_images/3d/expected_mesh3d_filtered/expected_mesh3d_filtered.png differ 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 000000000000..f91ce4e23279 Binary files /dev/null and b/tests/testdata/control_images/composer_legend/expected_atlas_legend_clipping/expected_atlas_legend_clipping.png differ 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 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 + + + + + + + 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 + + + + +