diff --git a/.github/actions/vcpkg_update_report/action.yml b/.github/actions/vcpkg_update_report/action.yml new file mode 100644 index 000000000000..b89672d48ede --- /dev/null +++ b/.github/actions/vcpkg_update_report/action.yml @@ -0,0 +1,49 @@ +name: Compare vcpkg install changes +description: Compares vcpkg install outputs between the base and head refs on pull requests and generates a report. + +inputs: + vcpkg-manifest-dir: + description: 'Directory containing the vcpkg.json manifest' + required: true + default: '.' + type: string + triplet: + description: 'Triplet to use for vcpkg installation' + required: true + default: 'x64-linux' + type: string + +outputs: + report: + description: 'The report of added and removed packages after vcpkg installation comparison' + value: ${{ steps.compare.outputs.report }} + +runs: + using: "composite" + steps: + # Run vcpkg install --dry-run on the head ref + - name: Run vcpkg install (HEAD) + shell: bash + run: | + vcpkg install --dry-run --triplet ${{ inputs.triplet }} --x-manifest-root=${{ inputs.vcpkg-manifest-dir }} > /tmp/vcpkg-head-output.txt + + # Run vcpkg install --dry-run on the base ref + - name: Run vcpkg install (BASE) + shell: bash + run: | + git worktree add .base-ref ${{ github.event.pull_request.base.sha }} + vcpkg install --dry-run --triplet ${{ inputs.triplet }} --x-manifest-root=.base-ref/${{ inputs.vcpkg-manifest-dir }} > /tmp/vcpkg-base-output.txt + + + # Compare the outputs and generate a report + - name: Compare vcpkg outputs + shell: bash + id: compare + run: | + python3 ${GITHUB_ACTION_PATH}/vcpkg-diff.py > /tmp/vcpkg-report.txt + cat /tmp/vcpkg-report.txt + { + echo 'report<> "$GITHUB_OUTPUT" diff --git a/.github/actions/vcpkg_update_report/vcpkg-diff.py b/.github/actions/vcpkg_update_report/vcpkg-diff.py new file mode 100644 index 000000000000..bc895b038b6c --- /dev/null +++ b/.github/actions/vcpkg_update_report/vcpkg-diff.py @@ -0,0 +1,101 @@ +import re + + +def extract_packages(data): + """ + Extract package name, triplet, version, and features information from the file content. + """ + packages = {} + lines = data.strip().split("\n") + for line in lines: + # Regex to match the package format and capture features inside brackets + match = re.match( + r"\s*\*\s+([^\[\]:]+)(?:\[(.*?)\])?:([^\[\]@]+)@([^\s]+)\s+--", line + ) + if match: + package_name = match.group(1) + features = match.group(2) if match.group(2) else "" + triplet = match.group(3) + version = match.group(4) + features_list = ( + [feature.strip() for feature in features.split(",")] if features else [] + ) + packages[package_name] = (triplet, version, features_list) + return packages + + +def compare_features(features1, features2): + """ + Compare two feature lists and return the differences. + """ + added_features = set(features2) - set(features1) + removed_features = set(features1) - set(features2) + return added_features, removed_features + + +def generate_report(file1_content, file2_content): + # Extract package information from both files + file1_packages = extract_packages(file1_content) + file2_packages = extract_packages(file2_content) + + added = [] + removed = [] + updated = [] + + # Identify removed and updated packages + for pkg in file1_packages: + if pkg not in file2_packages: + removed.append(pkg) + else: + # Compare version and features + triplet1, version1, features1 = file1_packages[pkg] + triplet2, version2, features2 = file2_packages[pkg] + updated_parts = [] + if version1 != version2 or triplet1 != triplet2: + updated_parts.append(f"{version1} -> {version2}") + added_features, removed_features = compare_features(features1, features2) + if added_features: + updated_parts.append("+" + ", ".join(added_features)) + if removed_features: + updated_parts.append("-" + ", ".join(removed_features)) + if updated_parts: + updated.append(f"{pkg}: " + " ".join(updated_parts)) + + # Identify added packages + for pkg in file2_packages: + if pkg not in file1_packages: + added.append(pkg) + + # Print the report + if added: + print("**Added packages:**") + for pkg in added: + triplet, version, features = file2_packages[pkg] + print(f" 🍓 {pkg}: {version} (Features: {', '.join(features)})") + + if removed: + print("\n**Removed packages:**") + for pkg in removed: + triplet, version, features = file1_packages[pkg] + print(f" 🍄 {pkg}: {version} (Features: {', '.join(features)})") + + if updated: + print("\n**Updated packages:**") + for pkg in updated: + print(f" 🍇 {pkg}") + + +def read_file(file_path): + """ + Read the content of a file. + """ + with open(file_path, "r") as file: + return file.read() + + +# Read files +file1_content = read_file("/tmp/vcpkg-base-output.txt") +file2_content = read_file("/tmp/vcpkg-head-output.txt") + +# Generate the report +generate_report(file1_content, file2_content) diff --git a/.github/workflows/build_artifact_comment.yml b/.github/workflows/build_artifact_comment.yml index bdd94c00054c..e953850d3f90 100644 --- a/.github/workflows/build_artifact_comment.yml +++ b/.github/workflows/build_artifact_comment.yml @@ -5,6 +5,7 @@ on: workflows: - 🪟 MingW64 Windows 64bit Build - 🪟 Windows Qt6 + - 🧮 Vcpkg report types: - completed diff --git a/.github/workflows/vcpkg-update-report.yml b/.github/workflows/vcpkg-update-report.yml new file mode 100644 index 000000000000..0d084d6d3d7b --- /dev/null +++ b/.github/workflows/vcpkg-update-report.yml @@ -0,0 +1,35 @@ +--- +name: 🧮 Vcpkg report +on: + pull_request: + paths: + - 'vcpkg/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + vcpkg-check: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 30 + + - name: Generate diff report + id: vcpkg_diff + uses: ./.github/actions/vcpkg_update_report + with: + vcpkg-manifest-dir: vcpkg + triplet: x64-linux + + - name: Schedule report comment + uses: ./.github/actions/post_sticky_comment + if: github.event_name == 'pull_request' + with: + marker: vcpkg-report + body: | + ### 🧮 Vcpkg update report + ${{ steps.vcpkg_diff.outputs.report }} + pr: ${{ github.event.number }} diff --git a/.github/workflows/windows-qt6.yml b/.github/workflows/windows-qt6.yml index 6d615d06f841..8b4d5a746f8e 100644 --- a/.github/workflows/windows-qt6.yml +++ b/.github/workflows/windows-qt6.yml @@ -76,8 +76,6 @@ jobs: -D FLEX_EXECUTABLE="${SOURCE_DIR}/win_flex.exe" \ -D BISON_EXECUTABLE="${SOURCE_DIR}/win_bison.exe" \ -D SIP_BUILD_EXECUTABLE="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\Scripts\sip-build.exe" \ - -D PYUIC_PROGRAM="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\pyuic5.bat" \ - -D PYRCC_PROGRAM="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\pyrcc5.bat" \ -D CMAKE_C_COMPILER_LAUNCHER=ccache \ -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ -D WITH_QTWEBKIT=OFF \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 82c4f3e81087..118d611bd20e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,6 @@ set (WITH_DESKTOP TRUE CACHE BOOL "Determines whether QGIS desktop should be bui set (WITH_GUI TRUE CACHE BOOL "Determines whether QGIS GUI library should be built") set(WITH_VCPKG FALSE CACHE BOOL "Use the vcpkg submodule for dependency management.") -set(WITH_VCPKG TRUE CACHE BOOL "Use the vcpkg submodule for dependency management.") set(SDK_PATH "" CACHE STRING "Build with VCPKG SDK") if(NOT SDK_PATH STREQUAL "") @@ -45,6 +44,9 @@ endif() if(WITH_VCPKG) list(APPEND CMAKE_PROGRAM_PATH "${VCPKG_INSTALL_PREFIX}/${VCPKG_TARGET_TRIPLET}/") + set(PREFER_INTERNAL_LIBS FALSE) +else() + set(PREFER_INTERNAL_LIBS TRUE) endif() ############################################################# @@ -272,7 +274,8 @@ if(WITH_CORE) endif() # try to configure and build POLY2TRI support - set (WITH_INTERNAL_POLY2TRI TRUE CACHE BOOL "Determines whether POLY2TRI should be built from internal copy") + set (WITH_INTERNAL_POLY2TRI PREFER_INTERNAL_LIBS CACHE BOOL "Determines whether POLY2TRI should be built from internal copy") + set (WITH_INTERNAL_MESHOPTIMIZER PREFER_INTERNAL_LIBS CACHE BOOL "Determines whether MESHOPTIMIZER should be built from internal copy") # try to configure and build POSTGRESQL support set (WITH_POSTGRESQL TRUE CACHE BOOL "Determines whether POSTGRESQL support should be built") @@ -385,7 +388,7 @@ if(WITH_CORE) find_package(EXPAT REQUIRED) find_package(Spatialindex REQUIRED) find_package(LibZip REQUIRED) - set (WITH_INTERNAL_NLOHMANN_JSON ON CACHE BOOL "Determines whether the vendored copy of nlohmann-json should be used") + set (WITH_INTERNAL_NLOHMANN_JSON PREFER_INTERNAL_LIBS CACHE BOOL "Determines whether the vendored copy of nlohmann-json should be used") find_package(nlohmann_json REQUIRED) # The following bypasses the FindSQLite3 module introduced in CMake 3.14 diff --git a/INSTALL.md b/INSTALL.md index 19e33110c122..0ecbcadf000b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,17 +26,17 @@ Building QGIS from source - step by step * [3.11.3. Additional tools for QGIS development](#3113-additional-tools-for-qgis-development) * [4. Building on Windows](#4-building-on-windows) * [4.1. Building with Microsoft Visual Studio](#41-building-with-microsoft-visual-studio) - * [4.1.1. Visual Studio 2019 Community Edition](#411-visual-studio-2019-community-edition) + * [4.1.1. Visual Studio 2022 Community Edition](#411-visual-studio-2022-community-edition) * [4.1.2. Other tools and dependencies](#412-other-tools-and-dependencies) * [4.1.3. Clone the QGIS Source Code](#413-clone-the-qgis-source-code) - * [4.1.4. OSGeo4W](#414-OSGeo4W) + * [4.1.4. OSGeo4W](#414-osgeo4w) * [4.2. Building on Linux with mingw64](#42-building-on-linux-with-mingw64) * [4.2.1. Building with Docker](#421-building-with-docker) * [4.2.1.1. Initial setup](#4211-initial-setup) * [4.2.1.2. Building the dependencies](#4212-building-the-dependencies) * [4.2.1.3. Cross-Building QGIS](#4213-cross-building-qgis) * [4.2.2. Testing QGIS](#422-testing-qgis) - * [4.3 Building for Qt 6 with VCPKG in Microsoft Visual Studio](#41-building-with-qt6) + * [4.3 Building for Qt 6 with VCPKG in Microsoft Visual Studio](43-building-on-windows-with-vcpkg) * [5. Building on MacOS X](#5-building-on-macos-x) * [5.1. Install Developer Tools](#51-install-developer-tools) * [5.2. Install CMake and other build tools](#52-install-cmake-and-other-build-tools) diff --git a/python/PyQt6/core/auto_additions/qgsrenderchecker.py b/python/PyQt6/core/auto_additions/qgsrenderchecker.py index f797e96ce7dd..9a43668bc849 100644 --- a/python/PyQt6/core/auto_additions/qgsrenderchecker.py +++ b/python/PyQt6/core/auto_additions/qgsrenderchecker.py @@ -1,11 +1,16 @@ # The following has been generated automatically from src/core/qgsrenderchecker.h # monkey patching scoped based enum QgsRenderChecker.Flag.AvoidExportingRenderedImage.__doc__ = "Avoids exporting rendered images to reports" +QgsRenderChecker.Flag.Silent.__doc__ = "Don't output non-critical messages to console \n.. versionadded:: 3.40" QgsRenderChecker.Flag.__doc__ = """Render checker flags. .. versionadded:: 3.28 * ``AvoidExportingRenderedImage``: Avoids exporting rendered images to reports +* ``Silent``: Don't output non-critical messages to console + + .. versionadded:: 3.40 + """ # -- diff --git a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in index 1b9a340f1b69..539b29a21125 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in @@ -643,6 +643,17 @@ If ``useZValues`` is ``True`` then z values will also be considered when testing + QVector splitToDisjointXYParts() const /Factory/; +%Docstring +Divides the linestring into parts that don't share any points or lines. + +This method throws away Z and M coordinates. + +The ownership of returned pointers is transferred to the caller. + +.. versionadded:: 3.40 +%End + double length3D() const /HoldGIL/; %Docstring Returns the length in 3D world of the line string. diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index 3de25b9e682b..fa0bf644f596 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -301,6 +301,7 @@ Emitted when point cloud generation state is changed protected: + }; /************************************************************************ diff --git a/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in b/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in index e854c08109f9..6306aa0c4417 100644 --- a/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in +++ b/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in @@ -183,6 +183,7 @@ Sets the largest allowable difference in size between the rendered and the expec enum class Flag /BaseType=IntFlag/ { AvoidExportingRenderedImage, + Silent, }; typedef QFlags Flags; diff --git a/python/PyQt6/core/class_map.yaml b/python/PyQt6/core/class_map.yaml index 5fc369f2c917..1f096a4162bf 100644 --- a/python/PyQt6/core/class_map.yaml +++ b/python/PyQt6/core/class_map.yaml @@ -8395,14 +8395,14 @@ QgsLineSegment2D.startX: src/core/geometry/qgslinesegment.h#L80 QgsLineSegment2D.startY: src/core/geometry/qgslinesegment.h#L90 QgsLineSegment2D: src/core/geometry/qgslinesegment.h#L31 QgsLineString.QgsLineString: src/core/geometry/qgslinestring.h#L257 -QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1159 -QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1100 -QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1082 -QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1128 -QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1050 -QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1021 +QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1169 +QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1110 +QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1092 +QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1138 +QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1060 +QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1031 QgsLineString.addVertex: src/core/geometry/qgslinestring.h#L918 -QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1049 +QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1059 QgsLineString.append: src/core/geometry/qgslinestring.h#L912 QgsLineString.asGml2: src/core/geometry/qgslinestring.h#L982 QgsLineString.asGml3: src/core/geometry/qgslinestring.h#L983 @@ -8412,25 +8412,25 @@ QgsLineString.asWkb: src/core/geometry/qgslinestring.h#L980 QgsLineString.asWkt: src/core/geometry/qgslinestring.h#L981 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L961 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L962 -QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1189 -QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1182 -QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1199 +QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1192 +QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1045 QgsLineString.clear: src/core/geometry/qgslinestring.h#L953 QgsLineString.clone: src/core/geometry/qgslinestring.h#L952 QgsLineString.close: src/core/geometry/qgslinestring.h#L921 -QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1032 -QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1242 -QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1056 -QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1079 -QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1030 -QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1010 -QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1042 +QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1252 +QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1066 +QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1089 +QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1040 +QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1020 +QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1036 QgsLineString.dimension: src/core/geometry/qgslinestring.h#L951 -QgsLineString.draw: src/core/geometry/qgslinestring.h#L1016 -QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1022 -QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1053 -QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1052 -QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1002 +QgsLineString.draw: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1032 +QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1063 +QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1062 +QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1012 QgsLineString.equals: src/core/geometry/qgslinestring.h#L429 QgsLineString.extend: src/core/geometry/qgslinestring.h#L934 QgsLineString.fromBezierCurve: src/core/geometry/qgslinestring.h#L296 @@ -8441,28 +8441,28 @@ QgsLineString.fuzzyDistanceEqual: src/core/geometry/qgslinestring.h#L424 QgsLineString.fuzzyEqual: src/core/geometry/qgslinestring.h#L395 QgsLineString.geometryType: src/core/geometry/qgslinestring.h#L950 QgsLineString.indexOf: src/core/geometry/qgslinestring.h#L955 -QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1024 -QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1212 -QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1034 +QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1222 +QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1039 QgsLineString.isClosed2D: src/core/geometry/qgslinestring.h#L960 QgsLineString.isClosed: src/core/geometry/qgslinestring.h#L959 QgsLineString.isEmpty: src/core/geometry/qgslinestring.h#L954 QgsLineString.isValid: src/core/geometry/qgslinestring.h#L956 -QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1000 +QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1010 QgsLineString.length: src/core/geometry/qgslinestring.h#L988 -QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1238 +QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1248 QgsLineString.mAt: src/core/geometry/qgslinestring.h#L698 -QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1197 -QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1025 -QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1013 -QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1012 -QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1033 +QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1207 +QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1023 +QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1022 +QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1043 QgsLineString.pointN: src/core/geometry/qgslinestring.h#L449 -QgsLineString.points: src/core/geometry/qgslinestring.h#L1014 +QgsLineString.points: src/core/geometry/qgslinestring.h#L1024 QgsLineString.removeDuplicateNodes: src/core/geometry/qgslinestring.h#L958 -QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1028 -QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1059 -QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1048 +QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1038 +QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1069 +QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1058 QgsLineString.setMAt: src/core/geometry/qgslinestring.h#L868 QgsLineString.setPoints: src/core/geometry/qgslinestring.h#L906 QgsLineString.setXAt: src/core/geometry/qgslinestring.h#L739 @@ -8470,14 +8470,14 @@ QgsLineString.setYAt: src/core/geometry/qgslinestring.h#L780 QgsLineString.setZAt: src/core/geometry/qgslinestring.h#L824 QgsLineString.simplifyByDistance: src/core/geometry/qgslinestring.h#L975 QgsLineString.snappedToGrid: src/core/geometry/qgslinestring.h#L957 -QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1001 -QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1045 -QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1054 +QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1011 +QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1055 +QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1064 QgsLineString.toCurveType: src/core/geometry/qgslinestring.h#L927 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1018 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1019 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1058 -QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1047 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1028 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1068 +QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1057 QgsLineString.wkbSize: src/core/geometry/qgslinestring.h#L979 QgsLineString.xAt: src/core/geometry/qgslinestring.h#L481 QgsLineString.yAt: src/core/geometry/qgslinestring.h#L511 @@ -14879,20 +14879,20 @@ QgsRemappingSinkDefinition.setSourceCrs: src/core/qgsremappingproxyfeaturesink.h QgsRemappingSinkDefinition.sourceCrs: src/core/qgsremappingproxyfeaturesink.h#L82 QgsRemappingSinkDefinition.toVariant: src/core/qgsremappingproxyfeaturesink.h#L138 QgsRemappingSinkDefinition: src/core/qgsremappingproxyfeaturesink.h#L38 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L240 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L247 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L241 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L248 QgsRenderChecker.controlImagePath: src/core/qgsrenderchecker.h#L74 -QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L265 +QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L266 QgsRenderChecker.elapsedTime: src/core/qgsrenderchecker.h#L129 -QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L281 -QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L272 +QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L282 +QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L273 QgsRenderChecker.imageToHash: src/core/qgsrenderchecker.h#L156 -QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L259 +QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L260 QgsRenderChecker.markdownReport: src/core/qgsrenderchecker.h#L103 QgsRenderChecker.matchPercent: src/core/qgsrenderchecker.h#L112 QgsRenderChecker.renderedImage: src/core/qgsrenderchecker.h#L175 QgsRenderChecker.report: src/core/qgsrenderchecker.h#L92 -QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L225 +QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L226 QgsRenderChecker.setColorTolerance: src/core/qgsrenderchecker.h#L185 QgsRenderChecker.setControlExtension: src/core/qgsrenderchecker.h#L145 QgsRenderChecker.setControlImagePath: src/core/qgsrenderchecker.h#L82 @@ -14905,7 +14905,7 @@ QgsRenderChecker.setMapSettings: src/core/qgsrenderchecker.h#L177 QgsRenderChecker.setRenderedImage: src/core/qgsrenderchecker.h#L161 QgsRenderChecker.setSizeTolerance: src/core/qgsrenderchecker.h#L192 QgsRenderChecker.shouldGenerateReport: src/core/qgsrenderchecker.h#L65 -QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L296 +QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L297 QgsRenderChecker.testReportDir: src/core/qgsrenderchecker.h#L57 QgsRenderChecker: src/core/qgsrenderchecker.h#L41 QgsRenderContext.addSymbolLayerClipGeometry: src/core/qgsrendercontext.h#L1028 diff --git a/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py b/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py index 012c2ff6e9de..245b0470a35f 100644 --- a/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py +++ b/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py @@ -17,7 +17,7 @@ *************************************************************************** """ -from qgis.PyQt.QtWidgets import QWidget, QComboBox +from qgis.PyQt.QtWidgets import QComboBox from qgis.core import QgsSettingsEntryBase from qgis.gui import QgsSettingsEditorWidgetWrapper @@ -46,23 +46,25 @@ def createWrapper(self, parent=None): def setWidgetFromSetting(self): if self.setting: - return self.setWidgetFromVariant(self.setting.value(self.dynamicKeyPartList())) + return self.setWidgetFromVariant(self.setting.valueAsVariant(self.dynamicKeyPartList())) return False def setSettingFromWidget(self): if self.editor: - self.setting.setValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) + self.setting.setVariantValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) return True else: return False def variantValueFromWidget(self): if self.editor: - return self.setting.defaultValue().__class__(self.editor.currentData()) + return self.editor.currentData() return None def setWidgetFromVariant(self, value): - if self.editor: + if self.editor and value is not None: + if isinstance(value, int): + value = self.setting.metaEnum().valueToKey(value) idx = self.editor.findData(value) self.editor.setCurrentIndex(idx) return idx >= 0 @@ -71,7 +73,7 @@ def setWidgetFromVariant(self, value): def createEditorPrivate(self, parent=None): return QComboBox(parent) - def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase): + def configureEditorPrivate(self, editor: QComboBox, setting: QgsSettingsEntryBase): self.setting = setting if isinstance(editor, QComboBox): self.editor = editor @@ -79,7 +81,7 @@ def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase) value = self.setting.metaEnum().value(i) key = self.setting.metaEnum().key(i) text = self.displayStrings.get(value, key) - self.editor.addItem(text, value) + self.editor.addItem(text, key) return True else: return False diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in index 8451b57d139b..b3af58e91d98 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in @@ -34,12 +34,19 @@ Adds an editor widget ``wrapper`` to the registry If an editor widget with same id already exists, the wrapper is deleted and ``False`` is returned. %End - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper /Transfer/, const QgsSettingsEntryBase *setting /KeepReference/ ); +%Docstring +Adds an editor widget ``wrapper`` for a specific setting to the registry + +.. versionadded:: 3.40 +%End + + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const /Factory/; %Docstring Returns a new instance of the editor widget for the given ``id`` %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /Factory/; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /TransferBack/; %Docstring Creates an editor widget for the given ``setting`` using the corresponding registered wrapper %End diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in index 2d5c5fd16fdf..7e6bbe1faaa9 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in @@ -23,7 +23,7 @@ Base class for settings editor wrappers #include "qgssettingseditorwidgetwrapper.h" %End public: - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) /Factory/; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); %Docstring Creates a wrapper from the definition stored in a ``widget`` created by :py:func:`~QgsSettingsEditorWidgetWrapper.createEditor` %End @@ -44,12 +44,12 @@ This id of the type of settings it handles This mostly correspond to the content of :py:class:`Qgis`.SettingsType but it's a string since custom Python implementation are possible. %End - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0 /Factory/; %Docstring Creates a new instance of the editor wrapper so it can be configured for a widget and a setting %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ) /TransferBack/; %Docstring Creates the editor widget for the given ``setting`` %End @@ -77,7 +77,7 @@ Returns the value from the widget as a variant The wrapper must be configured before calling this medthod %End - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; %Docstring Sets the ``value`` of the widget The wrapper must be configured before calling this medthod @@ -106,12 +106,12 @@ Returns the dynamic key parts protected: - virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0 /TransferBack/; %Docstring Creates the widgets %End - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor /TransferBack/, const QgsSettingsEntryBase *setting /KeepReference/ ) = 0; %Docstring Configures an existing ``editor`` widget %End diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in index 9f0fb0b0965d..92d26f50b7e2 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in @@ -36,7 +36,7 @@ Constructor virtual bool setSettingFromWidget() const = 0; - virtual void setWidgetFromVariant( const QVariant &value ) const; + virtual bool setWidgetFromVariant( const QVariant &value ) const; virtual bool setWidgetValue( const U &value ) const = 0; %Docstring diff --git a/python/PyQt6/gui/class_map.yaml b/python/PyQt6/gui/class_map.yaml index acd1eb675a33..b4f73b7ec9a6 100644 --- a/python/PyQt6/gui/class_map.yaml +++ b/python/PyQt6/gui/class_map.yaml @@ -6732,8 +6732,9 @@ QgsSettingsDoubleSpinBoxWrapper.setWidgetValue: src/gui/settings/qgssettingsedit QgsSettingsDoubleSpinBoxWrapper.valueFromWidget: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L285 QgsSettingsDoubleSpinBoxWrapper: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L267 QgsSettingsEditorWidgetRegistry.addWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L46 -QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 -QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L49 +QgsSettingsEditorWidgetRegistry.addWrapperForSetting: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 +QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L58 +QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L55 QgsSettingsEditorWidgetRegistry: src/gui/settings/qgssettingseditorwidgetregistry.h#L35 QgsSettingsEditorWidgetWrapper.configureAutomaticUpdate: src/gui/settings/qgssettingseditorwidgetwrapper.h#L95 QgsSettingsEditorWidgetWrapper.configureEditor: src/gui/settings/qgssettingseditorwidgetwrapper.h#L59 diff --git a/python/core/auto_additions/qgsrenderchecker.py b/python/core/auto_additions/qgsrenderchecker.py index 0bb885f095ab..ba68598300ce 100644 --- a/python/core/auto_additions/qgsrenderchecker.py +++ b/python/core/auto_additions/qgsrenderchecker.py @@ -1,11 +1,16 @@ # The following has been generated automatically from src/core/qgsrenderchecker.h # monkey patching scoped based enum QgsRenderChecker.Flag.AvoidExportingRenderedImage.__doc__ = "Avoids exporting rendered images to reports" +QgsRenderChecker.Flag.Silent.__doc__ = "Don't output non-critical messages to console \n.. versionadded:: 3.40" QgsRenderChecker.Flag.__doc__ = """Render checker flags. .. versionadded:: 3.28 * ``AvoidExportingRenderedImage``: Avoids exporting rendered images to reports +* ``Silent``: Don't output non-critical messages to console + + .. versionadded:: 3.40 + """ # -- diff --git a/python/core/auto_generated/geometry/qgslinestring.sip.in b/python/core/auto_generated/geometry/qgslinestring.sip.in index 1b9a340f1b69..539b29a21125 100644 --- a/python/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/core/auto_generated/geometry/qgslinestring.sip.in @@ -643,6 +643,17 @@ If ``useZValues`` is ``True`` then z values will also be considered when testing + QVector splitToDisjointXYParts() const /Factory/; +%Docstring +Divides the linestring into parts that don't share any points or lines. + +This method throws away Z and M coordinates. + +The ownership of returned pointers is transferred to the caller. + +.. versionadded:: 3.40 +%End + double length3D() const /HoldGIL/; %Docstring Returns the length in 3D world of the line string. diff --git a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index beeac3339fc0..3c5fd6b753ed 100644 --- a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -301,6 +301,7 @@ Emitted when point cloud generation state is changed protected: + }; /************************************************************************ diff --git a/python/core/auto_generated/qgsrenderchecker.sip.in b/python/core/auto_generated/qgsrenderchecker.sip.in index 67e6c82e5415..068cf934c6a5 100644 --- a/python/core/auto_generated/qgsrenderchecker.sip.in +++ b/python/core/auto_generated/qgsrenderchecker.sip.in @@ -183,6 +183,7 @@ Sets the largest allowable difference in size between the rendered and the expec enum class Flag { AvoidExportingRenderedImage, + Silent, }; typedef QFlags Flags; diff --git a/python/core/class_map.yaml b/python/core/class_map.yaml index 77a9bfcc6d88..de236db790c3 100644 --- a/python/core/class_map.yaml +++ b/python/core/class_map.yaml @@ -8395,14 +8395,14 @@ QgsLineSegment2D.startX: src/core/geometry/qgslinesegment.h#L80 QgsLineSegment2D.startY: src/core/geometry/qgslinesegment.h#L90 QgsLineSegment2D: src/core/geometry/qgslinesegment.h#L31 QgsLineString.QgsLineString: src/core/geometry/qgslinestring.h#L257 -QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1159 -QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1100 -QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1082 -QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1128 -QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1050 -QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1021 +QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1169 +QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1110 +QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1092 +QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1138 +QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1060 +QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1031 QgsLineString.addVertex: src/core/geometry/qgslinestring.h#L918 -QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1049 +QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1059 QgsLineString.append: src/core/geometry/qgslinestring.h#L912 QgsLineString.asGml2: src/core/geometry/qgslinestring.h#L982 QgsLineString.asGml3: src/core/geometry/qgslinestring.h#L983 @@ -8412,25 +8412,25 @@ QgsLineString.asWkb: src/core/geometry/qgslinestring.h#L980 QgsLineString.asWkt: src/core/geometry/qgslinestring.h#L981 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L961 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L962 -QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1189 -QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1182 -QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1199 +QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1192 +QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1045 QgsLineString.clear: src/core/geometry/qgslinestring.h#L953 QgsLineString.clone: src/core/geometry/qgslinestring.h#L952 QgsLineString.close: src/core/geometry/qgslinestring.h#L921 -QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1032 -QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1242 -QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1056 -QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1079 -QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1030 -QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1010 -QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1042 +QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1252 +QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1066 +QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1089 +QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1040 +QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1020 +QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1036 QgsLineString.dimension: src/core/geometry/qgslinestring.h#L951 -QgsLineString.draw: src/core/geometry/qgslinestring.h#L1016 -QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1022 -QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1053 -QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1052 -QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1002 +QgsLineString.draw: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1032 +QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1063 +QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1062 +QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1012 QgsLineString.equals: src/core/geometry/qgslinestring.h#L429 QgsLineString.extend: src/core/geometry/qgslinestring.h#L934 QgsLineString.fromBezierCurve: src/core/geometry/qgslinestring.h#L296 @@ -8441,28 +8441,28 @@ QgsLineString.fuzzyDistanceEqual: src/core/geometry/qgslinestring.h#L424 QgsLineString.fuzzyEqual: src/core/geometry/qgslinestring.h#L395 QgsLineString.geometryType: src/core/geometry/qgslinestring.h#L950 QgsLineString.indexOf: src/core/geometry/qgslinestring.h#L955 -QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1024 -QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1212 -QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1034 +QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1222 +QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1039 QgsLineString.isClosed2D: src/core/geometry/qgslinestring.h#L960 QgsLineString.isClosed: src/core/geometry/qgslinestring.h#L959 QgsLineString.isEmpty: src/core/geometry/qgslinestring.h#L954 QgsLineString.isValid: src/core/geometry/qgslinestring.h#L956 -QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1000 +QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1010 QgsLineString.length: src/core/geometry/qgslinestring.h#L988 -QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1238 +QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1248 QgsLineString.mAt: src/core/geometry/qgslinestring.h#L698 -QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1197 -QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1025 -QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1013 -QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1012 -QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1033 +QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1207 +QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1023 +QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1022 +QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1043 QgsLineString.pointN: src/core/geometry/qgslinestring.h#L449 -QgsLineString.points: src/core/geometry/qgslinestring.h#L1014 +QgsLineString.points: src/core/geometry/qgslinestring.h#L1024 QgsLineString.removeDuplicateNodes: src/core/geometry/qgslinestring.h#L958 -QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1028 -QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1059 -QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1048 +QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1038 +QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1069 +QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1058 QgsLineString.setMAt: src/core/geometry/qgslinestring.h#L868 QgsLineString.setPoints: src/core/geometry/qgslinestring.h#L906 QgsLineString.setXAt: src/core/geometry/qgslinestring.h#L739 @@ -8470,14 +8470,14 @@ QgsLineString.setYAt: src/core/geometry/qgslinestring.h#L780 QgsLineString.setZAt: src/core/geometry/qgslinestring.h#L824 QgsLineString.simplifyByDistance: src/core/geometry/qgslinestring.h#L975 QgsLineString.snappedToGrid: src/core/geometry/qgslinestring.h#L957 -QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1001 -QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1045 -QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1054 +QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1011 +QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1055 +QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1064 QgsLineString.toCurveType: src/core/geometry/qgslinestring.h#L927 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1018 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1019 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1058 -QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1047 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1028 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1068 +QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1057 QgsLineString.wkbSize: src/core/geometry/qgslinestring.h#L979 QgsLineString.xAt: src/core/geometry/qgslinestring.h#L481 QgsLineString.yAt: src/core/geometry/qgslinestring.h#L511 @@ -14879,20 +14879,20 @@ QgsRemappingSinkDefinition.setSourceCrs: src/core/qgsremappingproxyfeaturesink.h QgsRemappingSinkDefinition.sourceCrs: src/core/qgsremappingproxyfeaturesink.h#L82 QgsRemappingSinkDefinition.toVariant: src/core/qgsremappingproxyfeaturesink.h#L138 QgsRemappingSinkDefinition: src/core/qgsremappingproxyfeaturesink.h#L38 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L240 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L247 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L241 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L248 QgsRenderChecker.controlImagePath: src/core/qgsrenderchecker.h#L74 -QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L265 +QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L266 QgsRenderChecker.elapsedTime: src/core/qgsrenderchecker.h#L129 -QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L281 -QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L272 +QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L282 +QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L273 QgsRenderChecker.imageToHash: src/core/qgsrenderchecker.h#L156 -QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L259 +QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L260 QgsRenderChecker.markdownReport: src/core/qgsrenderchecker.h#L103 QgsRenderChecker.matchPercent: src/core/qgsrenderchecker.h#L112 QgsRenderChecker.renderedImage: src/core/qgsrenderchecker.h#L175 QgsRenderChecker.report: src/core/qgsrenderchecker.h#L92 -QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L225 +QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L226 QgsRenderChecker.setColorTolerance: src/core/qgsrenderchecker.h#L185 QgsRenderChecker.setControlExtension: src/core/qgsrenderchecker.h#L145 QgsRenderChecker.setControlImagePath: src/core/qgsrenderchecker.h#L82 @@ -14905,7 +14905,7 @@ QgsRenderChecker.setMapSettings: src/core/qgsrenderchecker.h#L177 QgsRenderChecker.setRenderedImage: src/core/qgsrenderchecker.h#L161 QgsRenderChecker.setSizeTolerance: src/core/qgsrenderchecker.h#L192 QgsRenderChecker.shouldGenerateReport: src/core/qgsrenderchecker.h#L65 -QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L296 +QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L297 QgsRenderChecker.testReportDir: src/core/qgsrenderchecker.h#L57 QgsRenderChecker: src/core/qgsrenderchecker.h#L41 QgsRenderContext.addSymbolLayerClipGeometry: src/core/qgsrendercontext.h#L1028 diff --git a/python/gui/additions/qgssettingsenumflageditorwrapper.py b/python/gui/additions/qgssettingsenumflageditorwrapper.py index 012c2ff6e9de..245b0470a35f 100644 --- a/python/gui/additions/qgssettingsenumflageditorwrapper.py +++ b/python/gui/additions/qgssettingsenumflageditorwrapper.py @@ -17,7 +17,7 @@ *************************************************************************** """ -from qgis.PyQt.QtWidgets import QWidget, QComboBox +from qgis.PyQt.QtWidgets import QComboBox from qgis.core import QgsSettingsEntryBase from qgis.gui import QgsSettingsEditorWidgetWrapper @@ -46,23 +46,25 @@ def createWrapper(self, parent=None): def setWidgetFromSetting(self): if self.setting: - return self.setWidgetFromVariant(self.setting.value(self.dynamicKeyPartList())) + return self.setWidgetFromVariant(self.setting.valueAsVariant(self.dynamicKeyPartList())) return False def setSettingFromWidget(self): if self.editor: - self.setting.setValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) + self.setting.setVariantValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) return True else: return False def variantValueFromWidget(self): if self.editor: - return self.setting.defaultValue().__class__(self.editor.currentData()) + return self.editor.currentData() return None def setWidgetFromVariant(self, value): - if self.editor: + if self.editor and value is not None: + if isinstance(value, int): + value = self.setting.metaEnum().valueToKey(value) idx = self.editor.findData(value) self.editor.setCurrentIndex(idx) return idx >= 0 @@ -71,7 +73,7 @@ def setWidgetFromVariant(self, value): def createEditorPrivate(self, parent=None): return QComboBox(parent) - def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase): + def configureEditorPrivate(self, editor: QComboBox, setting: QgsSettingsEntryBase): self.setting = setting if isinstance(editor, QComboBox): self.editor = editor @@ -79,7 +81,7 @@ def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase) value = self.setting.metaEnum().value(i) key = self.setting.metaEnum().key(i) text = self.displayStrings.get(value, key) - self.editor.addItem(text, value) + self.editor.addItem(text, key) return True else: return False diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in index 8451b57d139b..b3af58e91d98 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in @@ -34,12 +34,19 @@ Adds an editor widget ``wrapper`` to the registry If an editor widget with same id already exists, the wrapper is deleted and ``False`` is returned. %End - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper /Transfer/, const QgsSettingsEntryBase *setting /KeepReference/ ); +%Docstring +Adds an editor widget ``wrapper`` for a specific setting to the registry + +.. versionadded:: 3.40 +%End + + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const /Factory/; %Docstring Returns a new instance of the editor widget for the given ``id`` %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /Factory/; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /TransferBack/; %Docstring Creates an editor widget for the given ``setting`` using the corresponding registered wrapper %End diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in index 2d5c5fd16fdf..7e6bbe1faaa9 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in @@ -23,7 +23,7 @@ Base class for settings editor wrappers #include "qgssettingseditorwidgetwrapper.h" %End public: - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) /Factory/; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); %Docstring Creates a wrapper from the definition stored in a ``widget`` created by :py:func:`~QgsSettingsEditorWidgetWrapper.createEditor` %End @@ -44,12 +44,12 @@ This id of the type of settings it handles This mostly correspond to the content of :py:class:`Qgis`.SettingsType but it's a string since custom Python implementation are possible. %End - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0 /Factory/; %Docstring Creates a new instance of the editor wrapper so it can be configured for a widget and a setting %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ) /TransferBack/; %Docstring Creates the editor widget for the given ``setting`` %End @@ -77,7 +77,7 @@ Returns the value from the widget as a variant The wrapper must be configured before calling this medthod %End - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; %Docstring Sets the ``value`` of the widget The wrapper must be configured before calling this medthod @@ -106,12 +106,12 @@ Returns the dynamic key parts protected: - virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0 /TransferBack/; %Docstring Creates the widgets %End - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor /TransferBack/, const QgsSettingsEntryBase *setting /KeepReference/ ) = 0; %Docstring Configures an existing ``editor`` widget %End diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in index 5120c0f440b2..a81c81f014ef 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in @@ -36,7 +36,7 @@ Constructor virtual bool setSettingFromWidget() const = 0; - virtual void setWidgetFromVariant( const QVariant &value ) const; + virtual bool setWidgetFromVariant( const QVariant &value ) const; virtual bool setWidgetValue( const U &value ) const = 0; %Docstring diff --git a/python/gui/class_map.yaml b/python/gui/class_map.yaml index acd1eb675a33..b4f73b7ec9a6 100644 --- a/python/gui/class_map.yaml +++ b/python/gui/class_map.yaml @@ -6732,8 +6732,9 @@ QgsSettingsDoubleSpinBoxWrapper.setWidgetValue: src/gui/settings/qgssettingsedit QgsSettingsDoubleSpinBoxWrapper.valueFromWidget: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L285 QgsSettingsDoubleSpinBoxWrapper: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L267 QgsSettingsEditorWidgetRegistry.addWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L46 -QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 -QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L49 +QgsSettingsEditorWidgetRegistry.addWrapperForSetting: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 +QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L58 +QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L55 QgsSettingsEditorWidgetRegistry: src/gui/settings/qgssettingseditorwidgetregistry.h#L35 QgsSettingsEditorWidgetWrapper.configureAutomaticUpdate: src/gui/settings/qgssettingseditorwidgetwrapper.h#L95 QgsSettingsEditorWidgetWrapper.configureEditor: src/gui/settings/qgssettingseditorwidgetwrapper.h#L59 diff --git a/python/plugins/grassprovider/grass_utils.py b/python/plugins/grassprovider/grass_utils.py index 7d98aee3ee37..37c784c09d7d 100644 --- a/python/plugins/grassprovider/grass_utils.py +++ b/python/plugins/grassprovider/grass_utils.py @@ -463,7 +463,9 @@ def readline_with_recover(stdout): if 'r.out' in line or 'v.out' in line: grassOutDone = True loglines.append(line) - if any([l in line for l in ['WARNING', 'ERROR']]): + if any([l in line for l in ['WARNING', GrassUtils.tr('WARNING')]]): + feedback.pushWarning(line.strip()) + elif any([l in line for l in ['ERROR', GrassUtils.tr('ERROR')]]): feedback.reportError(line.strip()) elif 'Segmentation fault' in line: feedback.reportError(line.strip()) diff --git a/resources/server/src/landingpage/yarn.lock b/resources/server/src/landingpage/yarn.lock index e35766857376..3fd583265b8b 100644 --- a/resources/server/src/landingpage/yarn.lock +++ b/resources/server/src/landingpage/yarn.lock @@ -5184,9 +5184,9 @@ http-parser-js@>=0.5.1: integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 750cbcaaa72f..763de0f4968a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -30,8 +30,6 @@ set(QGIS_CORE_SRCS ${CMAKE_SOURCE_DIR}/external/nmea/time.c ${CMAKE_SOURCE_DIR}/external/nmea/tok.c - ${CMAKE_SOURCE_DIR}/external/meshOptimizer/simplifier.cpp - ${FLEX_QgsExpressionLexer_OUTPUTS} ${BISON_QgsExpressionParser_OUTPUTS} ${FLEX_QgsSqlStatementLexer_OUTPUTS} @@ -365,6 +363,7 @@ set(QGIS_CORE_SRCS stac/qgsstacasset.cpp stac/qgsstaccatalog.cpp stac/qgsstaccollection.cpp + stac/qgsstaccollections.cpp stac/qgsstacconnection.cpp stac/qgsstaccontroller.cpp stac/qgsstacdataitems.cpp @@ -1960,6 +1959,7 @@ set(QGIS_CORE_HDRS stac/qgsstacasset.h stac/qgsstaccatalog.h stac/qgsstaccollection.h + stac/qgsstaccollections.h stac/qgsstacconnection.h stac/qgsstaccontroller.h stac/qgsstacdataitems.h @@ -2339,6 +2339,20 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_library(qgis_core ${LIBRARY_TYPE} ${QGIS_CORE_SRCS} ${QGIS_CORE_HDRS} ${QGIS_CORE_PRIVATE_HDRS} ${IMAGE_RCCS}) +# Add meshoptimizer +if(WITH_INTERNAL_MESHOPTIMIZER) + target_sources(qgis_core PRIVATE + ${CMAKE_SOURCE_DIR}/external/meshOptimizer/simplifier.cpp + ) + + target_include_directories(qgis_core PRIVATE + ${CMAKE_SOURCE_DIR}/external/meshOptimizer + ) +else() + find_package(meshoptimizer CONFIG REQUIRED) + target_link_libraries(qgis_core PRIVATE meshoptimizer::meshoptimizer) +endif() + # require c++17 target_compile_features(qgis_core PRIVATE cxx_std_17) @@ -2434,7 +2448,6 @@ target_include_directories(qgis_core PUBLIC ${CMAKE_SOURCE_DIR}/external/kdbush/include ${CMAKE_SOURCE_DIR}/external/nmea ${CMAKE_SOURCE_DIR}/external/rtree/include - ${CMAKE_SOURCE_DIR}/external/meshOptimizer ${CMAKE_SOURCE_DIR}/external/tinygltf ) diff --git a/src/core/expression/qgsexpression.cpp b/src/core/expression/qgsexpression.cpp index 528c3616ff57..68c9e0500ed0 100644 --- a/src/core/expression/qgsexpression.cpp +++ b/src/core/expression/qgsexpression.cpp @@ -1022,10 +1022,10 @@ QString QgsExpression::formatPreviewString( const QVariant &value, const bool ht const QString startToken = htmlOutput ? QStringLiteral( "<" ) : QStringLiteral( "<" ); const QString endToken = htmlOutput ? QStringLiteral( ">" ) : QStringLiteral( ">" ); - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, nullptr ); + if ( !geom.isNull() ) { //result is a geometry - QgsGeometry geom = value.value(); if ( geom.isNull() ) return startToken + tr( "empty geometry" ) + endToken; else diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 212ae03dc1e2..6b17cde4475c 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1490,14 +1490,15 @@ static QVariant fcnWordwrap( const QVariantList &values, const QgsExpressionCont static QVariant fcnLength( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { // two variants, one for geometry, one for string - if ( values.at( 0 ).userType() == qMetaTypeId< QgsGeometry>() ) + + //geometry variant + QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent, true ); + if ( !geom.isNull() ) { - //geometry variant - QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); - if ( geom.type() != Qgis::GeometryType::Line ) + if ( geom.type() == Qgis::GeometryType::Line ) + return QVariant( geom.length() ); + else return QVariant(); - - return QVariant( geom.length() ); } //otherwise fall back to string variant @@ -3696,15 +3697,10 @@ static QVariant fcnCollectGeometries( const QVariantList &values, const QgsExpre parts.reserve( list.size() ); for ( const QVariant &value : std::as_const( list ) ) { - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) - { - parts << value.value(); - } - else - { - parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); + QgsGeometry part = QgsExpressionUtils::getGeometry( value, parent ); + if ( part.isNull() ) return QgsGeometry(); - } + parts << part; } return QgsGeometry::collectGeometry( parts ); diff --git a/src/core/expression/qgsexpressionutils.h b/src/core/expression/qgsexpressionutils.h index 4fbd3c0fa475..af4360cd9c30 100644 --- a/src/core/expression/qgsexpressionutils.h +++ b/src/core/expression/qgsexpressionutils.h @@ -23,6 +23,7 @@ #include "qgsexpression.h" #include "qgsvariantutils.h" #include "qgsfeaturerequest.h" +#include "qgsreferencedgeometry.h" #include #include @@ -94,15 +95,16 @@ class CORE_EXPORT QgsExpressionUtils return Unknown; //handle some special cases + int userType = value.userType(); if ( value.type() == QVariant::UserType ) { - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) + if ( userType == qMetaTypeId< QgsGeometry>() || userType == qMetaTypeId() ) { //geom is false if empty - const QgsGeometry geom = value.value(); + const QgsGeometry geom = getGeometry( value, nullptr ); return geom.isNull() ? False : True; } - else if ( value.userType() == qMetaTypeId() ) + else if ( userType == qMetaTypeId() ) { //feat is false if non-valid const QgsFeature feat = value.value(); @@ -110,14 +112,15 @@ class CORE_EXPORT QgsExpressionUtils } } - if ( value.userType() == QMetaType::Type::Int ) + if ( userType == QMetaType::Type::Int ) return value.toInt() != 0 ? True : False; bool ok; const double x = value.toDouble( &ok ); if ( !ok ) { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to boolean" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to boolean" ).arg( value.toString() ) ); return Unknown; } return !qgsDoubleNear( x, 0.0 ) ? True : False; @@ -215,7 +218,8 @@ class CORE_EXPORT QgsExpressionUtils { if ( value.userType() != QMetaType::Type::QByteArray ) { - parent->setEvalErrorString( QObject::tr( "Value is not a binary value" ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Value is not a binary value" ) ); return QByteArray(); } return value.toByteArray(); @@ -227,7 +231,8 @@ class CORE_EXPORT QgsExpressionUtils const double x = value.toDouble( &ok ); if ( !ok || std::isnan( x ) || !std::isfinite( x ) ) { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to double" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to double" ).arg( value.toString() ) ); return 0; } return x; @@ -243,7 +248,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to int" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to int" ).arg( value.toString() ) ); return 0; } } @@ -258,7 +264,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to native int" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to native int" ).arg( value.toString() ) ); return 0; } } @@ -278,7 +285,8 @@ class CORE_EXPORT QgsExpressionUtils return QDateTime( QDate( 1, 1, 1 ), t ); } - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( value.toString() ) ); return QDateTime(); } } @@ -292,7 +300,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( value.toString() ) ); return QDate(); } } @@ -306,7 +315,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( value.toString() ) ); return QTime(); } } @@ -324,7 +334,7 @@ class CORE_EXPORT QgsExpressionUtils return inter; } // If we get here then we can't convert so we just error and return invalid. - if ( report_error ) + if ( report_error && parent ) parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to interval" ).arg( value.toString() ) ); return QgsInterval(); @@ -332,12 +342,16 @@ class CORE_EXPORT QgsExpressionUtils static QgsGradientColorRamp getRamp( const QVariant &value, QgsExpression *parent, bool report_error = false ); - static QgsGeometry getGeometry( const QVariant &value, QgsExpression *parent ) + static QgsGeometry getGeometry( const QVariant &value, QgsExpression *parent, bool tolerant = false ) { + if ( value.userType() == qMetaTypeId< QgsReferencedGeometry>() ) + return value.value(); + if ( value.userType() == qMetaTypeId< QgsGeometry>() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); + if ( !tolerant && parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); return QgsGeometry(); } @@ -346,7 +360,8 @@ class CORE_EXPORT QgsExpressionUtils if ( value.userType() == qMetaTypeId() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to feature" ) ); + if ( parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to feature" ) ); return 0; } @@ -355,7 +370,8 @@ class CORE_EXPORT QgsExpressionUtils if ( value.canConvert() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to node" ) ); + if ( parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to node" ) ); return nullptr; } @@ -403,7 +419,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to array" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to array" ).arg( value.toString() ) ); return QVariantList(); } } @@ -416,7 +433,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to map" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to map" ).arg( value.toString() ) ); return QVariantMap(); } } diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index 419d9cd20ad6..a0e473970a7e 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -1086,6 +1086,40 @@ std::tuple, std::unique_ptr > QgsLineString: return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) ); } +QVector QgsLineString::splitToDisjointXYParts() const +{ + const double *allPointsX = xData(); + const double *allPointsY = yData(); + size_t allPointsCount = numPoints(); + QVector partX; + QVector partY; + QSet partPointSet; + + QVector disjointParts; + for ( size_t i = 0; i < allPointsCount; i++ ) + { + const QgsPointXY point( *allPointsX++, *allPointsY++ ); + if ( partPointSet.contains( point ) ) + { + // This point is used multiple times, cut the curve and add the + // current part + disjointParts.push_back( new QgsLineString( partX, partY ) ); + // Now start a new part containing the last line + partX = { partX.last() }; + partY = { partY.last() }; + partPointSet = { QgsPointXY( partX[0], partY[0] ) }; + } + partX.push_back( point.x() ); + partY.push_back( point.y() ); + partPointSet.insert( point ); + } + // Add the last part (if we didn't stop by closing the loop) + if ( partX.size() > 1 || disjointParts.size() == 0 ) + disjointParts.push_back( new QgsLineString( partX, partY ) ); + + return disjointParts; +} + double QgsLineString::length3D() const { if ( is3D() ) diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index deca218b11f8..6d02dca98746 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -991,6 +991,16 @@ class CORE_EXPORT QgsLineString: public QgsCurve std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const final; #endif + /** + * Divides the linestring into parts that don't share any points or lines. + * + * This method throws away Z and M coordinates. + * + * The ownership of returned pointers is transferred to the caller. + * \since QGIS 3.40 + */ + QVector splitToDisjointXYParts() const SIP_FACTORY; + /** * Returns the length in 3D world of the line string. * If it is not a 3D line string, return its 2D length. diff --git a/src/core/pointcloud/qgspointclouddataprovider.cpp b/src/core/pointcloud/qgspointclouddataprovider.cpp index 63de643c82f3..994935d2b0d9 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.cpp +++ b/src/core/pointcloud/qgspointclouddataprovider.cpp @@ -311,13 +311,36 @@ QVector QgsPointCloudDataProvider::identify( double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange, int pointsLimit ) +{ + QVector acceptedPoints; + + // Try sub-indexes first + for ( QgsPointCloudSubIndex &subidx : subIndexes() ) + { + // Check if the sub-index is relevant and if it is loaded. We shouldn't + // need to identify points in unloaded indices. + if ( !subidx.index() + || ( !subidx.zRange().overlaps( extentZRange ) ) + || !subidx.polygonBounds().intersects( extentGeometry ) ) + continue; + acceptedPoints.append( identify( subidx.index(), maxError, extentGeometry, extentZRange, pointsLimit ) ); + } + + // Then look at main index + acceptedPoints.append( identify( index(), maxError, extentGeometry, extentZRange, pointsLimit ) ); + + return acceptedPoints; +} + +QVector QgsPointCloudDataProvider::identify( + QgsPointCloudIndex *index, double maxError, + const QgsGeometry &extentGeometry, + const QgsDoubleRange &extentZRange, int pointsLimit ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS QVector acceptedPoints; - QgsPointCloudIndex *index = this->index(); - if ( !index || !index->isValid() ) return acceptedPoints; diff --git a/src/core/pointcloud/qgspointclouddataprovider.h b/src/core/pointcloud/qgspointclouddataprovider.h index 158d4736461e..94fdf6f6ca03 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.h +++ b/src/core/pointcloud/qgspointclouddataprovider.h @@ -385,6 +385,9 @@ class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider //! String used to define a subset of the layer QString mSubsetString; + //! Identify in a specific index (used for sub-indexes) + QVector identify( QgsPointCloudIndex *index, double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange, int pointsLimit ) SIP_SKIP ; + private: QVector traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxError, double nodeError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange ); diff --git a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp index 420a3eb6e917..9982c4f1d325 100644 --- a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp +++ b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp @@ -389,12 +389,23 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera // this is not AT ALL thread safe, but it's what QgsPointCloudLayerRenderer does ! // TODO: fix when QgsPointCloudLayerRenderer is made thread safe to use same approach - QgsPointCloudIndex *pc = mLayer->dataProvider()->index(); - if ( !pc || !pc->isValid() ) + QVector indexes; + QgsPointCloudIndex *mainIndex = mLayer->dataProvider()->index(); + if ( mainIndex && mainIndex->isValid() ) + indexes.append( mainIndex ); + + // Gather all relevant sub-indexes + const QgsRectangle profileCurveBbox = mProfileCurve->boundingBox(); + for ( const QgsPointCloudSubIndex &subidx : mLayer->dataProvider()->subIndexes() ) { - return false; + QgsPointCloudIndex *index = subidx.index(); + if ( index && index->isValid() && subidx.polygonBounds().intersects( profileCurveBbox ) ) + indexes.append( subidx.index() ); } + if ( indexes.empty() ) + return false; + const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 ); const double endDistance = context.distanceRange().upper(); @@ -433,9 +444,13 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera mSearchGeometryInLayerCrsGeometryEngine->prepareGeometry(); mMaxSearchExtentInLayerCrs = mSearchGeometryInLayerCrs->boundingBox(); - const IndexedPointCloudNode root = pc->root(); - double maximumErrorPixels = context.convertDistanceToPixels( mMaximumScreenError, mMaximumScreenErrorUnit ); + if ( maximumErrorPixels < 0.0 ) + { + QgsDebugError( QStringLiteral( "Invalid maximum error in pixels" ) ); + return false; + } + const double toleranceInPixels = context.convertDistanceToPixels( mTolerance, Qgis::RenderUnit::MapUnits ); // ensure that the maximum error is compatible with the tolerance size -- otherwise if the tolerance size // is much smaller than the maximum error, we don't dig deep enough into the point cloud nodes to find @@ -444,42 +459,6 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera if ( toleranceInPixels / 4 < maximumErrorPixels ) maximumErrorPixels = toleranceInPixels / 4; - const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); - QgsRectangle rootNodeExtentInCurveCrs; - try - { - QgsCoordinateTransform extentTransform = mLayerToTargetTransform; - extentTransform.setBallparkTransformsAreAppropriate( true ); - rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); - } - catch ( QgsCsException & ) - { - QgsDebugError( QStringLiteral( "Could not transform node extent to curve CRS" ) ); - rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords; - } - - const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc->span(); // in curve coords - - const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel(); - if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumErrorPixels < 0.0 ) ) - { - QgsDebugError( QStringLiteral( "invalid screen error" ) ); - return false; - } - 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; - - mResults = std::make_unique< QgsPointCloudLayerProfileResults >(); - mResults->copyPropertiesFromGenerator( this ); - mResults->mMaxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates; - QgsPointCloudRequest request; QgsPointCloudAttributeCollection attributes; attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) ); @@ -515,22 +494,75 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera request.setAttributes( attributes ); - switch ( pc->accessType() ) + mResults = std::make_unique< QgsPointCloudLayerProfileResults >(); + mResults->copyPropertiesFromGenerator( this ); + mResults->mMaxErrorInLayerCoordinates = 0; + + QgsCoordinateTransform extentTransform = mLayerToTargetTransform; + extentTransform.setBallparkTransformsAreAppropriate( true ); + + const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel(); + if ( mapUnitsPerPixel < 0.0 ) + { + QgsDebugError( QStringLiteral( "Invalid map units per pixel ratio" ) ); + return false; + } + + for ( QgsPointCloudIndex *pc : std::as_const( indexes ) ) { - case QgsPointCloudIndex::AccessType::Local: + const IndexedPointCloudNode root = pc->root(); + const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); + QgsRectangle rootNodeExtentInCurveCrs; + try { - visitNodesSync( nodes, pc, request, context.elevationRange() ); - break; + rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); } - case QgsPointCloudIndex::AccessType::Remote: + catch ( QgsCsException & ) { - visitNodesAsync( nodes, pc, request, context.elevationRange() ); - break; + QgsDebugError( QStringLiteral( "Could not transform node extent to curve CRS" ) ); + rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords; + } + + const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc->span(); // in curve coords + if ( rootErrorInMapCoordinates < 0.0 ) + { + QgsDebugError( QStringLiteral( "Invalid root node error" ) ); + return false; + } + + double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels + const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); + + const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span(); + const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel; + + mResults->mMaxErrorInLayerCoordinates = std::max( + mResults->mMaxErrorInLayerCoordinates, + maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates ); + + switch ( pc->accessType() ) + { + case QgsPointCloudIndex::AccessType::Local: + { + visitNodesSync( nodes, pc, request, context.elevationRange() ); + break; + } + case QgsPointCloudIndex::AccessType::Remote: + { + visitNodesAsync( nodes, pc, request, context.elevationRange() ); + break; + } } + + if ( mFeedback->isCanceled() ) + return false; } - if ( mFeedback->isCanceled() ) + if ( mGatheredPoints.empty() ) + { + mResults = nullptr; return false; + } // convert x/y values back to distance/height values diff --git a/src/core/pointcloud/qgspointcloudrenderer.cpp b/src/core/pointcloud/qgspointcloudrenderer.cpp index 4815f2387c43..83204c24200e 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudrenderer.cpp @@ -265,48 +265,33 @@ QVector QgsPointCloudRenderer::identify( QgsPointCloudLayer *layer, { QVector selectedPoints; - QgsPointCloudIndex *index = layer->dataProvider()->index(); - - if ( !index || !index->isValid() ) - return selectedPoints; - - const IndexedPointCloudNode root = index->root(); - const double maxErrorPixels = renderContext.convertToPainterUnits( maximumScreenError(), maximumScreenErrorUnit() );// in pixels - const QgsRectangle rootNodeExtentLayerCoords = index->nodeMapExtent( root ); - QgsRectangle rootNodeExtentMapCoords; + const QgsRectangle layerExtentLayerCoords = layer->dataProvider()->extent(); + QgsRectangle layerExtentMapCoords = layerExtentLayerCoords; if ( !renderContext.coordinateTransform().isShortCircuited() ) { try { QgsCoordinateTransform extentTransform = renderContext.coordinateTransform(); extentTransform.setBallparkTransformsAreAppropriate( true ); - rootNodeExtentMapCoords = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); + layerExtentMapCoords = extentTransform.transformBoundingBox( layerExtentLayerCoords ); } catch ( QgsCsException & ) { QgsDebugError( QStringLiteral( "Could not transform node extent to map CRS" ) ); - rootNodeExtentMapCoords = rootNodeExtentLayerCoords; } } - else - { - rootNodeExtentMapCoords = rootNodeExtentLayerCoords; - } - - const double rootErrorInMapCoordinates = rootNodeExtentMapCoords.width() / index->span(); - const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / index->span(); const double mapUnitsPerPixel = renderContext.mapToPixel().mapUnitsPerPixel(); - if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maxErrorPixels < 0.0 ) ) + if ( ( mapUnitsPerPixel < 0.0 ) || ( maxErrorPixels < 0.0 ) ) { QgsDebugError( QStringLiteral( "invalid screen error" ) ); return selectedPoints; } const double maxErrorInMapCoordinates = maxErrorPixels * mapUnitsPerPixel; - const double maxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates; + const double maxErrorInLayerCoordinates = maxErrorInMapCoordinates * layerExtentLayerCoords.width() / layerExtentMapCoords.width(); QgsGeometry selectionGeometry = geometry; if ( geometry.type() == Qgis::GeometryType::Point ) diff --git a/src/core/qgsmultirenderchecker.cpp b/src/core/qgsmultirenderchecker.cpp index 7dbab9950cfb..b446bc90ec63 100644 --- a/src/core/qgsmultirenderchecker.cpp +++ b/src/core/qgsmultirenderchecker.cpp @@ -78,13 +78,11 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma // we can only report one diff image, so just use the first QString diffImageFile; + QMap< QString, int > variantMismatchCount; + QMap< QString, int > variantSize; + for ( const QString &suffix : std::as_const( subDirs ) ) { - if ( subDirs.count() > 1 ) - { - qDebug() << "Checking subdir " << suffix; - } - bool result; QgsRenderChecker checker; checker.enableDashBuffering( true ); checker.setColorTolerance( mColorTolerance ); @@ -95,14 +93,15 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma checker.setMapSettings( mMapSettings ); checker.setExpectFail( mExpectFail ); + bool result = false; if ( !mRenderedImage.isNull() ) { checker.setRenderedImage( mRenderedImage ); - result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage ); + result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage | QgsRenderChecker::Flag::Silent ); } else { - result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage ); + result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage | QgsRenderChecker::Flag::Silent ); mRenderedImage = checker.renderedImage(); } @@ -120,6 +119,11 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma { diffImageFile = checker.mDiffImageFile; } + if ( !mResult ) + { + variantMismatchCount.insert( suffix, checker.mismatchCount() ); + variantSize.insert( suffix, checker.matchTarget() ); + } } if ( !mResult && !mExpectFail && mIsCiRun ) @@ -148,6 +152,17 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma if ( !mResult && !mExpectFail ) { + for ( auto it = variantMismatchCount.constBegin(); it != variantMismatchCount.constEnd(); it++ ) + { + if ( subDirs.size() > 1 ) + { + qDebug() << QStringLiteral( "Variant %1: %2/%3 pixels mismatched (%4 allowed)" ).arg( it.key() ).arg( it.value() ).arg( variantSize.value( it.key() ) ).arg( mismatchCount ); + } + else + { + qDebug() << QStringLiteral( "%1/%2 pixels mismatched (%4 allowed)" ).arg( it.value() ).arg( variantSize.value( it.key() ) ).arg( mismatchCount ); + } + } const QDir reportDir = QgsRenderChecker::testReportDir(); if ( !reportDir.exists() ) { diff --git a/src/core/qgsrenderchecker.cpp b/src/core/qgsrenderchecker.cpp index 503a12f9ce43..5d3457b118e1 100644 --- a/src/core/qgsrenderchecker.cpp +++ b/src/core/qgsrenderchecker.cpp @@ -523,7 +523,9 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re // Put the same info to debug too // - if ( expectedImage.width() != myResultImage.width() || expectedImage.height() != myResultImage.height() ) + if ( !flags.testFlag( Flag::Silent ) + && ( expectedImage.width() != myResultImage.width() || expectedImage.height() != myResultImage.height() ) + ) { qDebug( "Expected size: %dw x %dh", expectedImage.width(), expectedImage.height() ); qDebug( "Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() ); @@ -533,7 +535,10 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re if ( mMatchTarget != myPixelCount ) { - qDebug( "Expected image and rendered image for %s are different dimensions", testName.toLocal8Bit().constData() ); + if ( !flags.testFlag( Flag::Silent ) ) + { + qDebug( "Expected image and rendered image for %s are different dimensions", testName.toLocal8Bit().constData() ); + } if ( std::abs( expectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX || std::abs( expectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY ) @@ -690,7 +695,10 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re emitDashMessage( "Rendered Image " + testName + prefix, QgsDartMeasurement::ImagePng, mRenderedImageFile ); emitDashMessage( "Expected Image " + testName + prefix, QgsDartMeasurement::ImagePng, referenceImageFile ); - qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount ); + if ( !flags.testFlag( Flag::Silent ) ) + { + qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount ); + } // //save the diff image to disk diff --git a/src/core/qgsrenderchecker.h b/src/core/qgsrenderchecker.h index f411711d55f1..83b3b783e9e6 100644 --- a/src/core/qgsrenderchecker.h +++ b/src/core/qgsrenderchecker.h @@ -199,6 +199,7 @@ class CORE_EXPORT QgsRenderChecker enum class Flag : int SIP_ENUM_BASETYPE( IntFlag ) { AvoidExportingRenderedImage = 1 << 0, //!< Avoids exporting rendered images to reports + Silent = 1 << 1, //!< Don't output non-critical messages to console \since QGIS 3.40 }; Q_ENUM( Flag ) diff --git a/src/core/stac/qgsstaccatalog.cpp b/src/core/stac/qgsstaccatalog.cpp index 2d2360ef79c0..58ed28e8d438 100644 --- a/src/core/stac/qgsstaccatalog.cpp +++ b/src/core/stac/qgsstaccatalog.cpp @@ -58,7 +58,7 @@ QString QgsStacCatalog::toHtml() const html += QStringLiteral( "
    \n" ); for ( const QString &cc : mConformanceClasses ) { - html += QStringLiteral( "
  • %1
  • \n" ).arg( cc ); + html += QStringLiteral( "
  • %1
  • \n" ).arg( cc ); } html += QStringLiteral( "
\n" ); } diff --git a/src/core/stac/qgsstaccollections.cpp b/src/core/stac/qgsstaccollections.cpp new file mode 100644 index 000000000000..87913d64b68c --- /dev/null +++ b/src/core/stac/qgsstaccollections.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsstaccollections.cpp + --------------------- + begin : October 2024 + copyright : (C) 2024 by Stefanos Natsis + email : uclaros 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 "qgsstaccollections.h" + +QgsStacCollections::QgsStacCollections( const QVector< QgsStacCollection * > collections, const QVector< QgsStacLink > links, int numberMatched ) + : mCollections( collections ) + , mNumberMatched( numberMatched ) +{ + for ( const QgsStacLink &link : links ) + { + if ( link.relation() == QLatin1String( "self" ) || + link.relation() == QLatin1String( "root" ) || + link.relation() == QLatin1String( "next" ) || + link.relation() == QLatin1String( "prev" ) ) + mUrls.insert( link.relation(), link.href() ); + } +} + + +QgsStacCollections::~QgsStacCollections() +{ + qDeleteAll( mCollections ); +} + +QVector QgsStacCollections::collections() const +{ + return mCollections; +} + +QVector QgsStacCollections::takeCollections() +{ + QVector< QgsStacCollection * > cols = mCollections; + mCollections.clear(); // detach + return cols; +} + +int QgsStacCollections::numberReturned() const +{ + return mCollections.size(); +} + +int QgsStacCollections::numberMatched() const +{ + return mNumberMatched; +} + +QUrl QgsStacCollections::url() const +{ + return QUrl( mUrls.value( QStringLiteral( "self" ), QString() ) ); +} + +QUrl QgsStacCollections::rootUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "root" ), QString() ) ); +} + +QUrl QgsStacCollections::nextUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "next" ), QString() ) ); +} + +QUrl QgsStacCollections::prevUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "prev" ), QString() ) ); +} diff --git a/src/core/stac/qgsstaccollections.h b/src/core/stac/qgsstaccollections.h new file mode 100644 index 000000000000..140455c0da21 --- /dev/null +++ b/src/core/stac/qgsstaccollections.h @@ -0,0 +1,101 @@ +/*************************************************************************** + qgsstaccollections.h + --------------------- + begin : October 2024 + copyright : (C) 2024 by Stefanos Natsis + email : uclaros 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 QGSSTACCOLLECTIONS_H +#define QGSSTACCOLLECTIONS_H + +#define SIP_NO_FILE + +#include "qgis_core.h" +#include "qgsstaclink.h" + +#include +#include + +class QgsStacCollection; + +/** + * \ingroup core + * \brief Class for storing a list of STAC Collections. + * This is typically used to store the data returned by STAC API /collections endpoint + * \note Not available in python bindings + * \since QGIS 3.40 + */ +class CORE_EXPORT QgsStacCollections +{ + public: + //! Default constructor deleted, use the variant with required parameters + QgsStacCollections() = delete; + + /** + * Constructs a valid list of collections, + * \param collections The STAC Collections to be stored, ownership is transferred + * \param links A list of references to other documents. + * \param numberMatched The total number of collections in the parent catalog, collection or total matching results from a STAC API endpoint + * \note ownership of \a collections is transferred. Collections will be deleted when object is destroyed. + */ + QgsStacCollections( const QVector< QgsStacCollection * > collections, const QVector< QgsStacLink > links, int numberMatched = -1 ); + + //! Destructor + ~QgsStacCollections(); + + /** + * Returns the collections + * Ownership is not transferred + */ + QVector< QgsStacCollection * > collections() const; + + /** + * Returns the collections + * Caller takes ownership of the returned collections + */ + QVector< QgsStacCollection * > takeCollections(); + + //! Returns the number of returned collections + int numberReturned() const; + + /** + * Returns the total number of available collections + * If this information was not available by the STAC server, -1 is returned + */ + int numberMatched() const; + + /** + * Returns the url of the collections' "self" link + */ + QUrl url() const; + + /** + * Returns the url of the collections' "root" link + */ + QUrl rootUrl() const; + + /** + * Returns the url of the collections' "next" link + */ + QUrl nextUrl() const; + + /** + * Returns the url of the collections' "prev" link + */ + QUrl prevUrl() const; + + private: + QVector< QgsStacCollection * > mCollections; + QMap< QString, QString > mUrls; + int mNumberMatched = -1; +}; + +#endif // QGSSTACCOLLECTIONS_H diff --git a/src/core/stac/qgsstaccontroller.cpp b/src/core/stac/qgsstaccontroller.cpp index c7c0e843247f..aab1893a66ae 100644 --- a/src/core/stac/qgsstaccontroller.cpp +++ b/src/core/stac/qgsstaccontroller.cpp @@ -227,6 +227,30 @@ QgsStacItemCollection *QgsStacController::fetchItemCollection( const QUrl &url, return ic; } +QgsStacCollections *QgsStacController::fetchCollections( const QUrl &url, QString *error ) +{ + QgsNetworkReplyContent content = fetchBlocking( url ); + + if ( content.error() != QNetworkReply::NoError ) + { + if ( error ) + *error = content.errorString(); + + return nullptr; + } + + const QByteArray data = content.content(); + + QgsStacParser parser; + parser.setData( data ); + QgsStacCollections *col = parser.collections(); + + if ( error ) + *error = parser.error(); + + return col; +} + QgsNetworkReplyContent QgsStacController::fetchBlocking( const QUrl &url ) { QNetworkRequest req( url ); diff --git a/src/core/stac/qgsstaccontroller.h b/src/core/stac/qgsstaccontroller.h index 78d10c732401..10039133fd33 100644 --- a/src/core/stac/qgsstaccontroller.h +++ b/src/core/stac/qgsstaccontroller.h @@ -28,6 +28,7 @@ class QgsStacObject; class QgsStacCatalog; class QgsStacCollection; +class QgsStacCollections; class QgsStacItem; class QgsStacItemCollection; class QNetworkReply; @@ -83,6 +84,13 @@ class CORE_EXPORT QgsStacController : public QObject */ QgsStacItemCollection *fetchItemCollection( const QUrl &url, QString *error = nullptr ); + /** + * Fetches collections from \a url using a blocking network request. + * An optional \a error parameter will be populated with any network error information. + * The caller takes ownership of the returned feature collection + */ + QgsStacCollections *fetchCollections( const QUrl &url, QString *error = nullptr ); + /** * Initiates an asynchronous request for a STAC object using the \a url * and returns an associated request id. diff --git a/src/core/stac/qgsstacdataitems.cpp b/src/core/stac/qgsstacdataitems.cpp index b0dc8d7ef52c..1977ea9af7c5 100644 --- a/src/core/stac/qgsstacdataitems.cpp +++ b/src/core/stac/qgsstacdataitems.cpp @@ -19,6 +19,8 @@ #include "qgsstaccatalog.h" #include "qgsstacitem.h" #include "qgsstacitemcollection.h" +#include "qgsstaccollection.h" +#include "qgsstaccollections.h" constexpr int MAX_DISPLAYED_ITEMS = 20; @@ -332,6 +334,7 @@ QVector QgsStacCatalogItem::createChildren() // treat catalog/collection as static if it does not have a /items endpoint bool hasItemsEndpoint = false; + bool hasCollectionsEndpoint = false; if ( supportsApi ) { for ( const auto &link : links ) @@ -339,8 +342,14 @@ QVector QgsStacCatalogItem::createChildren() if ( link.relation() == QLatin1String( "items" ) ) { hasItemsEndpoint = true; - break; } + else if ( link.relation() == QLatin1String( "data" ) && + link.href().endsWith( QLatin1String( "/collections" ) ) ) + { + hasCollectionsEndpoint = true; + } + if ( hasItemsEndpoint && hasCollectionsEndpoint ) + break; } } @@ -353,12 +362,30 @@ QVector QgsStacCatalogItem::createChildren() link.relation() == QLatin1String( "collection" ) ) continue; - if ( link.relation() == QLatin1String( "child" ) ) + if ( link.relation() == QLatin1String( "child" ) && + !hasCollectionsEndpoint ) { // may be either catalog or collection QgsStacCatalogItem *c = new QgsStacCatalogItem( this, link.title(), link.href() ); contents.append( c ); } + else if ( link.relation() == QLatin1String( "data" ) && + link.href().endsWith( QLatin1String( "/collections" ) ) ) + { + // use /collections api + QString error; + std::unique_ptr< QgsStacCollections > cols( controller->fetchCollections( link.href(), &error ) ); + if ( cols ) + { + contents.append( createCollections( cols->takeCollections() ) ); + itemsCount = cols->numberMatched(); + } + else + { + // collection fetching failed + contents.append( new QgsErrorItem( this, error, path() + QStringLiteral( "/error" ) ) ); + } + } else if ( link.relation() == QLatin1String( "item" ) && !hasItemsEndpoint ) { @@ -370,8 +397,7 @@ QVector QgsStacCatalogItem::createChildren() QgsStacItemItem *i = new QgsStacItemItem( this, link.title(), link.href() ); contents.append( i ); } - else if ( link.relation() == QLatin1String( "items" ) && - hasItemsEndpoint ) + else if ( link.relation() == QLatin1String( "items" ) ) { // stac api items (ogcapi features) QString error; @@ -470,6 +496,24 @@ QVector< QgsDataItem * > QgsStacCatalogItem::createItems( const QVector QgsStacCatalogItem::createCollections( const QVector collections ) +{ + QVector< QgsDataItem * > contents; + contents.reserve( collections.size() ); + for ( QgsStacCollection *col : collections ) + { + if ( !col ) + continue; + + const QString name = col->title().isEmpty() ? col->id() : col->title(); + + QgsStacCatalogItem *i = new QgsStacCatalogItem( this, name, col->url() ); + i->setStacCatalog( col ); + contents.append( i ); + } + return contents; +} + void QgsStacCatalogItem::fetchMoreChildren() { if ( mFetchMoreUrl.isEmpty() ) diff --git a/src/core/stac/qgsstacdataitems.h b/src/core/stac/qgsstacdataitems.h index f7676bb60ef6..658a0cdd3289 100644 --- a/src/core/stac/qgsstacdataitems.h +++ b/src/core/stac/qgsstacdataitems.h @@ -25,6 +25,7 @@ #include class QgsStacController; +class QgsStacCollection; ///@cond PRIVATE #define SIP_NO_FILE @@ -108,6 +109,7 @@ class CORE_EXPORT QgsStacCatalogItem : public QgsDataCollectionItem private: //! takes ownership QVector< QgsDataItem * > createItems( const QVector items ); + QVector< QgsDataItem * > createCollections( const QVector collections ); //! The URI QString mUri; diff --git a/src/core/stac/qgsstacitemcollection.h b/src/core/stac/qgsstacitemcollection.h index a0e8adf732de..141c8f593a1a 100644 --- a/src/core/stac/qgsstacitemcollection.h +++ b/src/core/stac/qgsstacitemcollection.h @@ -19,7 +19,12 @@ #define SIP_NO_FILE #include "qgis_core.h" -#include "qgsstacitem.h" +#include "qgsstaclink.h" + +#include +#include + +class QgsStacItem; /** * \ingroup core diff --git a/src/core/stac/qgsstacparser.cpp b/src/core/stac/qgsstacparser.cpp index 790dcbe20adc..be0c19bbb87b 100644 --- a/src/core/stac/qgsstacparser.cpp +++ b/src/core/stac/qgsstacparser.cpp @@ -14,6 +14,11 @@ ***************************************************************************/ #include "qgsstacparser.h" +#include "qgsstacitem.h" +#include "qgsstaccatalog.h" +#include "qgsstaccollection.h" +#include "qgsstaccollections.h" +#include "qgsstacitemcollection.h" #include "qgsjsonutils.h" #include "qgslogger.h" @@ -65,43 +70,50 @@ QString QgsStacParser::error() const } QgsStacCatalog *QgsStacParser::catalog() +{ + return parseCatalog( mData ); +} + +QgsStacCatalog *QgsStacParser::parseCatalog( const nlohmann::json &data ) { try { - const QString ver( QString::fromStdString( mData.at( "stac_version" ) ) ); + const QString ver( QString::fromStdString( data.at( "stac_version" ) ) ); if ( !isSupportedStacVersion( ver ) ) { mError = QStringLiteral( "Unsupported STAC version: %1" ).arg( ver ); return nullptr; } - const QString id( QString::fromStdString( mData.at( "id" ) ) ); - const QString description( QString::fromStdString( mData.at( "description" ) ) ); + const QString id( QString::fromStdString( data.at( "id" ) ) ); + const QString description( getString( data.at( "description" ) ) ); - QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + QVector< QgsStacLink > links = parseLinks( data.at( "links" ) ); std::unique_ptr< QgsStacCatalog > catalog = std::make_unique< QgsStacCatalog >( id, ver, description, links ); - if ( mData.contains( "title" ) ) - catalog->setTitle( QString::fromStdString( mData["title"] ) ); + if ( data.contains( "title" ) ) + catalog->setTitle( getString( data["title"] ) ); - if ( mData.contains( "conformsTo" ) ) + if ( data.contains( "conformsTo" ) ) { - for ( const auto &conformanceClass : mData["conformsTo"] ) + for ( const auto &conformanceClass : data["conformsTo"] ) { - catalog->addConformanceClass( QString::fromStdString( conformanceClass ) ); + if ( conformanceClass.is_string() ) + catalog->addConformanceClass( QString::fromStdString( conformanceClass ) ); } } - if ( mData.contains( "stac_extensions" ) ) + if ( data.contains( "stac_extensions" ) ) { QStringList extensions; - for ( const auto &extension : mData["stac_extensions"] ) + for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } catalog->setStacExtensions( extensions ); } @@ -117,23 +129,28 @@ QgsStacCatalog *QgsStacParser::catalog() } QgsStacCollection *QgsStacParser::collection() +{ + return parseCollection( mData ); +} + +QgsStacCollection *QgsStacParser::parseCollection( const nlohmann::json &data ) { try { - const QString ver( QString::fromStdString( mData.at( "stac_version" ) ) ); + const QString ver( QString::fromStdString( data.at( "stac_version" ) ) ); if ( !isSupportedStacVersion( ver ) ) { mError = QStringLiteral( "Unsupported STAC version: %1" ).arg( ver ); return nullptr; } - const QString id( QString::fromStdString( mData.at( "id" ) ) ); - const QString description( QString::fromStdString( mData.at( "description" ) ) ); - const QString license( QString::fromStdString( mData.at( "license" ) ) ); + const QString id( QString::fromStdString( data.at( "id" ) ) ); + const QString description( getString( data.at( "description" ) ) ); + const QString license( getString( data.at( "license" ) ) ); QgsStacExtent stacExtent; int totalExtents = 0; - for ( const auto &e : mData.at( "extent" ).at( "spatial" ).at( "bbox" ) ) + for ( const auto &e : data.at( "extent" ).at( "spatial" ).at( "bbox" ) ) { QgsBox3D extent; if ( e.size() == 4 ) @@ -167,7 +184,7 @@ QgsStacCollection *QgsStacParser::collection() } totalExtents = 0; - for ( const auto &e : mData.at( "extent" ).at( "temporal" ).at( "interval" ) ) + for ( const auto &e : data.at( "extent" ).at( "temporal" ).at( "interval" ) ) { if ( !e.is_array() || e.size() != 2 ) @@ -176,8 +193,8 @@ QgsStacCollection *QgsStacParser::collection() QgsDebugError( mError ); return nullptr; } - const QDateTime start = e[0].is_null() ? QDateTime() : QDateTime::fromString( QString::fromStdString( e[0] ), Qt::ISODateWithMs ); - const QDateTime end = e[1].is_null() ? QDateTime() : QDateTime::fromString( QString::fromStdString( e[1] ), Qt::ISODateWithMs ); + const QDateTime start = QDateTime::fromString( getString( e[0] ), Qt::ISODateWithMs ); + const QDateTime end = QDateTime::fromString( getString( e[1] ), Qt::ISODateWithMs ); if ( ++totalExtents == 1 ) stacExtent.setTemporalExtent( QgsDateTimeRange( start, end ) ); @@ -185,7 +202,7 @@ QgsStacCollection *QgsStacParser::collection() stacExtent.addDetailedTemporalExtent( QgsDateTimeRange( start, end ) ); } - QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + QVector< QgsStacLink > links = parseLinks( data.at( "links" ) ); std::unique_ptr< QgsStacCollection > collection = std::make_unique< QgsStacCollection >( id, ver, @@ -194,33 +211,35 @@ QgsStacCollection *QgsStacParser::collection() license, stacExtent ); - if ( mData.contains( "title" ) ) - collection->setTitle( QString::fromStdString( mData["title"] ) ); + if ( data.contains( "title" ) ) + collection->setTitle( getString( data["title"] ) ); - if ( mData.contains( "stac_extensions" ) ) + if ( data.contains( "stac_extensions" ) ) { QStringList extensions; - for ( const auto &extension : mData["stac_extensions"] ) + for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } collection->setStacExtensions( extensions ); } - if ( mData.contains( "keywords" ) ) + if ( data.contains( "keywords" ) ) { QStringList keywords; - for ( const auto &kw : mData["keywords"] ) + for ( const auto &kw : data["keywords"] ) { - keywords.append( QString::fromStdString( kw ) ); + if ( kw.is_string() ) + keywords.append( QString::fromStdString( kw ) ); } collection->setKeywords( keywords ); } - if ( mData.contains( "providers" ) ) + if ( data.contains( "providers" ) ) { QVector< QgsStacProvider > providers; - for ( const auto &p : mData["providers"] ) + for ( const auto &p : data["providers"] ) { if ( !p.contains( "name" ) || ( p.contains( "roles" ) && !p["roles"].is_array() ) ) @@ -234,13 +253,14 @@ QgsStacCollection *QgsStacParser::collection() { for ( const auto &role : p["roles"] ) { - roles.append( QString::fromStdString( role ) ); + if ( role.is_string() ) + roles.append( QString::fromStdString( role ) ); } } const QgsStacProvider provider( QString::fromStdString( p["name"] ), - p.contains( "description" ) ? QString::fromStdString( p["description"] ) : QString(), + p.contains( "description" ) ? getString( p["description"] ) : QString(), roles, - p.contains( "url" ) ? QString::fromStdString( p["url"] ) : QString() ); + p.contains( "url" ) ? getString( p["url"] ) : QString() ); @@ -249,15 +269,15 @@ QgsStacCollection *QgsStacParser::collection() collection->setProviders( providers ); } - if ( mData.contains( "summaries" ) ) + if ( data.contains( "summaries" ) ) { - const QVariant summ = QgsJsonUtils::jsonToVariant( mData["summaries"] ); + const QVariant summ = QgsJsonUtils::jsonToVariant( data["summaries"] ); collection->setSummaries( summ.toMap() ); } - if ( mData.contains( "assets" ) ) + if ( data.contains( "assets" ) ) { - QMap< QString, QgsStacAsset > assets = parseAssets( mData["assets"] ); + QMap< QString, QgsStacAsset > assets = parseAssets( data["assets"] ); collection->setAssets( assets ); } @@ -350,13 +370,14 @@ QgsStacItem *QgsStacParser::parseItem( const nlohmann::json &data ) QStringList extensions; for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } item->setStacExtensions( extensions ); } if ( data.contains( "collection" ) ) - item->setCollection( QString::fromStdString( data["collection"] ) ); + item->setCollection( getString( data["collection"] ) ); return item.release(); } @@ -380,8 +401,8 @@ QVector QgsStacParser::parseLinks( const json &data ) const QgsStacLink l( linkUrl.toString(), QString::fromStdString( link.at( "rel" ) ), - link.contains( "type" ) ? QString::fromStdString( link["type"] ) : QString(), - link.contains( "title" ) ? QString::fromStdString( link["title"] ) : QString() ); + link.contains( "type" ) ? getString( link["type"] ) : QString(), + link.contains( "title" ) ? getString( link["title"] ) : QString() ); links.append( l ); } return links; @@ -398,9 +419,9 @@ QMap QgsStacParser::parseAssets( const json &data ) assetUrl = mBaseUrl.resolved( assetUrl ); const QgsStacAsset a( assetUrl.toString(), - value.contains( "title" ) ? QString::fromStdString( value["title"] ) : QString(), - value.contains( "description" ) ? QString::fromStdString( value["description"] ) : QString(), - value.contains( "type" ) ? QString::fromStdString( value["type"] ) : QString(), + value.contains( "title" ) ? getString( value["title"] ) : QString(), + value.contains( "description" ) ? getString( value["description"] ) : QString(), + value.contains( "type" ) ? getString( value["type"] ) : QString(), value.contains( "roles" ) ? QgsJsonUtils::jsonToVariant( value["roles"] ).toStringList() : QStringList() ); assets.insert( QString::fromStdString( asset.key() ), a ); } @@ -438,6 +459,11 @@ bool QgsStacParser::isSupportedStacVersion( const QString &version ) return true; } +QString QgsStacParser::getString( const nlohmann::json &data ) +{ + return data.is_null() ? QString() : QString::fromStdString( data ); +} + QgsStacItemCollection *QgsStacParser::itemCollection() { try @@ -464,3 +490,30 @@ QgsStacItemCollection *QgsStacParser::itemCollection() return nullptr; } } + +QgsStacCollections *QgsStacParser::collections() +{ + try + { + QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + + QVector< QgsStacCollection * > cols; + cols.reserve( static_cast( mData.at( "collections" ).size() ) ); + for ( auto &col : mData.at( "collections" ) ) + { + QgsStacCollection *c = parseCollection( col ); + if ( c ) + cols.append( c ); + } + + const int numberMatched = mData.contains( "numberMatched" ) ? mData["numberMatched"].get() : -1; + + return new QgsStacCollections( cols, links, numberMatched ); + } + catch ( nlohmann::json::exception &ex ) + { + mError = QStringLiteral( "Error parsing ItemCollection" ); + QgsDebugError( QStringLiteral( "Error parsing ItemCollection: %1" ).arg( ex.what() ) ); + return nullptr; + } +} diff --git a/src/core/stac/qgsstacparser.h b/src/core/stac/qgsstacparser.h index 9ea77cd4dd17..ef5082cfc868 100644 --- a/src/core/stac/qgsstacparser.h +++ b/src/core/stac/qgsstacparser.h @@ -20,10 +20,15 @@ #include -#include "qgsstacitem.h" -#include "qgsstaccollection.h" -#include "qgsstaccatalog.h" -#include "qgsstacitemcollection.h" +#include "qgsstacobject.h" +#include "qgsstacasset.h" + +class QgsStacCatalog; +class QgsStacCollection; +class QgsStacCollections; +class QgsStacItem; +class QgsStacItemCollection; + /** * \brief SpatioTemporal Asset Catalog JSON parser @@ -76,6 +81,13 @@ class QgsStacParser */ QgsStacItemCollection *itemCollection(); + /** + * Returns the parsed STAC API Collections + * If parsing failed, NULLPTR is returned + * The caller takes ownership of the returned collections + */ + QgsStacCollections *collections(); + //! Returns the type of the parsed object QgsStacObject::Type type() const; @@ -90,6 +102,8 @@ class QgsStacParser QVector< QgsStacLink > parseLinks( const nlohmann::json &data ); QMap< QString, QgsStacAsset > parseAssets( const nlohmann::json &data ); static bool isSupportedStacVersion( const QString &version ); + //! Returns a QString, treating null elements as empty strings + static QString getString( const nlohmann::json &data ); nlohmann::json mData; QgsStacObject::Type mType = QgsStacObject::Type::Unknown; diff --git a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp index 80c138dff0c4..3546320dd1c7 100644 --- a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp +++ b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgsgeometrygeneratorsymbollayer.h" +#include "qgsexpressionutils.h" #include "qgsgeometry.h" #include "qgsmarkersymbol.h" #include "qgslinesymbol.h" @@ -337,7 +338,8 @@ QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( con generatorScope->setGeometry( drawGeometry ); // step 3 - evaluate the new generated geometry. - QgsGeometry geom = mExpression->evaluate( &expressionContext ).value(); + QVariant value = mExpression->evaluate( &expressionContext ); + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, mExpression.get() ); // step 4 - transform geometry back from target units to painter units geom.transform( painterToTargetUnits.inverted( ) ); @@ -461,8 +463,8 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, Q case Qgis::RenderUnit::MetersInMapUnits: // unsupported, not exposed as an option case Qgis::RenderUnit::Percentage: // unsupported, not exposed as an option { - QgsGeometry geom = mExpression->evaluate( &expressionContext ).value(); - f.setGeometry( coerceToExpectedType( geom ) ); + QVariant value = mExpression->evaluate( &expressionContext ); + f.setGeometry( coerceToExpectedType( QgsExpressionUtils::getGeometry( value, mExpression.get() ) ) ); break; } diff --git a/src/core/textrenderer/qgstextformat.cpp b/src/core/textrenderer/qgstextformat.cpp index 953f5a04309c..09664c7ca8aa 100644 --- a/src/core/textrenderer/qgstextformat.cpp +++ b/src/core/textrenderer/qgstextformat.cpp @@ -214,6 +214,7 @@ void QgsTextFormat::setFont( const QFont &font ) { d->isValid = true; d->textFont = font; + d->originalFontFamily.clear(); } QString QgsTextFormat::namedStyle() const @@ -466,7 +467,8 @@ void QgsTextFormat::readFromLayer( QgsVectorLayer *layer ) { d->isValid = true; QFont appFont = QApplication::font(); - mTextFontFamily = QgsApplication::fontManager()->processFontFamilyName( layer->customProperty( QStringLiteral( "labeling/fontFamily" ), QVariant( appFont.family() ) ).toString() ); + d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( layer->customProperty( QStringLiteral( "labeling/fontFamily" ), QVariant( appFont.family() ) ).toString() ); + mTextFontFamily = d->originalFontFamily; QString fontFamily = mTextFontFamily; if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) ) { @@ -555,7 +557,8 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext else textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) ); QFont appFont = QApplication::font(); - mTextFontFamily = QgsApplication::fontManager()->processFontFamilyName( textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() ) ); + d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() ) ); + mTextFontFamily = d->originalFontFamily; QString fontFamily = mTextFontFamily; const QDomElement familiesElem = textStyleElem.firstChildElement( QStringLiteral( "families" ) ); @@ -749,7 +752,7 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex { // text style QDomElement textStyleElem = doc.createElement( QStringLiteral( "text-style" ) ); - textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), d->textFont.family() ); + textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), !d->originalFontFamily.isEmpty() ? d->originalFontFamily : d->textFont.family() ); QDomElement familiesElem = doc.createElement( QStringLiteral( "families" ) ); for ( const QString &family : std::as_const( d->families ) ) diff --git a/src/core/textrenderer/qgstextrenderer_p.h b/src/core/textrenderer/qgstextrenderer_p.h index 190ae64abd8e..91169f01f66f 100644 --- a/src/core/textrenderer/qgstextrenderer_p.h +++ b/src/core/textrenderer/qgstextrenderer_p.h @@ -264,6 +264,7 @@ class QgsTextSettingsPrivate : public QSharedData QgsTextSettingsPrivate( const QgsTextSettingsPrivate &other ) : QSharedData( other ) , isValid( other.isValid ) + , originalFontFamily( other.originalFontFamily ) , textFont( other.textFont ) , families( other.families ) , textNamedStyle( other.textNamedStyle ) @@ -289,6 +290,8 @@ class QgsTextSettingsPrivate : public QSharedData } bool isValid = false; + + QString originalFontFamily; QFont textFont; QStringList families; QString textNamedStyle; diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.cpp b/src/core/vector/qgsvectorlayerprofilegenerator.cpp index a77d368089bf..17f6cae20a90 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.cpp +++ b/src/core/vector/qgsvectorlayerprofilegenerator.cpp @@ -15,6 +15,7 @@ * * ***************************************************************************/ #include "qgsvectorlayerprofilegenerator.h" +#include "qgsabstractgeometry.h" #include "qgspolyhedralsurface.h" #include "qgsprofilerequest.h" #include "qgscurve.h" @@ -721,6 +722,72 @@ bool QgsVectorLayerProfileGenerator::generateProfile( const QgsProfileGeneration if ( !mProfileCurve || mFeedback->isCanceled() ) return false; + if ( QgsLineString *profileLine = + qgsgeometry_cast( mProfileCurve.get() ) ) + { + // The profile generation code can't deal with curves that enter a single + // point multiple times. We handle this for line strings by splitting them + // into multiple parts, each with no repeated points, and computing the + // profile for each by itself. + std::unique_ptr< QgsCurve > origCurve = std::move( mProfileCurve ); + std::unique_ptr< QgsVectorLayerProfileResults > totalResults; + double distanceProcessed = 0; + + QVector disjointParts = profileLine->splitToDisjointXYParts(); + for ( int i = 0; i < disjointParts.size(); i++ ) + { + mProfileCurve.reset( disjointParts[i] ); + if ( !generateProfileInner() ) + { + mProfileCurve = std::move( origCurve ); + + // Free the rest of the parts + for ( int j = i + 1; j < disjointParts.size(); j++ ) + delete disjointParts[j]; + + return false; + } + + if ( !totalResults ) + // Use the first result set as a base + totalResults.reset( mResults.release() ); + else + { + // Merge the results, shifting them by distanceProcessed + totalResults->mRawPoints.append( mResults->mRawPoints ); + totalResults->minZ = std::min( totalResults->minZ, mResults->minZ ); + totalResults->maxZ = std::max( totalResults->maxZ, mResults->maxZ ); + for ( auto it = mResults->mDistanceToHeightMap.constKeyValueBegin(); + it != mResults->mDistanceToHeightMap.constKeyValueEnd(); + ++it ) + { + totalResults->mDistanceToHeightMap[it->first + distanceProcessed] = it->second; + } + for ( auto it = mResults->features.constKeyValueBegin(); + it != mResults->features.constKeyValueEnd(); + ++it ) + { + for ( QgsVectorLayerProfileResults::Feature feature : it->second ) + { + feature.crossSectionGeometry.translate( distanceProcessed, 0 ); + totalResults->features[it->first].push_back( feature ); + } + } + } + + distanceProcessed += mProfileCurve->length(); + } + + mProfileCurve = std::move( origCurve ); + mResults.reset( totalResults.release() ); + return true; + } + + return generateProfileInner(); +} + +bool QgsVectorLayerProfileGenerator::generateProfileInner( const QgsProfileGenerationContext & ) +{ // we need to transform the profile curve to the vector's CRS mTransformedCurve.reset( mProfileCurve->clone() ); mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext ); diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.h b/src/core/vector/qgsvectorlayerprofilegenerator.h index 17ae6130298c..8bc647801107 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.h +++ b/src/core/vector/qgsvectorlayerprofilegenerator.h @@ -120,6 +120,10 @@ class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileSurf private: + // We may need to split mProfileCurve into multiple parts, this will be + // called for each part. + bool generateProfileInner( const QgsProfileGenerationContext &context = QgsProfileGenerationContext() ); + bool generateProfileForPoints(); bool generateProfileForLines(); bool generateProfileForPolygons(); diff --git a/src/gui/elevation/qgselevationprofilecanvas.cpp b/src/gui/elevation/qgselevationprofilecanvas.cpp index e2addc00caed..cbe6cfe0a775 100644 --- a/src/gui/elevation/qgselevationprofilecanvas.cpp +++ b/src/gui/elevation/qgselevationprofilecanvas.cpp @@ -864,13 +864,7 @@ void QgsElevationProfileCanvas::refresh() if ( !mProject || !profileCurve() ) return; - if ( mCurrentJob ) - { - mPlotItem->setRenderer( nullptr ); - disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished ); - mCurrentJob->deleteLater(); - mCurrentJob = nullptr; - } + cancelJobs(); QgsProfileRequest request( profileCurve()->clone() ); request.setCrs( mCrs ); @@ -1434,7 +1428,7 @@ QVector QgsElevationProfileCanvas::identify( const QR void QgsElevationProfileCanvas::clear() { setProfileCurve( nullptr ); - mPlotItem->setRenderer( nullptr ); + cancelJobs(); mPlotItem->updatePlot(); } diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index c793ecf1de6d..91006795a001 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -68,6 +68,8 @@ #include "qgsprocessingpointcloudexpressionlineedit.h" #include "qgsprocessingrastercalculatorexpressionlineedit.h" #include "qgsunittypes.h" +#include "qgsgeometrywidget.h" + #include #include #include @@ -4427,23 +4429,24 @@ QgsProcessingGeometryParameterDefinitionWidget::QgsProcessingGeometryParameterDe vlayout->addWidget( new QLabel( tr( "Default value" ) ) ); - mDefaultLineEdit = new QLineEdit(); - mDefaultLineEdit->setToolTip( tr( "Geometry as WKT" ) ); - mDefaultLineEdit->setPlaceholderText( tr( "Geometry as WKT" ) ); + mGeometryWidget = new QgsGeometryWidget(); if ( const QgsProcessingParameterGeometry *geometryParam = dynamic_cast( definition ) ) { QgsGeometry g = QgsProcessingParameters::parameterAsGeometry( geometryParam, geometryParam->defaultValueForGui(), context ); if ( !g.isNull() ) - mDefaultLineEdit->setText( g.asWkt() ); + { + mGeometryWidget->setGeometryValue( QgsReferencedGeometry( g, QgsCoordinateReferenceSystem() ) ); + } } - vlayout->addWidget( mDefaultLineEdit ); + vlayout->addWidget( mGeometryWidget ); setLayout( vlayout ); } QgsProcessingParameterDefinition *QgsProcessingGeometryParameterDefinitionWidget::createParameter( const QString &name, const QString &description, Qgis::ProcessingParameterFlags flags ) const { - auto param = std::make_unique< QgsProcessingParameterGeometry >( name, description, mDefaultLineEdit->text() ); + const QgsReferencedGeometry geometry = mGeometryWidget->geometryValue(); + auto param = std::make_unique< QgsProcessingParameterGeometry >( name, description, geometry.isEmpty() ? QVariant() : geometry.asWkt() ); param->setFlags( flags ); return param.release(); } @@ -4462,13 +4465,13 @@ QWidget *QgsProcessingGeometryWidgetWrapper::createWidget() case QgsProcessingGui::Modeler: case QgsProcessingGui::Batch: { - mLineEdit = new QLineEdit(); - mLineEdit->setToolTip( parameterDefinition()->toolTip() ); - connect( mLineEdit, &QLineEdit::textChanged, this, [ = ] + mGeometryWidget = new QgsGeometryWidget(); + mGeometryWidget->setToolTip( parameterDefinition()->toolTip() ); + connect( mGeometryWidget, &QgsGeometryWidget::geometryValueChanged, this, [ = ]( const QgsReferencedGeometry & ) { emit widgetValueHasChanged( this ); } ); - return mLineEdit; + return mGeometryWidget; } } return nullptr; @@ -4476,22 +4479,31 @@ QWidget *QgsProcessingGeometryWidgetWrapper::createWidget() void QgsProcessingGeometryWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) { - if ( mLineEdit ) + if ( mGeometryWidget ) { QgsGeometry g = QgsProcessingParameters::parameterAsGeometry( parameterDefinition(), value, context ); if ( !g.isNull() ) - mLineEdit->setText( g.asWkt() ); + { + mGeometryWidget->setGeometryValue( QgsReferencedGeometry( g, QgsCoordinateReferenceSystem() ) ); + } else - mLineEdit->clear(); + { + mGeometryWidget->clearGeometry(); + } } } QVariant QgsProcessingGeometryWidgetWrapper::widgetValue() const { - if ( mLineEdit ) - return mLineEdit->text().isEmpty() ? QVariant() : mLineEdit->text(); + if ( mGeometryWidget ) + { + const QgsReferencedGeometry geometry = mGeometryWidget->geometryValue(); + return geometry.isEmpty() ? QVariant() : geometry.asWkt(); + } else + { return QVariant(); + } } QStringList QgsProcessingGeometryWidgetWrapper::compatibleParameterTypes() const diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index bb3052e2152a..db31a36cf34f 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -75,6 +75,7 @@ class QgsProcessingPointCloudExpressionLineEdit; class QgsProcessingRasterCalculatorExpressionLineEdit; class QgsRubberBand; class QgsHighlightableLineEdit; +class QgsGeometryWidget; ///@cond PRIVATE @@ -1238,7 +1239,7 @@ class GUI_EXPORT QgsProcessingGeometryParameterDefinitionWidget : public QgsProc private: - QLineEdit *mDefaultLineEdit = nullptr; + QgsGeometryWidget *mGeometryWidget = nullptr; }; @@ -1274,7 +1275,7 @@ class GUI_EXPORT QgsProcessingGeometryWidgetWrapper : public QgsAbstractProcessi QString modelerExpressionFormatString() const override; private: - QLineEdit *mLineEdit = nullptr; + QgsGeometryWidget *mGeometryWidget = nullptr; friend class TestProcessingGui; }; diff --git a/src/gui/qgsadvanceddigitizingtools.cpp b/src/gui/qgsadvanceddigitizingtools.cpp index 00ef475180cd..d57beda9421e 100644 --- a/src/gui/qgsadvanceddigitizingtools.cpp +++ b/src/gui/qgsadvanceddigitizingtools.cpp @@ -73,6 +73,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle1X->setToolTip( tr( "X coordinate" ) ); mCircle1X->setMinimum( std::numeric_limits::lowest() ); mCircle1X->setMaximum( std::numeric_limits::max() ); + mCircle1X->setDecimals( mCadDockWidget->constraintX()->precision() ); mCircle1X->setClearValue( 0.0 ); connect( mCircle1X, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle1Digitize->setChecked( false ); } ); layout->addWidget( mCircle1X, 1, 1 ); @@ -84,6 +85,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle1Y->setToolTip( tr( "Y coordinate" ) ); mCircle1Y->setMinimum( std::numeric_limits::lowest() ); mCircle1Y->setMaximum( std::numeric_limits::max() ); + mCircle1Y->setDecimals( mCadDockWidget->constraintY()->precision() ); mCircle1Y->setClearValue( 0.0 ); connect( mCircle1Y, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle1Digitize->setChecked( false ); } ); layout->addWidget( mCircle1Y, 2, 1 ); @@ -122,6 +124,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle2X->setToolTip( tr( "X coordinate" ) ); mCircle2X->setMinimum( std::numeric_limits::lowest() ); mCircle2X->setMaximum( std::numeric_limits::max() ); + mCircle2X->setDecimals( mCadDockWidget->constraintX()->precision() ); mCircle2X->setClearValue( 0.0 ); connect( mCircle2X, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle2Digitize->setChecked( false ); } ); layout->addWidget( mCircle2X, 5, 1 ); @@ -133,6 +136,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle2Y->setToolTip( tr( "Y coordinate" ) ); mCircle2Y->setMinimum( std::numeric_limits::lowest() ); mCircle2Y->setMaximum( std::numeric_limits::max() ); + mCircle2Y->setDecimals( mCadDockWidget->constraintY()->precision() ); mCircle2Y->setClearValue( 0.0 ); connect( mCircle2Y, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle2Digitize->setChecked( false ); } ); layout->addWidget( mCircle2Y, 6, 1 ); diff --git a/src/gui/settings/qgssettingseditorwidgetregistry.cpp b/src/gui/settings/qgssettingseditorwidgetregistry.cpp index fd4872ae69d9..39afcb47a421 100644 --- a/src/gui/settings/qgssettingseditorwidgetregistry.cpp +++ b/src/gui/settings/qgssettingseditorwidgetregistry.cpp @@ -81,6 +81,11 @@ bool QgsSettingsEditorWidgetRegistry::addWrapper( QgsSettingsEditorWidgetWrapper return true; } +void QgsSettingsEditorWidgetRegistry::addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper, const QgsSettingsEntryBase *setting ) +{ + mSpecificWrappers.insert( setting, wrapper ); +} + QgsSettingsEditorWidgetWrapper *QgsSettingsEditorWidgetRegistry::createWrapper( const QString &id, QObject *parent ) const { QgsSettingsEditorWidgetWrapper *wrapper = mWrappers.value( id ); @@ -97,6 +102,10 @@ QgsSettingsEditorWidgetWrapper *QgsSettingsEditorWidgetRegistry::createWrapper( QWidget *QgsSettingsEditorWidgetRegistry::createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent ) const { + if ( mSpecificWrappers.contains( setting ) ) + { + return mSpecificWrappers.value( setting )->createEditor( setting, dynamicKeyPartList, parent ); + } QgsSettingsEditorWidgetWrapper *eww = createWrapper( setting->typeId(), parent ); if ( eww ) return eww->createEditor( setting, dynamicKeyPartList, parent ); diff --git a/src/gui/settings/qgssettingseditorwidgetregistry.h b/src/gui/settings/qgssettingseditorwidgetregistry.h index 3eef40266792..d42be8b12308 100644 --- a/src/gui/settings/qgssettingseditorwidgetregistry.h +++ b/src/gui/settings/qgssettingseditorwidgetregistry.h @@ -45,14 +45,21 @@ class GUI_EXPORT QgsSettingsEditorWidgetRegistry */ bool addWrapper( QgsSettingsEditorWidgetWrapper *wrapper SIP_TRANSFER ); + /** + * Adds an editor widget \a wrapper for a specific setting to the registry + * \since QGIS 3.40 + */ + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper SIP_TRANSFER, const QgsSettingsEntryBase *setting SIP_KEEPREFERENCE ); + //! Returns a new instance of the editor widget for the given \a id - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const SIP_FACTORY; //! Creates an editor widget for the given \a setting using the corresponding registered wrapper - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = nullptr ) const SIP_FACTORY; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = nullptr ) const SIP_TRANSFERBACK; private: QMap mWrappers; + QMap mSpecificWrappers; }; #endif // QGSSETTINGSEDITORREGISTRY_H diff --git a/src/gui/settings/qgssettingseditorwidgetwrapper.h b/src/gui/settings/qgssettingseditorwidgetwrapper.h index 70066075357d..2561385cfc27 100644 --- a/src/gui/settings/qgssettingseditorwidgetwrapper.h +++ b/src/gui/settings/qgssettingseditorwidgetwrapper.h @@ -36,7 +36,7 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject Q_OBJECT public: //! Creates a wrapper from the definition stored in a \a widget created by createEditor() - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) SIP_FACTORY; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); //! Constructor QgsSettingsEditorWidgetWrapper( QObject *parent = nullptr ); @@ -50,10 +50,10 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject virtual QString id() const = 0; //! Creates a new instance of the editor wrapper so it can be configured for a widget and a setting - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = nullptr ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = nullptr ) const = 0 SIP_FACTORY; //! Creates the editor widget for the given \a setting - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = nullptr ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = nullptr ) SIP_TRANSFERBACK; //! Configures the \a editor according the setting bool configureEditor( QWidget *editor, const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList() ); @@ -80,7 +80,7 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject * Sets the \a value of the widget * The wrapper must be configured before calling this medthod */ - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; /** * Configure the settings update behavior when a widget value is changed. @@ -103,10 +103,10 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject protected: //! Creates the widgets - virtual QWidget *createEditorPrivate( QWidget *parent = nullptr ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = nullptr ) const = 0 SIP_TRANSFERBACK; //! Configures an existing \a editor widget - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor SIP_TRANSFERBACK, const QgsSettingsEntryBase *setting SIP_KEEPREFERENCE ) = 0; /** * Enables automatic update, which causes the setting to be updated immediately when the widget diff --git a/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h b/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h index d5d93de353a4..acf1d3819f05 100644 --- a/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h +++ b/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h @@ -63,9 +63,9 @@ class QgsSettingsEditorWidgetWrapperTemplate : public QgsSettingsEditorWidgetWra virtual bool setSettingFromWidget() const override = 0; - void setWidgetFromVariant( const QVariant &value ) const override + bool setWidgetFromVariant( const QVariant &value ) const override { - setWidgetValue( mSetting->convertFromVariant( value ) ); + return setWidgetValue( mSetting->convertFromVariant( value ) ); } //! Sets the widget value diff --git a/src/gui/settings/qgssettingstreemodel.cpp b/src/gui/settings/qgssettingstreemodel.cpp index 450a7c6d881d..c3a60a105d21 100644 --- a/src/gui/settings/qgssettingstreemodel.cpp +++ b/src/gui/settings/qgssettingstreemodel.cpp @@ -486,7 +486,10 @@ void QgsSettingsTreeItemDelegate::setEditorData( QWidget *editor, const QModelIn { QgsSettingsEditorWidgetWrapper *eww = QgsSettingsEditorWidgetWrapper::fromWidget( editor ); if ( eww ) - eww->setWidgetFromVariant( index.model()->data( index, Qt::DisplayRole ) ); + { + const QVariant value = index.model()->data( index, Qt::DisplayRole ); + eww->setWidgetFromVariant( value ); + } } void QgsSettingsTreeItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const diff --git a/src/gui/stac/qgsstacdataitemguiprovider.cpp b/src/gui/stac/qgsstacdataitemguiprovider.cpp index 7d709bee69cf..c4ae1db25c5a 100644 --- a/src/gui/stac/qgsstacdataitemguiprovider.cpp +++ b/src/gui/stac/qgsstacdataitemguiprovider.cpp @@ -217,9 +217,9 @@ void QgsStacDataItemGuiProvider::downloadAssets( QgsDataItem *item, QgsDataItemG connect( fetcher, &QgsNetworkContentFetcherTask::fetched, item, [fetcher, folder, context] { QNetworkReply *reply = fetcher->reply(); - if ( !reply ) + if ( !reply || reply->error() != QNetworkReply::NoError ) { - // canceled + // canceled or failed return; } else diff --git a/src/providers/postgres/qgspostgresexpressioncompiler.cpp b/src/providers/postgres/qgspostgresexpressioncompiler.cpp index f24fa7334f2d..23d797c2e29b 100644 --- a/src/providers/postgres/qgspostgresexpressioncompiler.cpp +++ b/src/providers/postgres/qgspostgresexpressioncompiler.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgspostgresexpressioncompiler.h" +#include "qgsexpressionutils.h" #include "qgssqlexpressioncompiler.h" #include "qgsexpressionnodeimpl.h" @@ -46,13 +47,10 @@ QString QgsPostgresExpressionCompiler::quotedValue( const QVariant &value, bool default: - if ( value.userType() == qMetaTypeId() ) - { - const QgsGeometry geom = value.value(); - return QString( "ST_GeomFromText('%1',%2)" ).arg( geom.asWkt() ).arg( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid ); - } - break; - + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, nullptr ); + if ( geom.isNull() ) + break; + return QString( "ST_GeomFromText('%1',%2)" ).arg( geom.asWkt() ).arg( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid ); } return QgsPostgresConn::quotedValue( value ); diff --git a/tests/src/app/testqgsidentify.cpp b/tests/src/app/testqgsidentify.cpp index 951326b116a2..58392b46e1a9 100644 --- a/tests/src/app/testqgsidentify.cpp +++ b/tests/src/app/testqgsidentify.cpp @@ -72,6 +72,7 @@ class TestQgsIdentify : public QObject void testLineStringZ(); void testPolygonZ(); void identifyPointCloud(); + void identifyVirtualPointCloud(); private: void doAction(); @@ -1303,6 +1304,34 @@ void TestQgsIdentify::identifyPointCloud() #endif } +void TestQgsIdentify::identifyVirtualPointCloud() +{ +#ifdef HAVE_COPC + std::unique_ptr< QgsPointCloudLayer > pointCloud = std::make_unique< QgsPointCloudLayer >( QStringLiteral( TEST_DATA_DIR ) + "/point_clouds/virtual/sunshine-coast/combined.vpc", QStringLiteral( "pointcloud" ), QStringLiteral( "vpc" ) ); + QVERIFY( pointCloud->isValid() ); + pointCloud->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) ); + QCOMPARE( pointCloud->crs3D().horizontalCrs().authid(), QStringLiteral( "EPSG:28356" ) ); + + for ( int i = 0; i < pointCloud->dataProvider()->subIndexes().size(); i++ ) + pointCloud->dataProvider()->loadSubIndex( i ); + + // set project CRS and ellipsoid + // Note that using a different CRS here (a world-wide WGS84-based one) caused + // problems on some machines due to insufficient precision in reprojection. + QgsProject::instance()->setCrs( pointCloud->crs() ); + canvas->setDestinationCrs( pointCloud->crs() ); + canvas->setExtent( QgsRectangle::fromCenterAndSize( QgsPointXY( 498065.5, 7050992.5 ), 1, 1 ) ); + + const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 498065.23, 7050992.90 ); + + std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); + QList result = action->identify( static_cast< int >( mapPoint.x() ), static_cast< int >( mapPoint.y() ), QList() << pointCloud.get() ); + QCOMPARE( result.length(), 1 ); + double z = result.at( 0 ).mDerivedAttributes[ QStringLiteral( "Z" )].toDouble(); + QGSCOMPARENEAR( z, 74.91, 0.001 ); +#endif +} + QGSTEST_MAIN( TestQgsIdentify ) #include "testqgsidentify.moc" diff --git a/tests/src/core/geometry/testqgsgeometry.cpp b/tests/src/core/geometry/testqgsgeometry.cpp index 82c0643caee0..74c069015528 100644 --- a/tests/src/core/geometry/testqgsgeometry.cpp +++ b/tests/src/core/geometry/testqgsgeometry.cpp @@ -89,6 +89,7 @@ class TestQgsGeometry : public QgsTest void curveIndexOf(); void splitCurve_data(); void splitCurve(); + void splitToDisjointXYParts(); void fromBox3d(); void fromPoint(); @@ -752,6 +753,38 @@ void TestQgsGeometry::splitCurve() QCOMPARE( p2->asWkt(), curve2 ); } +void TestQgsGeometry::splitToDisjointXYParts() +{ + QgsLineString onePartLine( QgsPoint( 1.0, 1.0 ), QgsPoint( 2.0, 2.0 ) ); + QVector onePartParts = onePartLine.splitToDisjointXYParts(); + QCOMPARE( onePartParts.size(), 1 ); + QCOMPARE( onePartParts[0]->asWkt(), onePartLine.asWkt() ); + for ( QgsLineString *part : onePartParts ) + delete part; + + QgsLineString onePointLine( QVector {QgsPoint( 1.0, 1.0 )} ); + QVector onePointParts = onePointLine.splitToDisjointXYParts(); + QCOMPARE( onePointParts.size(), 1 ); + QCOMPARE( onePointParts[0]->asWkt(), onePointLine.asWkt() ); + for ( QgsLineString *part : onePointParts ) + delete part; + + QgsLineString emptyLine( QVector { } ); + QVector emptyParts = emptyLine.splitToDisjointXYParts(); + QCOMPARE( emptyParts.size(), 1 ); + QCOMPARE( emptyParts[0]->asWkt(), emptyLine.asWkt() ); + for ( QgsLineString *part : emptyParts ) + delete part; + + QgsLineString triangle( QVector {QgsPoint( 0.0, 0.0 ), QgsPoint( 1.0, 0.0 ), QgsPoint( 1.0, 1.0 ), QgsPoint( 0.0, 0.0 )} ); + QVector triangleParts = triangle.splitToDisjointXYParts(); + QCOMPARE( triangleParts.size(), 2 ); + QCOMPARE( triangleParts[0]->asWkt(), "LineString (0 0, 1 0, 1 1)" ); + QCOMPARE( triangleParts[1]->asWkt(), "LineString (1 1, 0 0)" ); + for ( QgsLineString *part : triangleParts ) + delete part; +} + void TestQgsGeometry::fromBox3d() { QgsBox3D box3d( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 ); diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 8a301bdaae87..3fd0188f861b 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -3423,12 +3423,16 @@ class TestQgsExpression: public QObject QgsPolylineXY line; line << QgsPointXY( 1, 1 ) << QgsPointXY( 4, 2 ) << QgsPointXY( 3, 1 ); + QgsGeometry lineGeometry = QgsGeometry::fromPolylineXY( line ); QTest::newRow( "geom x" ) << "$x" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 123. ); QTest::newRow( "geom y" ) << "$y" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 456. ); QTest::newRow( "geom z" ) << "$z" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 789. ); - QTest::newRow( "geom xat" ) << "xat(-1)" << QgsGeometry::fromPolylineXY( line ) << false << QVariant( 3. ); - QTest::newRow( "geom yat" ) << "yat(1)" << QgsGeometry::fromPolylineXY( line ) << false << QVariant( 2. ); + QTest::newRow( "geom xat" ) << "xat(-1)" << lineGeometry << false << QVariant( 3. ); + QTest::newRow( "geom yat" ) << "yat(1)" << lineGeometry << false << QVariant( 2. ); + QTest::newRow( "geom length" ) << "length($geometry)" << lineGeometry << false << QVariant( lineGeometry.length() ); + QTest::newRow( "collected geometry" ) << "geom_to_wkt(collect_geometries($geometry, $geometry))" << lineGeometry << false << QVariant( QStringLiteral( "MultiLineString ((1 1, 4 2, 3 1),(1 1, 4 2, 3 1))" ) ); QTest::newRow( "null geometry" ) << "$geometry" << QgsGeometry() << false << QVariant(); + QTest::newRow( "empty geometry" ) << "geom_to_wkt($geometry)" << QgsGeometry().fromWkt( QStringLiteral( "Point()" ) ) << false << QVariant( QStringLiteral( "Point EMPTY" ) ); } void eval_geometry() @@ -3439,16 +3443,34 @@ class TestQgsExpression: public QObject QFETCH( QVariant, result ); QgsFeature f; - f.setGeometry( geom ); + QList geometryList; + geometryList << geom << QgsReferencedGeometry( geom, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) ); - QgsExpression exp( string ); - QCOMPARE( exp.hasParserError(), false ); - QCOMPARE( exp.needsGeometry(), true ); + // With standard geometry + { + f.setGeometry( geom ); + QgsExpression exp( string ); + QCOMPARE( exp.hasParserError(), false ); + QCOMPARE( exp.needsGeometry(), true ); - QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); - QVariant out = exp.evaluate( &context ); - QCOMPARE( exp.hasEvalError(), evalError ); - QCOMPARE( out, result ); + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + QVariant out = exp.evaluate( &context ); + QCOMPARE( exp.hasEvalError(), evalError ); + QCOMPARE( out, result ); + } + + // With referenced geometry + { + f.setGeometry( QgsReferencedGeometry( geom, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) ) ); + QgsExpression exp( string ); + QCOMPARE( exp.hasParserError(), false ); + QCOMPARE( exp.needsGeometry(), true ); + + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + QVariant out = exp.evaluate( &context ); + QCOMPARE( exp.hasEvalError(), evalError ); + QCOMPARE( out, result ); + } } void testGeometryFromContext() diff --git a/tests/src/core/testqgsstac.cpp b/tests/src/core/testqgsstac.cpp index 917057f34ead..d2b834dfab79 100644 --- a/tests/src/core/testqgsstac.cpp +++ b/tests/src/core/testqgsstac.cpp @@ -24,6 +24,8 @@ #include "qgsstaccatalog.h" #include "qgsstaccollection.h" #include "qgsstacitem.h" +#include "qgsstacitemcollection.h" +#include "qgsstaccollections.h" #include "qgsapplication.h" #include "qgsproject.h" #include "qgis.h" @@ -48,6 +50,8 @@ class TestQgsStac : public QObject void testParseLocalCatalog(); void testParseLocalCollection(); void testParseLocalItem(); + void testParseLocalItemCollection(); + void testParseLocalCollections(); private: QString mDataDir; @@ -180,5 +184,50 @@ void TestQgsStac::testParseLocalItem() delete item; } +void TestQgsStac::testParseLocalItemCollection() +{ + const QString fileName = mDataDir + QStringLiteral( "itemcollection-sample-full.json" ); + QgsStacController c; + QgsStacItemCollection *ic = c.fetchItemCollection( QStringLiteral( "file://%1" ).arg( fileName ) ); + QVERIFY( ic ); + QCOMPARE( ic->numberReturned(), 1 ); + QCOMPARE( ic->numberMatched(), 10 ); + QCOMPARE( ic->rootUrl().toString(), QLatin1String( "http://stac.example.com/" ) ); + + QVector items = ic->items(); + QCOMPARE( items.size(), 1 ); + QCOMPARE( items.first()->id(), QLatin1String( "cs3-20160503_132131_05" ) ); + QCOMPARE( items.first()->stacVersion(), QLatin1String( "1.0.0" ) ); + QCOMPARE( items.first()->links().size(), 3 ); + QCOMPARE( items.first()->stacExtensions().size(), 0 ); + QCOMPARE( items.first()->assets().size(), 2 ); + + delete ic; +} + +void TestQgsStac::testParseLocalCollections() +{ + const QString fileName = mDataDir + QStringLiteral( "collectioncollection-sample-full.json" ); + QgsStacController c; + QgsStacCollections *cols = c.fetchCollections( QStringLiteral( "file://%1" ).arg( fileName ) ); + QVERIFY( cols ); + QCOMPARE( cols->numberReturned(), 1 ); + QCOMPARE( cols->numberMatched(), 11 ); + QCOMPARE( cols->rootUrl().toString(), QLatin1String( "http://stac.example.com/" ) ); + QCOMPARE( cols->url().toString(), QLatin1String( "http://stac.example.com/collections?page=2" ) ); + QCOMPARE( cols->nextUrl().toString(), QLatin1String( "http://stac.example.com/collections?page=3" ) ); + QCOMPARE( cols->prevUrl().toString(), QLatin1String( "http://stac.example.com/collections?page=1" ) ); + + QCOMPARE( cols->collections().size(), 1 ); + + QgsStacCollection *col = cols->collections().first(); + QCOMPARE( col->id(), QStringLiteral( "simple-collection" ) ); + QCOMPARE( col->stacVersion(), QLatin1String( "1.0.0" ) ); + QCOMPARE( col->links().size(), 3 ); + QCOMPARE( col->stacExtensions().size(), 0 ); + + delete cols; +} + QGSTEST_MAIN( TestQgsStac ) #include "testqgsstac.moc" diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index 262291feb431..ad5dbdcc9d0c 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -105,6 +105,7 @@ #include "qgsprocessingalignrasterlayerswidgetwrapper.h" #include "qgsprocessingrasteroptionswidgetwrapper.h" #include "qgsrasterformatsaveoptionswidget.h" +#include "qgsgeometrywidget.h" class TestParamType : public QgsProcessingParameterDefinition @@ -6069,15 +6070,15 @@ void TestProcessingGui::testGeometryWrapper() QgsProcessingContext context; QWidget *w = wrapper.createWrappedWidget( context ); - QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged ); + QSignalSpy spy( &wrapper, &QgsProcessingGeometryWidgetWrapper::widgetValueHasChanged ); wrapper.setWidgetValue( QStringLiteral( "POINT (1 2)" ), context ); QCOMPARE( spy.count(), 1 ); QCOMPARE( wrapper.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); - QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ).toLower() ); + QCOMPARE( static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->geometryValue().asWkt().toLower(), QStringLiteral( "point (1 2)" ).toLower() ); wrapper.setWidgetValue( QString(), context ); QCOMPARE( spy.count(), 2 ); QVERIFY( wrapper.widgetValue().toString().isEmpty() ); - QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); QLabel *l = wrapper.createWrappedLabel(); if ( wrapper.type() != QgsProcessingGui::Batch ) @@ -6093,9 +6094,9 @@ void TestProcessingGui::testGeometryWrapper() } // check signal - static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) ); + static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->setGeometryValue( QgsReferencedGeometry( QgsGeometry::fromWkt( "point(0 0)" ), QgsCoordinateReferenceSystem() ) ); QCOMPARE( spy.count(), 3 ); - static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear(); + static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->clearGeometry(); QCOMPARE( spy.count(), 4 ); delete w; @@ -6112,19 +6113,19 @@ void TestProcessingGui::testGeometryWrapper() wrapper2.setWidgetValue( "POINT (1 2)", context ); QCOMPARE( spy2.count(), 1 ); QCOMPARE( wrapper2.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); - QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ) ); + QCOMPARE( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().toLower(), QStringLiteral( "point (1 2)" ) ); wrapper2.setWidgetValue( QVariant(), context ); QCOMPARE( spy2.count(), 2 ); QVERIFY( !wrapper2.widgetValue().isValid() ); - QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); wrapper2.setWidgetValue( "POINT (1 3)", context ); QCOMPARE( spy2.count(), 3 ); wrapper2.setWidgetValue( "", context ); QCOMPARE( spy2.count(), 4 ); QVERIFY( !wrapper2.widgetValue().isValid() ); - QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); delete w; }; diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index ce1dde961d8c..16aa32a3f776 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -520,6 +520,7 @@ if (WITH_GUI) ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py) ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py) ADD_PYTHON_TEST(PyQgsTextFormatWidget test_qgstextformatwidget.py) + ADD_PYTHON_TEST(PyQgsTextFormat test_qgstextformat.py) ADD_PYTHON_TEST(PyQgsTreeWidgetItem test_qgstreewidgetitem.py) ADD_PYTHON_TEST(PyQgsValidityResultsWidget test_qgsvalidityresultswidget.py) ADD_PYTHON_TEST(PyQgsVectorLayer test_qgsvectorlayer.py) diff --git a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py index 64fe7208a5a0..13806cb9779f 100644 --- a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py +++ b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py @@ -35,17 +35,20 @@ QgsGeometry, QgsGeometryGeneratorSymbolLayer, QgsLineSymbol, + QgsMapRendererSequentialJob, QgsMapSettings, QgsMarkerSymbol, QgsProject, QgsProperty, QgsRectangle, + QgsReferencedGeometry, QgsRenderContext, QgsSingleSymbolRenderer, QgsSymbol, QgsSymbolLayer, QgsUnitTypes, QgsVectorLayer, + QgsVectorLayerUtils, ) import unittest from qgis.testing import start_app, QgisTestCase @@ -463,6 +466,31 @@ def test_geometry_function(self): ) ) + def test_field_geometry(self): + """ + Use a geometry field + """ + + points = QgsVectorLayer('Point?crs=epsg:2154&field=other_geom:geometry(0,0)', 'Points', 'memory') + f = QgsVectorLayerUtils.createFeature(points, + QgsGeometry.fromWkt('Point(5 4)'), + {0: QgsReferencedGeometry(QgsGeometry.fromWkt('LineString(5 6, 7 8)'), QgsCoordinateReferenceSystem("EPSG:4326"))}) + points.dataProvider().addFeature(f) + other_layer = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': '"other_geom"', 'outline_color': 'black', 'SymbolType': 'Line', 'line_width': 2}) + points.renderer().symbol().changeSymbolLayer(0, other_layer) + + mapsettings = QgsMapSettings(self.mapsettings) + mapsettings.setExtent(QgsRectangle(0, 0, 10, 10)) + mapsettings.setLayers([points]) + + self.assertTrue( + self.render_map_settings_check( + 'geometrygenerator_field_geometry', + 'geometrygenerator_field_geometry', + mapsettings + ) + ) + def test_feature_geometry(self): """ The geometry($currentfeature) expression used in a subsymbol should refer to the original FEATURE geometry diff --git a/tests/src/python/test_qgssettingseditorregistry.py b/tests/src/python/test_qgssettingseditorregistry.py new file mode 100644 index 000000000000..390b878b7b8e --- /dev/null +++ b/tests/src/python/test_qgssettingseditorregistry.py @@ -0,0 +1,75 @@ +""" +Test the PyQgsSettingsRegistry classes + +Run with: ctest -V -R PyQgsSettingsRegistry + +.. note:: 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. +""" + +from qgis.PyQt.QtWidgets import QComboBox, QSpinBox +from qgis.core import ( + QgsLocatorFilter, + QgsSettings, + QgsSettingsTree, + QgsSettingsEntryEnumFlag, + QgsSettingsEntryInteger, +) +from qgis.gui import QgsGui, QgsSettingsEditorWidgetWrapper, QgsSettingsEnumEditorWidgetWrapper +import unittest +from qgis.testing import start_app, QgisTestCase + + +start_app() + +PLUGIN_NAME = "UnitTestSettingsRegistry" + + +class PyQgsSettingsRegistry(QgisTestCase): + + def setUp(self): + self.settings_node = QgsSettingsTree.createPluginTreeNode(pluginName=PLUGIN_NAME) + + def tearDown(self): + QgsSettingsTree.unregisterPluginTreeNode(PLUGIN_NAME) + + def test_settings_registry(self): + int_setting = QgsSettingsEntryInteger("int_setting", self.settings_node, 77) + registry = QgsGui.settingsEditorWidgetRegistry() + + editor = registry.createEditor(int_setting, []) + self.assertIsInstance(editor, QSpinBox) + + wrapper = QgsSettingsEditorWidgetWrapper.fromWidget(editor) + self.assertEqual(editor.value(), 77) + + editor.setValue(6) + self.assertEqual(wrapper.variantValueFromWidget(), 6) + wrapper.setSettingFromWidget() + self.assertEqual(int_setting.value(), 6) + + def test_settings_registry_custom_enumflag_py(self): + priority_setting = QgsSettingsEntryEnumFlag("priority", self.settings_node, QgsLocatorFilter.Priority.High) + registry = QgsGui.settingsEditorWidgetRegistry() + registry.addWrapperForSetting(QgsSettingsEnumEditorWidgetWrapper(), priority_setting) + + editor = registry.createEditor(priority_setting, []) + self.assertIsInstance(editor, QComboBox) + wrapper = QgsSettingsEditorWidgetWrapper.fromWidget(editor) + + self.assertEqual(editor.currentData(), "High") + + editor.setCurrentIndex(editor.findData("Low")) + + self.assertEqual(wrapper.variantValueFromWidget(), "Low") + + wrapper.setSettingFromWidget() + + self.assertEqual(priority_setting.value(), QgsLocatorFilter.Priority.Low) + self.assertEqual(QgsSettings().value(f"plugins/{PLUGIN_NAME}/priority"), "Low") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgstextformat.py b/tests/src/python/test_qgstextformat.py new file mode 100644 index 000000000000..b4f4fe6ab354 --- /dev/null +++ b/tests/src/python/test_qgstextformat.py @@ -0,0 +1,55 @@ +"""QGIS Unit tests for QgsTextFormat. + +.. note:: 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__ = 'Mathieu Pellerin' +__date__ = '2024-10-20' +__copyright__ = 'Copyright 2024, The QGIS Project' + +from qgis.PyQt.QtGui import QFont +from qgis.PyQt.QtXml import ( + QDomDocument, + QDomElement, +) +from qgis.core import ( + QgsTextFormat, + QgsReadWriteContext, +) +import unittest +from qgis.testing import start_app, QgisTestCase +from utilities import getTestFont + +start_app() + + +class PyQgsTextFormat(QgisTestCase): + + def testRestoringAndSavingMissingFont(self): + # test that a missing font on text format load will still save with the same missing font unless manually changed + document = QDomDocument() + document.setContent('') + + context = QgsReadWriteContext() + text_format = QgsTextFormat() + text_format.readXml(document.documentElement(), context) + + self.assertFalse(text_format.fontFound()) + self.assertTrue(text_format.font().family() != "__MISSING__") + + # when writign the settings to XML, the missing font family should still be there + element = text_format.writeXml(document, context) + self.assertEqual(element.attribute("fontFamily"), "__MISSING__") + + font = getTestFont() + text_format.setFont(font) + + # when writing the settings to XML, the originally missing font family should have been replaced by the new font family + element = text_format.writeXml(document, context) + self.assertEqual(element.attribute("fontFamily"), "QGIS Vera Sans") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 7146cc3e679b..79d0644e4904 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -1674,7 +1674,7 @@ def testDrawMassiveFont(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(1100) - assert self.checkRender(format, 'massive_font', rect=QRectF(-800, -600, 1000, 1000), text=['a t'], image_size=800) + self.assertTrue(self.checkRender(format, 'massive_font', rect=QRectF(-800, -600, 1000, 1000), text=['a t'], image_size=800)) def testDrawRectMixedHtml(self): """ @@ -1684,7 +1684,7 @@ def testDrawRectMixedHtml(self): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line']) + self.assertTrue(self.checkRender(format, 'rect_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'])) def testDrawDocumentRect(self): """ @@ -1736,7 +1736,7 @@ def testDrawRectCapHeightMode(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) - assert self.checkRender(format, 'rect_cap_height_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased) + self.assertTrue(self.checkRender(format, 'rect_cap_height_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased)) def testDrawRectCapHeightModeMixedHtml(self): """ @@ -1746,7 +1746,7 @@ def testDrawRectCapHeightModeMixedHtml(self): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_cap_height_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased) + self.assertTrue(self.checkRender(format, 'rect_cap_height_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased)) def testDrawDocumentRectCapHeightMode(self): """ @@ -1798,7 +1798,7 @@ def testDrawRectAscentMode(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) - assert self.checkRender(format, 'rect_ascent_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased) + self.assertTrue(self.checkRender(format, 'rect_ascent_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased)) def testDrawRectAscentModeMixedHtml(self): """ @@ -1808,7 +1808,7 @@ def testDrawRectAscentModeMixedHtml(self): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_ascent_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased) + self.assertTrue(self.checkRender(format, 'rect_ascent_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased)) def testDrawDocumentRectAscentMode(self): """ @@ -1912,7 +1912,7 @@ def testDrawForcedItalic(self): format.setFont(getTestFont()) format.setSize(30) format.setForcedItalic(True) - assert self.checkRender(format, 'forced_italic', text=['Forced italic']) + self.assertTrue(self.checkRender(format, 'forced_italic', text=['Forced italic'])) def testDrawRTL(self): """ @@ -2094,7 +2094,7 @@ def testDrawSmallCaps(self): format.setFont(getTestFont('bold')) format.setCapitalization(Qgis.Capitalization.SmallCaps) format.setSize(30) - assert self.checkRender(format, 'mixed_small_caps', text=['Small Caps']) + self.assertTrue(self.checkRender(format, 'mixed_small_caps', text=['Small Caps'])) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawAllSmallCaps(self): @@ -2102,7 +2102,7 @@ def testDrawAllSmallCaps(self): format.setFont(getTestFont('bold')) format.setSize(30) format.setCapitalization(Qgis.Capitalization.AllSmallCaps) - assert self.checkRender(format, 'all_small_caps', text=['Small Caps']) + self.assertTrue(self.checkRender(format, 'all_small_caps', text=['Small Caps'])) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawStretch(self): @@ -2110,7 +2110,7 @@ def testDrawStretch(self): format.setFont(getTestFont('bold')) format.setSize(30) format.setStretchFactor(150) - assert self.checkRender(format, 'stretch_expand') + self.assertTrue(self.checkRender(format, 'stretch_expand')) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawStretchCondense(self): @@ -2118,13 +2118,13 @@ def testDrawStretchCondense(self): format.setFont(getTestFont('bold')) format.setSize(30) format.setStretchFactor(50) - assert self.checkRender(format, 'stretch_condense') + self.assertTrue(self.checkRender(format, 'stretch_condense')) def testDrawBackgroundDisabled(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.background().setEnabled(False) - assert self.checkRender(format, 'background_disabled', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_disabled', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2134,7 +2134,7 @@ def testDrawBackgroundRectangleFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizeWithRotatedText(self): format = QgsTextFormat() @@ -2145,7 +2145,7 @@ def testDrawBackgroundRectangleFixedSizeWithRotatedText(self): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRenderPoint(format, 'background_rect_fixed_rotated_text', angle=3.141 / 4) + self.assertTrue(self.checkRenderPoint(format, 'background_rect_fixed_rotated_text', angle=3.141 / 4)) def testDrawBackgroundRectangleBufferSizeWithRotatedText(self): format = QgsTextFormat() @@ -2156,7 +2156,7 @@ def testDrawBackgroundRectangleBufferSizeWithRotatedText(self): format.background().setSize(QSizeF(2, 3)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRenderPoint(format, 'background_rect_buffer_rotated_text', angle=3.141 / 4) + self.assertTrue(self.checkRenderPoint(format, 'background_rect_buffer_rotated_text', angle=3.141 / 4)) def testDrawBackgroundRectangleMultilineFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2166,8 +2166,8 @@ def testDrawBackgroundRectangleMultilineFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_multiline_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'background_rect_multiline_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointMultilineFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2177,8 +2177,8 @@ def testDrawBackgroundPointMultilineFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_multiline_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_multiline_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundRectangleMultilineBufferMapUnits(self): format = QgsTextFormat() @@ -2188,8 +2188,8 @@ def testDrawBackgroundRectangleMultilineBufferMapUnits(self): format.background().setSize(QSizeF(4, 2)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'background_rect_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointMultilineBufferMapUnits(self): format = QgsTextFormat() @@ -2199,8 +2199,8 @@ def testDrawBackgroundPointMultilineBufferMapUnits(self): format.background().setSize(QSizeF(4, 2)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2210,8 +2210,8 @@ def testDrawBackgroundPointFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_mapunits', QgsTextRenderer.TextPart.Background, - text=['Testy']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_mapunits', QgsTextRenderer.TextPart.Background, + text=['Testy'])) def testDrawBackgroundRectangleCenterAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2221,8 +2221,8 @@ def testDrawBackgroundRectangleCenterAlignFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_center_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRender(format, 'background_rect_center_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawBackgroundPointCenterAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2232,8 +2232,8 @@ def testDrawBackgroundPointCenterAlignFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_center_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRenderPoint(format, 'background_point_center_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawBackgroundRectangleRightAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2243,8 +2243,8 @@ def testDrawBackgroundRectangleRightAlignFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_right_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'background_rect_right_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawBackgroundPointRightAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2254,8 +2254,8 @@ def testDrawBackgroundPointRightAlignFixedSizeMapUnits(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_right_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRenderPoint(format, 'background_point_right_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawBackgroundRectangleFixedSizeMM(self): format = QgsTextFormat() @@ -2265,7 +2265,7 @@ def testDrawBackgroundRectangleFixedSizeMM(self): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_rect_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizePixels(self): format = QgsTextFormat() @@ -2275,7 +2275,7 @@ def testDrawBackgroundRectangleFixedSizePixels(self): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectBufferPixels(self): format = QgsTextFormat() @@ -2288,8 +2288,8 @@ def testDrawBackgroundRectBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectRightAlignBufferPixels(self): format = QgsTextFormat() @@ -2302,9 +2302,9 @@ def testDrawBackgroundRectRightAlignBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_right_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_right_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectCenterAlignBufferPixels(self): format = QgsTextFormat() @@ -2317,9 +2317,9 @@ def testDrawBackgroundRectCenterAlignBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_center_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_center_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundPointBufferPixels(self): format = QgsTextFormat() @@ -2332,8 +2332,8 @@ def testDrawBackgroundPointBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_buffer_pixels', QgsTextRenderer.TextPart.Background, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_buffer_pixels', QgsTextRenderer.TextPart.Background, + point=QPointF(100, 100))) def testDrawBackgroundPointRightAlignBufferPixels(self): format = QgsTextFormat() @@ -2346,9 +2346,9 @@ def testDrawBackgroundPointRightAlignBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_right_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_right_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight, + point=QPointF(100, 100))) def testDrawBackgroundPointCenterAlignBufferPixels(self): format = QgsTextFormat() @@ -2361,9 +2361,9 @@ def testDrawBackgroundPointCenterAlignBufferPixels(self): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_center_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_center_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter, + point=QPointF(100, 100))) def testDrawBackgroundRectBufferMapUnits(self): format = QgsTextFormat() @@ -2376,8 +2376,8 @@ def testDrawBackgroundRectBufferMapUnits(self): format.background().setSize(QSizeF(4, 6)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectBufferMM(self): format = QgsTextFormat() @@ -2390,8 +2390,8 @@ def testDrawBackgroundRectBufferMM(self): format.background().setSize(QSizeF(10, 16)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_rect_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundEllipse(self): format = QgsTextFormat() @@ -2401,7 +2401,7 @@ def testDrawBackgroundEllipse(self): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_ellipse_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_ellipse_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedPixels(self): format = QgsTextFormat() @@ -2414,7 +2414,7 @@ def testDrawBackgroundSvgFixedPixels(self): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_svg_fixed_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedMapUnits(self): format = QgsTextFormat() @@ -2427,7 +2427,7 @@ def testDrawBackgroundSvgFixedMapUnits(self): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_svg_fixed_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedMM(self): format = QgsTextFormat() @@ -2440,7 +2440,7 @@ def testDrawBackgroundSvgFixedMM(self): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_svg_fixed_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRotationSynced(self): format = QgsTextFormat() @@ -2452,7 +2452,7 @@ def testDrawBackgroundRotationSynced(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) # should be ignored format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationSync) - assert self.checkRender(format, 'background_rotation_sync', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_sync', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawBackgroundSvgBufferPixels(self): format = QgsTextFormat() @@ -2465,8 +2465,8 @@ def testDrawBackgroundSvgBufferPixels(self): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_svg_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundSvgBufferMapUnits(self): format = QgsTextFormat() @@ -2479,8 +2479,8 @@ def testDrawBackgroundSvgBufferMapUnits(self): format.background().setSize(QSizeF(4, 4)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_svg_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundSvgBufferMM(self): format = QgsTextFormat() @@ -2493,8 +2493,8 @@ def testDrawBackgroundSvgBufferMM(self): format.background().setSize(QSizeF(10, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerFixedPixels(self): format = QgsTextFormat() @@ -2506,7 +2506,7 @@ def testDrawBackgroundMarkerFixedPixels(self): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerFixedReferenceScale(self): format = QgsTextFormat() @@ -2519,8 +2519,8 @@ def testDrawBackgroundMarkerFixedReferenceScale(self): format.background().setSize(QSizeF(6, 8)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_fixed_reference_scale', - reference_scale=10000, renderer_scale=5000) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_reference_scale', + reference_scale=10000, renderer_scale=5000)) def testDrawBackgroundMarkerFixedMapUnits(self): format = QgsTextFormat() @@ -2532,7 +2532,7 @@ def testDrawBackgroundMarkerFixedMapUnits(self): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerFixedMM(self): format = QgsTextFormat() @@ -2544,7 +2544,7 @@ def testDrawBackgroundMarkerFixedMM(self): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerBufferPixels(self): format = QgsTextFormat() @@ -2556,8 +2556,8 @@ def testDrawBackgroundMarkerBufferPixels(self): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerBufferMapUnits(self): format = QgsTextFormat() @@ -2569,8 +2569,8 @@ def testDrawBackgroundMarkerBufferMapUnits(self): format.background().setSize(QSizeF(4, 4)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerBufferMM(self): format = QgsTextFormat() @@ -2582,8 +2582,8 @@ def testDrawBackgroundMarkerBufferMM(self): format.background().setSize(QSizeF(10, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRotationFixed(self): format = QgsTextFormat() @@ -2595,7 +2595,7 @@ def testDrawBackgroundRotationFixed(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationFixed) - assert self.checkRender(format, 'background_rotation_fixed', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_fixed', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawRotationOffset(self): format = QgsTextFormat() @@ -2607,7 +2607,7 @@ def testDrawRotationOffset(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationOffset) - assert self.checkRender(format, 'background_rotation_offset', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_offset', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawBackgroundOffsetMM(self): format = QgsTextFormat() @@ -2619,7 +2619,7 @@ def testDrawBackgroundOffsetMM(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setOffset(QPointF(30, 20)) format.background().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_offset_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_offset_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundOffsetMapUnits(self): format = QgsTextFormat() @@ -2631,7 +2631,7 @@ def testDrawBackgroundOffsetMapUnits(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setOffset(QPointF(10, 5)) format.background().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_offset_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_offset_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRadiiMM(self): format = QgsTextFormat() @@ -2643,7 +2643,7 @@ def testDrawBackgroundRadiiMM(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRadii(QSizeF(6, 4)) format.background().setRadiiUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_radii_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_radii_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRadiiMapUnits(self): format = QgsTextFormat() @@ -2655,7 +2655,7 @@ def testDrawBackgroundRadiiMapUnits(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRadii(QSizeF(3, 2)) format.background().setRadiiUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_radii_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_radii_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundOpacity(self): format = QgsTextFormat() @@ -2665,7 +2665,7 @@ def testDrawBackgroundOpacity(self): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setOpacity(0.6) - assert self.checkRender(format, 'background_opacity', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_opacity', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundFillColor(self): format = QgsTextFormat() @@ -2675,7 +2675,7 @@ def testDrawBackgroundFillColor(self): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setFillColor(QColor(50, 100, 50)) - assert self.checkRender(format, 'background_fillcolor', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_fillcolor', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundFillSymbol(self): format = QgsTextFormat() @@ -2693,7 +2693,7 @@ def testDrawBackgroundFillSymbol(self): fill.setStrokeWidth(6) format.background().setFillSymbol(fill_symbol) - assert self.checkRender(format, 'background_fillsymbol', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_fillsymbol', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundStroke(self): format = QgsTextFormat() @@ -2705,7 +2705,7 @@ def testDrawBackgroundStroke(self): format.background().setStrokeColor(QColor(50, 100, 50)) format.background().setStrokeWidth(3) format.background().setStrokeWidthUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_outline', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_outline', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundEffect(self): format = QgsTextFormat() @@ -2717,21 +2717,21 @@ def testDrawBackgroundEffect(self): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setPaintEffect(QgsBlurEffect.create({'blur_level': '10', 'enabled': '1'})) - assert self.checkRender(format, 'background_effect', QgsTextRenderer.TextPart.Background, text=['test']) + self.assertTrue(self.checkRender(format, 'background_effect', QgsTextRenderer.TextPart.Background, text=['test'])) def testDrawText(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_bold', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_bold', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextPoint(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_bold', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRenderPoint(format, 'text_point_bold', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextNamedStyle(self): format = QgsTextFormat() @@ -2742,7 +2742,7 @@ def testDrawTextNamedStyle(self): format.setNamedStyle('Bold Oblique') format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_named_style', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_named_style', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextColor(self): format = QgsTextFormat() @@ -2750,7 +2750,7 @@ def testDrawTextColor(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) - assert self.checkRender(format, 'text_color', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_color', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextOpacity(self): format = QgsTextFormat() @@ -2758,7 +2758,7 @@ def testDrawTextOpacity(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOpacity(0.7) - assert self.checkRender(format, 'text_opacity', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_opacity', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextBlendMode(self): format = QgsTextFormat() @@ -2767,43 +2767,43 @@ def testDrawTextBlendMode(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(100, 100, 100)) format.setBlendMode(QPainter.CompositionMode.CompositionMode_Difference) - assert self.checkRender(format, 'text_blend_mode', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_blend_mode', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextAngle(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_angled', QgsTextRenderer.TextPart.Text, angle=90 / 180 * 3.141, text=['test']) + self.assertTrue(self.checkRender(format, 'text_angled', QgsTextRenderer.TextPart.Text, angle=90 / 180 * 3.141, text=['test'])) def testDrawTextMapUnits(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(5) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextPixels(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(50) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'text_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawMultiLineText(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_multiline', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_multiline', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawMultiLineTextPoint(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_multiline', QgsTextRenderer.TextPart.Text, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'text_point_multiline', QgsTextRenderer.TextPart.Text, + text=['test', 'multi', 'line'])) def testDrawLineHeightText(self): format = QgsTextFormat() @@ -2811,7 +2811,7 @@ def testDrawLineHeightText(self): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(1.5) - assert self.checkRender(format, 'text_line_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawLineHeightAbsolutePoints(self): format = QgsTextFormat() @@ -2820,7 +2820,7 @@ def testDrawLineHeightAbsolutePoints(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(20) format.setLineHeightUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_line_absolute_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_absolute_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawLineHeightAbsoluteMillimeters(self): format = QgsTextFormat() @@ -2829,7 +2829,7 @@ def testDrawLineHeightAbsoluteMillimeters(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(20) format.setLineHeightUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_line_absolute_mm_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_absolute_mm_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawBufferSizeMM(self): format = QgsTextFormat() @@ -2839,7 +2839,7 @@ def testDrawBufferSizeMM(self): format.buffer().setEnabled(True) format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_buffer_mm', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_mm', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferDisabled(self): format = QgsTextFormat() @@ -2847,7 +2847,7 @@ def testDrawBufferDisabled(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.buffer().setEnabled(False) - assert self.checkRender(format, 'text_disabled_buffer', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_disabled_buffer', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizeMapUnits(self): format = QgsTextFormat() @@ -2857,7 +2857,7 @@ def testDrawBufferSizeMapUnits(self): format.buffer().setEnabled(True) format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_buffer_mapunits', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_mapunits', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizePixels(self): format = QgsTextFormat() @@ -2867,7 +2867,7 @@ def testDrawBufferSizePixels(self): format.buffer().setEnabled(True) format.buffer().setSize(10) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'text_buffer_pixels', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_pixels', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizePercentage(self): format = QgsTextFormat() @@ -2877,7 +2877,7 @@ def testDrawBufferSizePercentage(self): format.buffer().setEnabled(True) format.buffer().setSize(10) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'text_buffer_percentage', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_percentage', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferColor(self): format = QgsTextFormat() @@ -2888,7 +2888,7 @@ def testDrawBufferColor(self): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setColor(QColor(0, 255, 0)) - assert self.checkRender(format, 'text_buffer_color', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_color', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferOpacity(self): format = QgsTextFormat() @@ -2899,7 +2899,7 @@ def testDrawBufferOpacity(self): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setOpacity(0.5) - assert self.checkRender(format, 'text_buffer_opacity', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_opacity', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferFillInterior(self): format = QgsTextFormat() @@ -2910,7 +2910,7 @@ def testDrawBufferFillInterior(self): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setFillBufferInterior(True) - assert self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferEffect(self): format = QgsTextFormat() @@ -2921,7 +2921,7 @@ def testDrawBufferEffect(self): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setPaintEffect(QgsBlurEffect.create({'blur_level': '10', 'enabled': '1'})) - assert self.checkRender(format, 'text_buffer_effect', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_effect', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawShadow(self): format = QgsTextFormat() @@ -2935,7 +2935,7 @@ def testDrawShadow(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_enabled', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_enabled', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetAngle(self): format = QgsTextFormat() @@ -2950,7 +2950,7 @@ def testDrawShadowOffsetAngle(self): format.shadow().setOffsetDistance(5) format.shadow().setOffsetAngle(0) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_offset_angle', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_angle', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetMapUnits(self): format = QgsTextFormat() @@ -2964,7 +2964,7 @@ def testDrawShadowOffsetMapUnits(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_offset_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetPixels(self): format = QgsTextFormat() @@ -2978,7 +2978,7 @@ def testDrawShadowOffsetPixels(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'shadow_offset_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetPercentage(self): format = QgsTextFormat() @@ -2992,7 +2992,7 @@ def testDrawShadowOffsetPercentage(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'shadow_offset_percentage', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusMM(self): format = QgsTextFormat() @@ -3007,7 +3007,7 @@ def testDrawShadowBlurRadiusMM(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(1) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_radius_mm', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_mm', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusMapUnits(self): format = QgsTextFormat() @@ -3022,7 +3022,7 @@ def testDrawShadowBlurRadiusMapUnits(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_radius_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusPixels(self): format = QgsTextFormat() @@ -3037,7 +3037,7 @@ def testDrawShadowBlurRadiusPixels(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'shadow_radius_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusPercentage(self): format = QgsTextFormat() @@ -3052,7 +3052,7 @@ def testDrawShadowBlurRadiusPercentage(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(5) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'shadow_radius_percentage', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOpacity(self): format = QgsTextFormat() @@ -3066,7 +3066,7 @@ def testDrawShadowOpacity(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_opacity', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_opacity', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowColor(self): format = QgsTextFormat() @@ -3080,7 +3080,7 @@ def testDrawShadowColor(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_color', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_color', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowWithJustifyAlign(self): format = QgsTextFormat() @@ -3093,9 +3093,9 @@ def testDrawShadowWithJustifyAlign(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_justify_aligned_with_shadow', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_justify_aligned_with_shadow', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawShadowScale(self): format = QgsTextFormat() @@ -3109,7 +3109,7 @@ def testDrawShadowScale(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_scale_50', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_scale_50', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowScaleUp(self): format = QgsTextFormat() @@ -3123,7 +3123,7 @@ def testDrawShadowScaleUp(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_scale_150', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_scale_150', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBackgroundPlacement(self): format = QgsTextFormat() @@ -3141,7 +3141,7 @@ def testDrawShadowBackgroundPlacement(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_placement_background', QgsTextRenderer.TextPart.Background, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_placement_background', QgsTextRenderer.TextPart.Background, text=['test'])) def testDrawShadowBufferPlacement(self): format = QgsTextFormat() @@ -3157,7 +3157,7 @@ def testDrawShadowBufferPlacement(self): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_placement_buffer', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_placement_buffer', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawTextWithBuffer(self): format = QgsTextFormat() @@ -3167,7 +3167,26 @@ def testDrawTextWithBuffer(self): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_buffer', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_buffer', text=['test'], rect=QRectF(100, 100, 200, 100))) + + def testDrawTextWithBufferBlendMode(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(60) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(20, 10)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) + format.background().setFillColor(QColor(200, 100, 150)) + format.buffer().setEnabled(True) + format.buffer().setSize(4) + format.buffer().setColor(QColor(100, 255, 100)) + format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.buffer().setBlendMode(QPainter.CompositionMode.CompositionMode_Multiply) + self.assertTrue(self.checkRender(format, 'text_with_buffer_blend_mode', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackground(self): format = QgsTextFormat() @@ -3179,7 +3198,7 @@ def testDrawTextWithBackground(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_background', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_background', text=['test'], rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBufferAndBackground(self): format = QgsTextFormat() @@ -3195,8 +3214,8 @@ def testDrawTextWithBufferAndBackground(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowAndBuffer(self): format = QgsTextFormat() @@ -3213,7 +3232,7 @@ def testDrawTextWithShadowAndBuffer(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_and_buffer', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_and_buffer', text=['test'], rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowBelowTextAndBuffer(self): format = QgsTextFormat() @@ -3231,8 +3250,8 @@ def testDrawTextWithShadowBelowTextAndBuffer(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_text_and_buffer', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_and_buffer', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundAndShadow(self): format = QgsTextFormat() @@ -3250,8 +3269,8 @@ def testDrawTextWithBackgroundAndShadow(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_shadow_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowBelowTextAndBackground(self): format = QgsTextFormat() @@ -3270,8 +3289,8 @@ def testDrawTextWithShadowBelowTextAndBackground(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_shadow_below_text_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadow(self): format = QgsTextFormat() @@ -3293,8 +3312,8 @@ def testDrawTextWithBackgroundBufferAndShadow(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadowBelowText(self): format = QgsTextFormat() @@ -3317,8 +3336,8 @@ def testDrawTextWithBackgroundBufferAndShadowBelowText(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_text_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadowBelowBuffer(self): format = QgsTextFormat() @@ -3341,24 +3360,24 @@ def testDrawTextWithBackgroundBufferAndShadowBelowBuffer(self): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultilineRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_right_aligned', text=['test', 'right', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_right_aligned', text=['test', 'right', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_right_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_right_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultilineJustifyAlign(self): format = QgsTextFormat() @@ -3368,17 +3387,17 @@ def testDrawTextRectMultilineJustifyAlign(self): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_rect_multiline_justify_aligned', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_justify_aligned', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_justify_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_justify_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultiparagraphJustifyAlign(self): format = QgsTextFormat() @@ -3388,9 +3407,9 @@ def testDrawTextRectMultiparagraphJustifyAlign(self): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_rect_multiparagraph_justify_aligned', - text=['a t est', 'of justify', '', 'with two', 'pgraphs'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(50, 100, 250, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiparagraph_justify_aligned', + text=['a t est', 'of justify', '', 'with two', 'pgraphs'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(50, 100, 250, 100))) def testDrawTextRectWordWrapSingleLine(self): format = QgsTextFormat() @@ -3422,9 +3441,9 @@ def testDrawTextRectWordWrapSingleLine(self): mode=QgsTextRenderer.DrawMode.Rect, flags=Qgis.TextRendererFlag.WrapLines, maxLineWidth=200), QgsTextRenderer.textHeight(context, format, ['a test of word wrap'], mode=QgsTextRenderer.DrawMode.Rect) * 2.75) - assert self.checkRender(format, 'text_rect_word_wrap_single_line', text=['a test of word wrap'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_single_line', text=['a test of word wrap'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testWordWrapSingleLineStabilityAtSmallScaling(self): format = QgsTextFormat() @@ -3494,18 +3513,18 @@ def testDrawTextRectWordWrapMultiLine(self): mode=QgsTextRenderer.DrawMode.Rect, flags=Qgis.TextRendererFlag.WrapLines, maxLineWidth=200), QgsTextRenderer.textHeight(context, format, ['a test of word wrap with with bit more'], mode=QgsTextRenderer.DrawMode.Rect) * 4.75) - assert self.checkRender(format, 'text_rect_word_wrap_multi_line', text=['a test of word wrap', 'with bit more'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_multi_line', text=['a test of word wrap', 'with bit more'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testDrawTextRectWordWrapWithJustify(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_word_wrap_justify', text=['a test of word wrap'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_justify', text=['a test of word wrap'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testDrawTextRectWordWrapHtml1(self): format = QgsTextFormat() @@ -3595,9 +3614,9 @@ def testDrawTextRectMultilineBottomAlign(self): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_bottom_aligned', text=['test', 'bottom', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignBottom) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_bottom_aligned', text=['test', 'bottom', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignBottom)) def testDrawTextRectBottomAlign(self): format = QgsTextFormat() @@ -3605,9 +3624,9 @@ def testDrawTextRectBottomAlign(self): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_bottom_aligned', text=['bottom aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignBottom) + self.assertTrue(self.checkRender(format, 'text_rect_bottom_aligned', text=['bottom aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignBottom)) def testDrawTextRectMultilineVCenterAlign(self): format = QgsTextFormat() @@ -3615,9 +3634,9 @@ def testDrawTextRectMultilineVCenterAlign(self): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_vcenter_aligned', text=['test', 'center', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignVCenter) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_vcenter_aligned', text=['test', 'center', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignVCenter)) def testDrawTextRectVCenterAlign(self): format = QgsTextFormat() @@ -3625,17 +3644,17 @@ def testDrawTextRectVCenterAlign(self): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_vcenter_aligned', text=['center aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignVCenter) + self.assertTrue(self.checkRender(format, 'text_rect_vcenter_aligned', text=['center aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignVCenter)) def testDrawTextRectMultilineCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_center_aligned', text=['test', 'c', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_center_aligned', text=['test', 'c', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectCenterAlign(self): format = QgsTextFormat() @@ -3643,57 +3662,57 @@ def testDrawTextRectCenterAlign(self): format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_center_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_center_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100))) def testDrawTextPointMultilineRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_right_multiline_aligned', text=['test', 'right', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_right_multiline_aligned', text=['test', 'right', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200))) def testDrawTextPointMultilineCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_center_multiline_aligned', text=['test', 'center', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_center_multiline_aligned', text=['test', 'center', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200))) def testDrawTextPointRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_right_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_right_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200))) def testDrawTextPointJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_justify_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_justify_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200))) def testDrawTextPointMultilineJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_justify_multiline_aligned', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_justify_multiline_aligned', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200))) def testDrawTextPointCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_center_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_center_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200))) def testDrawTextDataDefinedColorPoint(self): format = QgsTextFormat() @@ -3702,7 +3721,7 @@ def testDrawTextDataDefinedColorPoint(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.Color, QgsProperty.fromExpression("'#bb00cc'")) - assert self.checkRenderPoint(format, 'text_dd_color_point', None, text=['test'], point=QPointF(50, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_dd_color_point', None, text=['test'], point=QPointF(50, 200))) def testDrawTextDataDefinedColorRect(self): format = QgsTextFormat() @@ -3711,8 +3730,8 @@ def testDrawTextDataDefinedColorRect(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.Color, QgsProperty.fromExpression("'#bb00cc'")) - assert self.checkRender(format, 'text_dd_color_rect', None, text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'text_dd_color_rect', None, text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 100, 100))) def testDrawTextDataDefinedBufferColorPoint(self): format = QgsTextFormat() @@ -3724,7 +3743,7 @@ def testDrawTextDataDefinedBufferColorPoint(self): QgsProperty.fromExpression("'#bb00cc'")) format.buffer().setEnabled(True) format.buffer().setSize(5) - assert self.checkRenderPoint(format, 'text_dd_buffer_color', None, text=['test'], point=QPointF(50, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_dd_buffer_color', None, text=['test'], point=QPointF(50, 200))) def testDrawTabPercent(self): format = QgsTextFormat() @@ -3755,9 +3774,9 @@ def testHtmlFormatting(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_formatting', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlTabPercent(self): format = QgsTextFormat() @@ -3796,9 +3815,9 @@ def testHtmlFormattingBuffer(self): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferScaleFactor(self): """ @@ -3814,9 +3833,9 @@ def testHtmlFormattingBufferScaleFactor(self): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_scale_workaround', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_scale_workaround', None, text=[ 't e s'], - point=QPointF(50, 200), enable_scale_workaround=True) + point=QPointF(50, 200), enable_scale_workaround=True)) def testHtmlFormattingMask(self): """ @@ -3830,9 +3849,9 @@ def testHtmlFormattingMask(self): format.setAllowHtmlFormatting(True) format.mask().setEnabled(True) format.mask().setSize(5) - assert self.checkRenderPoint(format, 'text_html_formatting_mask', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_mask', None, text=[ 't e s'], - point=QPointF(50, 200), render_mask=True) + point=QPointF(50, 200), render_mask=True)) def testHtmlFormattingMaskScaleFactor(self): """ @@ -3847,9 +3866,9 @@ def testHtmlFormattingMaskScaleFactor(self): format.setAllowHtmlFormatting(True) format.mask().setEnabled(True) format.mask().setSize(5) - assert self.checkRenderPoint(format, 'text_html_formatting_mask_scale_workaround', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_mask_scale_workaround', None, text=[ 't e s'], - point=QPointF(50, 200), render_mask=True, enable_scale_workaround=True) + point=QPointF(50, 200), render_mask=True, enable_scale_workaround=True)) def testHtmlFormattingShadow(self): format = QgsTextFormat() @@ -3862,9 +3881,9 @@ def testHtmlFormattingShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_shadow', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferShadow(self): format = QgsTextFormat() @@ -3880,9 +3899,9 @@ def testHtmlFormattingBufferShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_shadow', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingVertical(self): format = QgsTextFormat() @@ -3892,9 +3911,9 @@ def testHtmlFormattingVertical(self): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_formatting_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_vertical', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferVertical(self): format = QgsTextFormat() @@ -3907,9 +3926,9 @@ def testHtmlFormattingBufferVertical(self): format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_vertical', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormatting(self): format = QgsTextFormat() @@ -3918,9 +3937,9 @@ def testHtmlMixedMetricFormatting(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricLineHeight(self): format = QgsTextFormat() @@ -3930,9 +3949,9 @@ def testHtmlMixedMetricLineHeight(self): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setLineHeight(0.5) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_line_height', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_line_height', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBuffer(self): format = QgsTextFormat() @@ -3944,9 +3963,9 @@ def testHtmlMixedMetricFormattingBuffer(self): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingShadow(self): format = QgsTextFormat() @@ -3959,9 +3978,9 @@ def testHtmlMixedMetricFormattingShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferShadow(self): format = QgsTextFormat() @@ -3977,9 +3996,9 @@ def testHtmlMixedMetricFormattingBufferShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingVertical(self): format = QgsTextFormat() @@ -3989,9 +4008,9 @@ def testHtmlMixedMetricFormattingVertical(self): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferVertical(self): format = QgsTextFormat() @@ -4004,9 +4023,9 @@ def testHtmlMixedMetricFormattingBufferVertical(self): format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingShadowVertical(self): format = QgsTextFormat() @@ -4020,9 +4039,9 @@ def testHtmlMixedMetricFormattingShadowVertical(self): format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferShadowVertical(self): format = QgsTextFormat() @@ -4039,9 +4058,9 @@ def testHtmlMixedMetricFormattingBufferShadowVertical(self): format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlHeadings(self): format = QgsTextFormat() @@ -4050,9 +4069,9 @@ def testHtmlHeadings(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_headings', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_headings', None, text=[ '

h1

h2

h3

h4

h5
h6
'], - point=QPointF(10, 300)) + point=QPointF(10, 300))) def testHtmlHeadingsLargerFont(self): format = QgsTextFormat() @@ -4061,9 +4080,9 @@ def testHtmlHeadingsLargerFont(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_headings_larger', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_headings_larger', None, text=[ '

h1

h2

h3

h4

h5
h6
'], - point=QPointF(10, 350)) + point=QPointF(10, 350))) def testHtmlAlignmentLeftBase(self): format = QgsTextFormat() @@ -4072,9 +4091,9 @@ def testHtmlAlignmentLeftBase(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_left_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_left_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300)) + rect=QRectF(10, 10, 300, 300))) def testHtmlAlignmentRightBase(self): format = QgsTextFormat() @@ -4083,9 +4102,9 @@ def testHtmlAlignmentRightBase(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_right_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_right_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Right) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Right)) def testHtmlAlignmentCenterBase(self): format = QgsTextFormat() @@ -4094,9 +4113,9 @@ def testHtmlAlignmentCenterBase(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_center_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_center_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoSize(self): format = QgsTextFormat() @@ -4112,9 +4131,9 @@ def testHtmlImageAutoSize(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autosize', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autosize', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoWidth(self): format = QgsTextFormat() @@ -4130,9 +4149,9 @@ def testHtmlImageAutoWidth(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autowidth', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autowidth', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoHeight(self): format = QgsTextFormat() @@ -4148,9 +4167,9 @@ def testHtmlImageAutoHeight(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autoheight', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autoheight', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageFixedSize(self): format = QgsTextFormat() @@ -4166,9 +4185,9 @@ def testHtmlImageFixedSize(self): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_fixed_size', None, text=[ + self.assertTrue(self.checkRender(format, 'image_fixed_size', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlSuperSubscript(self): format = QgsTextFormat() @@ -4177,9 +4196,9 @@ def testHtmlSuperSubscript(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_supersubscript', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptFixedFontSize(self): format = QgsTextFormat() @@ -4188,9 +4207,9 @@ def testHtmlSuperSubscriptFixedFontSize(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_supersubscript_fixed_font_size', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_fixed_font_size', None, text=[ 'suNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptBuffer(self): format = QgsTextFormat() @@ -4202,9 +4221,9 @@ def testHtmlSuperSubscriptBuffer(self): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_buffer', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptShadow(self): format = QgsTextFormat() @@ -4217,9 +4236,9 @@ def testHtmlSuperSubscriptShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_shadow', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptBufferShadow(self): format = QgsTextFormat() @@ -4235,9 +4254,9 @@ def testHtmlSuperSubscriptBufferShadow(self): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_buffer_shadow', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlWordSpacing(self): format = QgsTextFormat() @@ -4246,9 +4265,9 @@ def testHtmlWordSpacing(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_word_spacing', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testHtmlWordSpacingPx(self): format = QgsTextFormat() @@ -4259,9 +4278,9 @@ def testHtmlWordSpacingPx(self): format.setAllowHtmlFormatting(True) # unit should be ignored, we always treat it as pt as pixels don't # scale - assert self.checkRenderPoint(format, 'html_word_spacing', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testHtmlWordSpacingNegative(self): format = QgsTextFormat() @@ -4270,9 +4289,9 @@ def testHtmlWordSpacingNegative(self): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_word_spacing_negative', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing_negative', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testTextRenderFormat(self): format = QgsTextFormat() @@ -4358,8 +4377,8 @@ def testDrawTextVerticalRectMode(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode', QgsTextRenderer.TextPart.Text, text=['1234'], - rect=QRectF(40, 20, 350, 350)) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode', QgsTextRenderer.TextPart.Text, text=['1234'], + rect=QRectF(40, 20, 350, 350))) def testDrawTextVerticalRectModeCenterAligned(self): format = QgsTextFormat() @@ -4367,9 +4386,9 @@ def testDrawTextVerticalRectModeCenterAligned(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode_center_aligned', QgsTextRenderer.TextPart.Text, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode_center_aligned', QgsTextRenderer.TextPart.Text, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawTextVerticalRectModeRightAligned(self): format = QgsTextFormat() @@ -4377,9 +4396,9 @@ def testDrawTextVerticalRectModeRightAligned(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode_right_aligned', QgsTextRenderer.TextPart.Text, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode_right_aligned', QgsTextRenderer.TextPart.Text, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawTextVerticalPointMode(self): format = QgsTextFormat() @@ -4387,8 +4406,8 @@ def testDrawTextVerticalPointMode(self): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_vertical_point_mode', QgsTextRenderer.TextPart.Text, text=['1234', '5678'], - point=QPointF(40, 380)) + self.assertTrue(self.checkRenderPoint(format, 'text_vertical_point_mode', QgsTextRenderer.TextPart.Text, text=['1234', '5678'], + point=QPointF(40, 380))) def testDrawTextOnLineAtStart(self): format = QgsTextFormat() @@ -4663,9 +4682,9 @@ def testDrawTextDataDefinedProperties(self): QgsProperty.fromExpression('90*1.5') ) - assert self.checkRender(format, 'datadefined_render', None, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'datadefined_render', None, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignRight)) if __name__ == '__main__': diff --git a/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png b/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png new file mode 100644 index 000000000000..d6990d4314d7 Binary files /dev/null and b/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png differ diff --git a/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png new file mode 100644 index 000000000000..c8880c5dbdd9 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png differ diff --git a/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png new file mode 100644 index 000000000000..2de2dc93c92a Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz new file mode 100644 index 000000000000..187f03a3c9ca Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz new file mode 100644 index 000000000000..c9790a104afc Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz new file mode 100644 index 000000000000..e42d651409c6 Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz new file mode 100644 index 000000000000..e08a4a146bc9 Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc b/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc new file mode 100644 index 000000000000..b353cfea7b03 --- /dev/null +++ b/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc @@ -0,0 +1,853 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "0-0.copc", + "geometry": { + "coordinates": [ + [ + [ + 498062.0, + 7050992.85, + 74.68 + ], + [ + 498062.0, + 7050995.82, + 74.68 + ], + [ + 498064.98, + 7050995.82, + 75.16 + ], + [ + 498064.98, + 7050992.85, + 75.16 + ], + [ + 498062.0, + 7050992.85, + 74.68 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498062.0, + 7050992.85, + 74.68, + 498064.98, + 7050995.82, + 75.16 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 91, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498062.0, + 7050992.85, + 74.68, + 498064.98, + 7050995.82, + 75.16 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498062.0, + 7050992.85, + 74.68 + ], + [ + 498062.0, + 7050995.82, + 74.68 + ], + [ + 498064.98, + 7050995.82, + 75.16 + ], + [ + 498064.98, + 7050992.85, + 75.16 + ], + [ + 498062.0, + 7050992.85, + 74.68 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./0-0.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "0-1.copc", + "geometry": { + "coordinates": [ + [ + [ + 498062.04, + 7050995.84, + 74.64 + ], + [ + 498062.04, + 7050997.03, + 74.64 + ], + [ + 498064.91, + 7050997.03, + 79.0 + ], + [ + 498064.91, + 7050995.84, + 79.0 + ], + [ + 498062.04, + 7050995.84, + 74.64 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498062.04, + 7050995.84, + 74.64, + 498064.91, + 7050997.03, + 79.0 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 47, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498062.04, + 7050995.84, + 74.64, + 498064.91, + 7050997.03, + 79.0 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498062.04, + 7050995.84, + 74.64 + ], + [ + 498062.04, + 7050997.03, + 74.64 + ], + [ + 498064.91, + 7050997.03, + 79.0 + ], + [ + 498064.91, + 7050995.84, + 79.0 + ], + [ + 498062.04, + 7050995.84, + 74.64 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./0-1.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "1-0.copc", + "geometry": { + "coordinates": [ + [ + [ + 498065.0, + 7050992.84, + 74.46 + ], + [ + 498065.0, + 7050995.83, + 74.46 + ], + [ + 498067.39, + 7050995.83, + 74.91 + ], + [ + 498067.39, + 7050992.84, + 74.91 + ], + [ + 498065.0, + 7050992.84, + 74.46 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498065.0, + 7050992.84, + 74.46, + 498067.39, + 7050995.83, + 74.91 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 77, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498065.0, + 7050992.84, + 74.46, + 498067.39, + 7050995.83, + 74.91 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498065.0, + 7050992.84, + 74.46 + ], + [ + 498065.0, + 7050995.83, + 74.46 + ], + [ + 498067.39, + 7050995.83, + 74.91 + ], + [ + 498067.39, + 7050992.84, + 74.91 + ], + [ + 498065.0, + 7050992.84, + 74.46 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./1-0.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "1-1.copc", + "geometry": { + "coordinates": [ + [ + [ + 498065.01, + 7050995.9, + 74.34 + ], + [ + 498065.01, + 7050997.04, + 74.34 + ], + [ + 498067.32, + 7050997.04, + 80.02 + ], + [ + 498067.32, + 7050995.9, + 80.02 + ], + [ + 498065.01, + 7050995.9, + 74.34 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498065.01, + 7050995.9, + 74.34, + 498067.32, + 7050997.04, + 80.02 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 38, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498065.01, + 7050995.9, + 74.34, + 498067.32, + 7050997.04, + 80.02 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498065.01, + 7050995.9, + 74.34 + ], + [ + 498065.01, + 7050997.04, + 74.34 + ], + [ + 498067.32, + 7050997.04, + 80.02 + ], + [ + 498067.32, + 7050995.9, + 80.02 + ], + [ + 498065.01, + 7050995.9, + 74.34 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./1-1.copc.laz", + "roles": [ + "data" + ] + } + } + } + ] +} diff --git a/tests/testdata/stac/collectioncollection-sample-full.json b/tests/testdata/stac/collectioncollection-sample-full.json new file mode 100644 index 000000000000..e26616b726df --- /dev/null +++ b/tests/testdata/stac/collectioncollection-sample-full.json @@ -0,0 +1,85 @@ +{ + "collections": [ + { + "id": "simple-collection", + "type": "Collection", + "stac_extensions": [], + "stac_version": "1.0.0", + "description": "A simple collection demonstrating core collection fields in an API", + "title": "Simple Example Collection", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "links": [ + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + }, + { + "rel": "items", + "href": "http://stac.example.com/collections/simple-collection/items", + "type": "application/geo+json" + }, + { + "rel": "self", + "href": "http://stac.example.com/collections/simple-collection", + "type": "application/json" + } + ] + } + ], + "links": [ + { + "rel": "self", + "href": "http://stac.example.com/collections?page=2", + "type": "application/json" + }, + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + }, + { + "rel": "next", + "href": "http://stac.example.com/collections?page=3", + "type": "application/json" + }, + { + "rel": "prev", + "href": "http://stac.example.com/collections?page=1", + "type": "application/json" + } + ], + "numberMatched": 11, + "numberReturned": 1 +} diff --git a/tests/testdata/stac/itemcollection-sample-full.json b/tests/testdata/stac/itemcollection-sample-full.json new file mode 100644 index 000000000000..e2fe86b5d0cb --- /dev/null +++ b/tests/testdata/stac/itemcollection-sample-full.json @@ -0,0 +1,101 @@ +{ + "type": "FeatureCollection", + "numberMatched": 10, + "numberReturned": 1, + "features": [ + { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "cs3-20160503_132131_05", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.288048600, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "datetime": "2016-05-03T13:22:30.040Z", + "title": "A CS3 item", + "license": "PDDL-1.0", + "providers": [ + { + "name": "CoolSat", + "roles": [ + "producer", + "licensor" + ], + "url": "https://stac-api.example.com" + } + ] + }, + "collection": "cs3", + "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stac-api.example.com/collections/cs3/items/CS3-20160503_132131_05" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stac-api.example.com/" + }, + { + "rel": "collection", + "type": "application/json", + "href": "https://stac-api.example.com/collections/cs3" + } + ], + "assets": { + "analytic": { + "href": "https://stac-api.example.com/catalog/cs3-20160503_132130_04/analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic" + }, + "thumbnail": { + "href": "https://stac-api.example.com/catalog/cs3-20160503_132130_04/thumbnail.png", + "type": "image/png", + "title": "Thumbnail", + "roles": [ + "thumbnail" + ] + } + } + } + ], + "links": [ + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + } + ] +} \ No newline at end of file diff --git a/vcpkg/vcpkg.json b/vcpkg/vcpkg.json index b2716078a9cc..6454695bf7ba 100644 --- a/vcpkg/vcpkg.json +++ b/vcpkg/vcpkg.json @@ -2,8 +2,7 @@ "vcpkg-configuration": { "default-registry": { "kind": "git", - "baseline": "7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3", - "reference": "7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3", + "baseline": "41626fd77bf42f29e8f7e43dc1f2f05780588cde", "repository": "https://github.com/microsoft/vcpkg" }, "registries": [ @@ -60,6 +59,8 @@ }, "libxml2", "libzip", + "meshoptimizer", + "nlohmann-json", "pdal", "proj", "protobuf",