diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 106f2c6a576b..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,40 +0,0 @@ - - -## Expected behavior and actual behavior. - -For example (please modify !!!): I expected to be able to open this raster file (with a link to -the raster file, or it as an attachment) and it returns an error message -instead. - -## Steps to reproduce the problem. - -For example (please modify !!!): "gdalinfo myfile" - -## Operating system - -For example (please modify !!!): Ubuntu 16.04 64 bit - -## GDAL version and provenance - -For example (please modify !!!): the 2.2.3 version from ubuntugis-unstable PPA diff --git a/.github/ISSUE_TEMPLATE/10_bug_report.yml b/.github/ISSUE_TEMPLATE/10_bug_report.yml new file mode 100644 index 000000000000..e87303029ac9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/10_bug_report.yml @@ -0,0 +1,46 @@ +name: Bug report +description: Create a bug report. +labels: + - 'Bug' +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report correctly. Do NOT use GitHub to post any questions or support requests! They will be closed immediately and ignored. + + The GDAL project is made of contributions from various individuals and organizations, each with their own focus. The issue you are facing is not necessarily in the priority list of those contributors and consequently there is no guarantee that it will be addressed in a timely manner. If this bug report or feature request is high-priority for you, and you cannot address it yourself, we suggest engaging a GDAL developer or support organisation and financially sponsoring a fix. + + - type: textarea + id: what + attributes: + label: What is the bug? + description: | + If you think there is an issue with coordinate order in GDAL 3.0, then it is likely an intended behavior. See https://github.com/OSGeo/gdal/issues/1974 for more explanations. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce the issue + description: | + Steps, code extract and/or sample datasets to reproduce the behavior. + validations: + required: true + + - type: textarea + id: about-info + attributes: + label: Versions and provenance + description: | + Please indicate the operating system (e.g "Windows 11", or "Linux Ubuntu 22.04") and the GDAL version, for example by pasting the output of ``gdalinfo --version``. Please also indicate how you got the GDAL binary. For example: self-built, package coming from the Linux distribution, from OSGeo4W, from Conda-Forge, from vcpkg, etc. + Note that the GDAL project supports only the current version https://gdal.org/download.html#current-release so it should be used for testing. Or at least the newest easily available version for your platform. + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: | + Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/20_feature_request.yml b/.github/ISSUE_TEMPLATE/20_feature_request.yml new file mode 100644 index 000000000000..c9b7d0c11d10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/20_feature_request.yml @@ -0,0 +1,25 @@ +name: Feature request +description: Suggest a feature idea. +labels: + - 'enhancement' +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request correctly. + + - type: textarea + id: what + attributes: + label: Feature description + description: A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: Additional + attributes: + label: Additional context + description: | + Add any other context about the feature request here. Open source is community driven, please consider a way to support this work either by hiring developers, supporting the GDAL project, find someone to submit a pull request. + If the change required is important, you should consider writing a [GDAL RFC](https://gdal.org/development/rfc/index.html) or hiring someone to, and announce your work on the maiing list. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..879d3256f9b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +blank_issues_enabled: false + +contact_links: + + - name: Question + url: https://gis.stackexchange.com/ + about: > + Questions should go to the gdal-dev mailing list at https://lists.osgeo.org/mailman/listinfo/gdal-dev + or other support forums such as https://gis.stackexchange.com/. + GitHub issues are for bug reports and suggestions for new features. diff --git a/.github/workflows/android_cmake.yml b/.github/workflows/android_cmake.yml index 6d1f232161ab..efc7f8bfd2c2 100644 --- a/.github/workflows/android_cmake.yml +++ b/.github/workflows/android_cmake.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 id: cache with: path: | diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 8111b4b4f575..d0aa3d14695f 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -33,7 +33,7 @@ jobs: - name: Checkout GDAL uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 id: cache with: path: ${{ github.workspace }}/.ccache @@ -325,7 +325,7 @@ jobs: mingw-w64-x86_64-libgeotiff mingw-w64-x86_64-libpng mingw-w64-x86_64-libtiff mingw-w64-x86_64-openjpeg2 mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-numpy mingw-w64-x86_64-python-pytest mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-python-lxml mingw-w64-x86_64-swig mingw-w64-x86_64-python-psutil - name: Setup cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 id: cache with: path: ${{ github.workspace }}\.ccache @@ -409,7 +409,7 @@ jobs: shell: pwsh run: | echo "JAVA_HOME=$env:JAVA_HOME_11_X64" >> %GITHUB_ENV% - - uses: conda-incubator/setup-miniconda@392cf345b1784333caa1a1185081c71e6ffd61bc # v3.0.2 + - uses: conda-incubator/setup-miniconda@030178870c779d9e5e1b4e563269f3aa69b04081 # v3.0.3 with: activate-environment: gdalenv miniforge-variant: Mambaforge @@ -506,7 +506,7 @@ jobs: git config --global core.autocrlf false - name: Checkout GDAL uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: conda-incubator/setup-miniconda@392cf345b1784333caa1a1185081c71e6ffd61bc # v3.0.2 + - uses: conda-incubator/setup-miniconda@030178870c779d9e5e1b4e563269f3aa69b04081 # v3.0.3 with: activate-environment: gdalenv miniforge-variant: Mambaforge @@ -574,7 +574,7 @@ jobs: - name: Checkout GDAL uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup cache - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 id: cache with: path: ${{ github.workspace }}/.ccache @@ -653,7 +653,7 @@ jobs: git config --global core.autocrlf false - name: Checkout GDAL uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: conda-incubator/setup-miniconda@392cf345b1784333caa1a1185081c71e6ffd61bc # v3.0.2 + - uses: conda-incubator/setup-miniconda@030178870c779d9e5e1b4e563269f3aa69b04081 # v3.0.3 with: activate-environment: gdalenv python-version: 3.9 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index daa0604025ba..ec48de50551c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -45,7 +45,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -112,7 +112,7 @@ jobs: - name: Restore build cache if: matrix.language == 'c-cpp' id: restore-cache - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ matrix.id }}-${{ steps.get-arch.outputs.arch }}-${{ github.ref_name }}-${{ github.run_id }} @@ -143,12 +143,12 @@ jobs: - name: Save build cache if: matrix.language == 'c-cpp' - uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ steps.restore-cache.outputs.cache-primary-key }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml index ddcfa46891e9..bec14fb0f946 100644 --- a/.github/workflows/conda.yml +++ b/.github/workflows/conda.yml @@ -42,12 +42,12 @@ jobs: if: matrix.platform == 'windows-latest' - name: Cache Conda Environment - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ~/conda_pkgs_dir key: ${{ runner.os }}-${{ steps.get-date.outputs.today }}-conda-${{ env.CACHE_NUMBER }} - - uses: conda-incubator/setup-miniconda@392cf345b1784333caa1a1185081c71e6ffd61bc # v3.0.2 + - uses: conda-incubator/setup-miniconda@030178870c779d9e5e1b4e563269f3aa69b04081 # v3.0.3 with: #miniforge-variant: Mambaforge miniforge-version: latest diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index 7f855b968121..64a90f5c5db4 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -210,7 +210,7 @@ jobs: # different architecture. - name: Restore build cache id: restore-cache - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ matrix.id }}-${{ steps.get-arch.outputs.arch }}-${{ github.ref_name }}-${{ github.run_id }} @@ -258,7 +258,7 @@ jobs: ccache -s - name: Save build cache - uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ steps.restore-cache.outputs.cache-primary-key }} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 28e3d5e5bf23..4ab2d0be775b 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: conda-incubator/setup-miniconda@392cf345b1784333caa1a1185081c71e6ffd61bc # v3.0.2 + - uses: conda-incubator/setup-miniconda@030178870c779d9e5e1b4e563269f3aa69b04081 # v3.0.3 with: channels: conda-forge auto-update-conda: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 71e2979ebde0..8bd577c9892f 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: sarif_file: results.sarif diff --git a/.github/workflows/slow_tests.yml b/.github/workflows/slow_tests.yml index 4e1ea8e7926b..b2f08f6a7d78 100644 --- a/.github/workflows/slow_tests.yml +++ b/.github/workflows/slow_tests.yml @@ -107,7 +107,7 @@ jobs: # different architecture. - name: Restore build cache id: restore-cache - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/restore@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ matrix.id }}-${{ steps.get-arch.outputs.arch }}-${{ github.ref_name }}-${{ github.run_id }} @@ -147,7 +147,7 @@ jobs: ccache -s - name: Save build cache - uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/save@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 with: path: ${{ github.workspace }}/.ccache key: ${{ steps.restore-cache.outputs.cache-primary-key }} diff --git a/alg/gdal_alg.h b/alg/gdal_alg.h index ee15033a7810..e76b76f1adb7 100644 --- a/alg/gdal_alg.h +++ b/alg/gdal_alg.h @@ -257,6 +257,8 @@ GDALSuggestedWarpOutput(GDALDatasetH hSrcDS, GDALTransformerFunc pfnTransformer, /** Flag for GDALSuggestedWarpOutput2() to ask to round-up output size */ #define GDAL_SWO_ROUND_UP_SIZE 0x1 +/** Flag for GDALSuggestedWarpOutput2() to ask to force square pixels */ +#define GDAL_SWO_FORCE_SQUARE_PIXEL 0x2 CPLErr CPL_DLL CPL_STDCALL GDALSuggestedWarpOutput2( GDALDatasetH hSrcDS, GDALTransformerFunc pfnTransformer, diff --git a/alg/gdalpansharpen.cpp b/alg/gdalpansharpen.cpp index beccf909ec8a..952f0f5b7d06 100644 --- a/alg/gdalpansharpen.cpp +++ b/alg/gdalpansharpen.cpp @@ -307,7 +307,7 @@ GDALPansharpenOperation::Initialize(const GDALPansharpenOptions *psOptionsIn) CPLError(CE_Failure, CPLE_AppDefined, "Dimensions of input spectral band %d different from " "first spectral band", - i); + i + 1); return CE_Failure; } @@ -317,8 +317,8 @@ GDALPansharpenOperation::Initialize(const GDALPansharpenOptions *psOptionsIn) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot retrieve dataset associated with " - "ahInputSpectralBands[%i]", - i); + "input spectral band %d", + i + 1); return CE_Failure; } // Make sure that the band is really a first level child of the owning dataset @@ -334,16 +334,16 @@ GDALPansharpenOperation::Initialize(const GDALPansharpenOptions *psOptionsIn) { CPLError( CE_Failure, CPLE_AppDefined, - "ahInputSpectralBands[%d] band has no associated geotransform", - i); + "input spectral band %d band has no associated geotransform", + i + 1); return CE_Failure; } if (adfMSGT != adfRefMSGT) { CPLError(CE_Failure, CPLE_AppDefined, - "ahInputSpectralBands[%d] band has a different " - "geotransform than ahInputSpectralBands[0]", - i); + "input spectral band %d has a different " + "geotransform than the first spectral band", + i + 1); return CE_Failure; } diff --git a/alg/gdaltransformer.cpp b/alg/gdaltransformer.cpp index 496c20ea8995..f0950fd5d389 100644 --- a/alg/gdaltransformer.cpp +++ b/alg/gdaltransformer.cpp @@ -75,6 +75,18 @@ static void *GDALCreateApproxTransformer2(GDALTransformerFunc pfnRawTransformer, double dfMaxErrorForward, double dfMaxErrorReverse); +static bool IsTransformer(void *hTransformerArg, const char *pszClassName) +{ + if (!hTransformerArg) + return false; + // All transformers should have a GDALTransformerInfo member as their first members + GDALTransformerInfo *psInfo = + static_cast(hTransformerArg); + return memcmp(psInfo->abySignature, GDAL_GTI2_SIGNATURE, + strlen(GDAL_GTI2_SIGNATURE)) == 0 && + strcmp(psInfo->pszClassName, pszClassName) == 0; +} + /************************************************************************/ /* ==================================================================== */ /* GDALGenImgProjTransformer */ @@ -106,6 +118,9 @@ typedef struct // GDALRefreshGenImgProjTransformer() must do something or not. bool bCheckWithInvertPROJ; + // Set to TRUE when the transformation pipline is a custom one. + bool bHasCustomTransformationPipeline; + } GDALGenImgProjTransformInfo; /************************************************************************/ @@ -396,8 +411,9 @@ static int GDALSuggestedWarpOutput2_MustAdjustForBottomBorder( * @param pnLines int in which the suggest pixel height of output is returned. * @param padfExtent Four entry array to return extents as (xmin, ymin, xmax, * ymax). - * @param nOptions Options. Zero or GDAL_SWO_ROUND_UP_SIZE to ask *pnPixels - * and *pnLines to be rounded up instead of being rounded to the closes integer. + * @param nOptions Options flags. Zero or GDAL_SWO_ROUND_UP_SIZE to ask *pnPixels + * and *pnLines to be rounded up instead of being rounded to the closes integer, or + * GDAL_SWO_FORCE_SQUARE_PIXEL to indicate that the generated pixel size is a square. * * @return CE_None if successful or CE_Failure otherwise. */ @@ -411,15 +427,19 @@ CPLErr CPL_STDCALL GDALSuggestedWarpOutput2(GDALDatasetH hSrcDS, { VALIDATE_POINTER1(hSrcDS, "GDALSuggestedWarpOutput2", CE_Failure); + const bool bIsGDALGenImgProjTransform{ + pTransformArg && + IsTransformer(pTransformArg, "GDALGenImgProjTransformer")}; + /* -------------------------------------------------------------------- */ /* Setup sample points all around the edge of the input raster. */ /* -------------------------------------------------------------------- */ - if (pfnTransformer == GDALGenImgProjTransform) + if (bIsGDALGenImgProjTransform) { // In case CHECK_WITH_INVERT_PROJ has been modified. GDALRefreshGenImgProjTransformer(pTransformArg); } - else if (pfnTransformer == GDALApproxTransform) + else if (IsTransformer(pTransformArg, "GDALApproxTransformer")) { // In case CHECK_WITH_INVERT_PROJ has been modified. GDALRefreshApproxTransformer(pTransformArg); @@ -428,6 +448,83 @@ CPLErr CPL_STDCALL GDALSuggestedWarpOutput2(GDALDatasetH hSrcDS, const int nInXSize = GDALGetRasterXSize(hSrcDS); const int nInYSize = GDALGetRasterYSize(hSrcDS); + /* ------------------------------------------------------------- */ + /* Special case for warping on the same (or null) CRS. */ + /* ------------------------------------------------------------- */ + if ((!nOptions || (nOptions & GDAL_SWO_FORCE_SQUARE_PIXEL) == 0) && + pTransformArg && bIsGDALGenImgProjTransform) + { + double adfGeoTransform[6]; + + if (GDALGetGeoTransform(hSrcDS, adfGeoTransform) == CE_None && + adfGeoTransform[2] == 0.0 && adfGeoTransform[4] == 0.0) + { + GDALGenImgProjTransformInfo *psInfo{ + static_cast(pTransformArg)}; + + if (psInfo && !psInfo->pSrcTransformer && + !psInfo->bHasCustomTransformationPipeline && + !psInfo->pDstTransformer && + psInfo->adfDstGeoTransform[0] == 0 && + psInfo->adfDstGeoTransform[1] == 1 && + psInfo->adfDstGeoTransform[2] == 0 && + psInfo->adfDstGeoTransform[3] == 0 && + psInfo->adfDstGeoTransform[4] == 0 && + psInfo->adfDstGeoTransform[5] == 1) + { + const OGRSpatialReference *poSourceCRS = nullptr; + const OGRSpatialReference *poTargetCRS = nullptr; + + if (psInfo->pReprojectArg) + { + const GDALReprojectionTransformInfo *psRTI = + static_cast( + psInfo->pReprojectArg); + poSourceCRS = psRTI->poForwardTransform->GetSourceCS(); + poTargetCRS = psRTI->poForwardTransform->GetTargetCS(); + } + + if ((!poSourceCRS && !poTargetCRS) || + (poSourceCRS && poTargetCRS && + poSourceCRS->IsSame(poTargetCRS))) + { + + const bool bNorthUp{adfGeoTransform[5] < 0.0}; + + memcpy(padfGeoTransformOut, adfGeoTransform, + sizeof(double) * 6); + + if (!bNorthUp) + { + padfGeoTransformOut[3] = + padfGeoTransformOut[3] + + nInYSize * padfGeoTransformOut[5]; + padfGeoTransformOut[5] = -padfGeoTransformOut[5]; + } + + *pnPixels = nInXSize; + *pnLines = nInYSize; + + // Calculate extent from hSrcDS + if (padfExtent) + { + padfExtent[0] = adfGeoTransform[0]; + padfExtent[1] = + adfGeoTransform[3] + nInYSize * adfGeoTransform[5]; + padfExtent[2] = + adfGeoTransform[0] + nInXSize * adfGeoTransform[1]; + padfExtent[3] = adfGeoTransform[3]; + if (!bNorthUp) + { + std::swap(padfExtent[1], padfExtent[3]); + } + } + return CE_None; + } + } + } + } + const int N_PIXELSTEP = 50; int nSteps = static_cast( static_cast(std::min(nInYSize, nInXSize)) / N_PIXELSTEP + 0.5); @@ -1256,6 +1353,7 @@ static GDALGenImgProjTransformInfo *GDALCreateGenImgProjTransformerInternal() psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarGenImgProjTransformer; psInfo->bCheckWithInvertPROJ = GetCurrentCheckWithInvertPROJ(); + psInfo->bHasCustomTransformationPipeline = false; return psInfo; } @@ -2471,6 +2569,11 @@ void *GDALCreateGenImgProjTransformer2(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, : nullptr, aosOptions.List()); + if (pszCO) + { + psInfo->bHasCustomTransformationPipeline = true; + } + if (psInfo->pReprojectArg == nullptr) { GDALDestroyGenImgProjTransformer(psInfo); diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index 20d530d70569..234fae858594 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -110,9 +110,9 @@ inline static double CalcHeight(double dfZ, double dfZ2, GDALViewshedMode eMode) if (eMode == GVM_Edge) return dfZ2; else if (eMode == GVM_Max) - return (std::max)(dfZ, dfZ2); + return std::max(dfZ, dfZ2); else if (eMode == GVM_Min) - return (std::min)(dfZ, dfZ2); + return std::min(dfZ, dfZ2); else return dfZ; } @@ -282,29 +282,38 @@ GDALDatasetH GDALViewshedGenerate( } /* calculate the area of interest */ + constexpr double EPSILON = 1e-8; int nXStart = dfMaxDistance > 0 - ? (std::max)(0, static_cast(std::floor( - nX - adfInvGeoTransform[1] * dfMaxDistance))) + ? std::max( + 0, static_cast(std::floor( + nX - adfInvGeoTransform[1] * dfMaxDistance + EPSILON))) : 0; int nXStop = dfMaxDistance > 0 - ? (std::min)(nXSize, - static_cast(std::ceil(nX + adfInvGeoTransform[1] * - dfMaxDistance) + - 1)) + ? std::min( + nXSize, + static_cast( + std::ceil(nX + adfInvGeoTransform[1] * dfMaxDistance - + EPSILON) + + 1)) : nXSize; int nYStart = dfMaxDistance > 0 - ? (std::max)(0, static_cast(std::floor( - nY + adfInvGeoTransform[5] * dfMaxDistance))) + ? std::max( + 0, static_cast(std::floor( + nY - std::fabs(adfInvGeoTransform[5]) * dfMaxDistance + + EPSILON)) - + (adfInvGeoTransform[5] > 0 ? 1 : 0)) : 0; int nYStop = dfMaxDistance > 0 - ? (std::min)(nYSize, - static_cast(std::ceil(nY - adfInvGeoTransform[5] * - dfMaxDistance) + - 1)) + ? std::min(nYSize, static_cast( + std::ceil(nY + + std::fabs(adfInvGeoTransform[5]) * + dfMaxDistance - + EPSILON) + + (adfInvGeoTransform[5] < 0 ? 1 : 0))) : nYSize; /* normalize horizontal index (0 - nXSize) */ diff --git a/apps/data/gdalinfo_output.schema.json b/apps/data/gdalinfo_output.schema.json index 04055fcdabe9..166a490d9e26 100644 --- a/apps/data/gdalinfo_output.schema.json +++ b/apps/data/gdalinfo_output.schema.json @@ -176,6 +176,7 @@ } }, "size": { + "$comment": "note that the order of items in side is width,height", "$ref": "#/definitions/arrayOfTwoIntegers" }, "coordinateSystem": { @@ -306,6 +307,7 @@ }, "proj:shape": { + "$comment": "note that the order of items in proj:shape is height,width starting with GDAL 3.8.5 (previous versions ordered it wrongly as width,height)", "title": "Shape", "type": "array", "minItems": 2, diff --git a/apps/gdalinfo_lib.cpp b/apps/gdalinfo_lib.cpp index 577fc617b962..13524b5b507e 100644 --- a/apps/gdalinfo_lib.cpp +++ b/apps/gdalinfo_lib.cpp @@ -342,19 +342,33 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions) if (bJson) { - json_object *poSize = json_object_new_array(); - json_object *poSizeX = - json_object_new_int(GDALGetRasterXSize(hDataset)); - json_object *poSizeY = - json_object_new_int(GDALGetRasterYSize(hDataset)); - - json_object_array_add(poSize, poSizeX); - json_object_array_add(poSize, poSizeY); - - json_object *poStacSize = nullptr; - json_object_deep_copy(poSize, &poStacSize, nullptr); - json_object_object_add(poJsonObject, "size", poSize); - json_object_object_add(poStac, "proj:shape", poStacSize); + { + json_object *poSize = json_object_new_array(); + json_object *poSizeX = + json_object_new_int(GDALGetRasterXSize(hDataset)); + json_object *poSizeY = + json_object_new_int(GDALGetRasterYSize(hDataset)); + + // size is X, Y ordered + json_object_array_add(poSize, poSizeX); + json_object_array_add(poSize, poSizeY); + + json_object_object_add(poJsonObject, "size", poSize); + } + + { + json_object *poStacSize = json_object_new_array(); + json_object *poSizeX = + json_object_new_int(GDALGetRasterXSize(hDataset)); + json_object *poSizeY = + json_object_new_int(GDALGetRasterYSize(hDataset)); + + // ... but ... proj:shape is Y, X ordered. + json_object_array_add(poStacSize, poSizeY); + json_object_array_add(poStacSize, poSizeX); + + json_object_object_add(poStac, "proj:shape", poStacSize); + } } else { diff --git a/apps/gdaltransform.cpp b/apps/gdaltransform.cpp index fe2a5cb2509b..e161638b9432 100644 --- a/apps/gdaltransform.cpp +++ b/apps/gdaltransform.cpp @@ -67,7 +67,7 @@ static void Usage(bool bIsError, const char *pszErrorMsg = nullptr) " [-ct ] [-order ] [-tps] [-rpc] [-geoloc] \n" " [-gcp [elevation]]... " "[-output_xy]\n" - " [srcfile [dstfile]]\n" + " [ []]\n" "\n"); if (pszErrorMsg != nullptr) diff --git a/apps/gdalwarp_lib.cpp b/apps/gdalwarp_lib.cpp index ed17f2290f74..695c2bcd4b59 100644 --- a/apps/gdalwarp_lib.cpp +++ b/apps/gdalwarp_lib.cpp @@ -2798,12 +2798,10 @@ static GDALDatasetH GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, * would be for this */ /* input dataset */ double adfSuggestedGeoTransform[6]; - double adfExtent[4]; int nPixels, nLines; - if (GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, - hTransformArg, - adfSuggestedGeoTransform, &nPixels, - &nLines, adfExtent, 0) == CE_None) + if (GDALSuggestedWarpOutput( + hSrcDS, pfnTransformer, hTransformArg, + adfSuggestedGeoTransform, &nPixels, &nLines) == CE_None) { dfTargetRatio = 1.0 / adfSuggestedGeoTransform[1]; } @@ -3977,9 +3975,13 @@ static GDALDatasetH GDALWarpCreateOutput( // For sum, round-up dimension, to be sure that the output extent // includes all source pixels, to have the sum preserving property. - const int nOptions = (psOptions->eResampleAlg == GRA_Sum) - ? GDAL_SWO_ROUND_UP_SIZE - : 0; + int nOptions = (psOptions->eResampleAlg == GRA_Sum) + ? GDAL_SWO_ROUND_UP_SIZE + : 0; + if (psOptions->bSquarePixels) + { + nOptions |= GDAL_SWO_FORCE_SQUARE_PIXEL; + } if (GDALSuggestedWarpOutput2(hSrcDS, psInfo->pfnTransform, hTransformArg, adfThisGeoTransform, &nThisPixels, &nThisLines, adfExtent, diff --git a/apps/ogr2ogr_bin.cpp b/apps/ogr2ogr_bin.cpp index 9558e220967f..18fc96b38e1b 100644 --- a/apps/ogr2ogr_bin.cpp +++ b/apps/ogr2ogr_bin.cpp @@ -386,10 +386,9 @@ MAIN_START(nArgc, papszArgv) { GDALDriverManager *poDM = GetGDALDriverManager(); - fprintf(stderr, - "FAILURE:\n" - "Unable to open datasource `%s' with the following drivers.\n", - psOptionsForBinary->osDataSource.c_str()); + CPLError(CE_Failure, CPLE_AppDefined, + "Unable to open datasource `%s' with the following drivers.", + psOptionsForBinary->osDataSource.c_str()); for (int iDriver = 0; iDriver < poDM->GetDriverCount(); iDriver++) { diff --git a/autotest/alg/warp.py b/autotest/alg/warp.py index b128930a9e9c..2729fd4f3264 100755 --- a/autotest/alg/warp.py +++ b/autotest/alg/warp.py @@ -1669,3 +1669,74 @@ def test_warp_average_oversampling(): assert math.isnan(got_data[(y + 4) * 14 + (14 - 1 - x)]) for x in range(6): assert got_data[(y + 4) * 14 + (x + 4)] == 3.0 + + +############################################################################### +# Test bug fix for https://github.com/qgis/QGIS/issues/56288 + + +@pytest.mark.require_driver("XYZ") +def test_non_square(): + + # bottom up + content = """y x z +30.0 10.0 1 +30.0 10.2 2 +30.0 10.4 3 +30.1 10.0 4 +30.1 10.2 5 +30.1 10.4 6 +30.2 10.0 7 +30.2 10.2 8 +30.2 10.4 9 +""" + + with gdaltest.tempfile("/vsimem/grid.xyz", content): + ds = gdal.Open("/vsimem/grid.xyz") + assert ds.RasterXSize == 3 and ds.RasterYSize == 3 + assert ds.GetGeoTransform() == pytest.approx((9.9, 0.2, 0.0, 29.95, 0.0, 0.1)) + ulx, xres, xskew, uly, yskew, yres = ds.GetGeoTransform() + lrx = ulx + (ds.RasterXSize * xres) + lry = uly + (ds.RasterYSize * yres) + assert lrx == pytest.approx(10.5) + assert lry == pytest.approx(30.25) + assert ds.GetRasterBand(1).DataType == gdal.GDT_Byte + assert struct.unpack("b" * (3 * 3), ds.ReadRaster()) == ( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + ) + + warped = gdal.AutoCreateWarpedVRT(ds) + assert warped.GetRasterBand(1).DataType == gdal.GDT_Byte + assert ( + warped.RasterXSize == ds.RasterXSize + and warped.RasterYSize == ds.RasterYSize + ) + assert warped.GetGeoTransform() == pytest.approx( + (9.9, 0.2, 0.0, 30.25, 0.0, -0.1) + ) + assert struct.unpack("b" * (3 * 3), warped.ReadRaster()) == ( + 7, + 8, + 9, + 4, + 5, + 6, + 1, + 2, + 3, + ) + + # Test extent calculation + res = gdal.SuggestedWarpOutput(ds, []) + assert res.ymin == pytest.approx(29.95) + assert res.ymax == pytest.approx(30.25) + assert res.xmin == pytest.approx(9.9) + assert res.xmax == pytest.approx(10.5) diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 5828da330ef5..674bbbf9181d 100644 --- a/autotest/cpp/test_gdal.cpp +++ b/autotest/cpp/test_gdal.cpp @@ -2462,6 +2462,193 @@ TEST_F(test_gdal, GDALBufferHasOnlyNoData) GSF_FLOATING_POINT)); } +// Test GetRasterNoDataReplacementValue() +TEST_F(test_gdal, GetRasterNoDataReplacementValue) +{ + // Test GDT_Byte + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Byte, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Byte, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Byte, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Byte, std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_Int8 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int8, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Int8, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int8, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Int8, + std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_UInt16 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt16, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_UInt16, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt16, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt16, std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_Int16 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int16, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Int16, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int16, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int16, std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_UInt32 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt32, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_UInt32, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt32, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt32, std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_Int32 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int32, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Int32, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int32, std::numeric_limits::lowest()), + std::numeric_limits::lowest() + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int32, std::numeric_limits::max()), + std::numeric_limits::max() - 1); + + // Test GDT_UInt64 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt64, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_UInt64, + std::numeric_limits::max()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_UInt64, + static_cast(std::numeric_limits::lowest())), + static_cast(std::numeric_limits::lowest()) + 1); + // uin64_t max is not representable in double so we expect the next value to be returned + EXPECT_EQ( + GDALGetNoDataReplacementValue( + GDT_UInt64, + static_cast(std::numeric_limits::max())), + std::nextafter( + static_cast(std::numeric_limits::max()), 0) - + 1); + + // Test GDT_Int64 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int64, std::numeric_limits::lowest()), + 0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Int64, + std::numeric_limits::max()), + 0); + // in64_t max is not representable in double so we expect the next value to be returned + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int64, + static_cast(std::numeric_limits::lowest())), + static_cast(std::numeric_limits::lowest()) + 1); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Int64, + static_cast(std::numeric_limits::max())), + std::nextafter( + static_cast(std::numeric_limits::max()), 0) - + 1); + + // Test floating point types + + // out of range for float32 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float32, std::numeric_limits::lowest()), + 0.0); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float32, + std::numeric_limits::max()), + 0.0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float32, std::numeric_limits::infinity()), + 0.0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float32, -std::numeric_limits::infinity()), + 0.0); + + // in range for float 32 + EXPECT_EQ( + static_cast(GDALGetNoDataReplacementValue(GDT_Float32, -1.0)), + std::nextafter(float(-1.0), 0.0f)); + EXPECT_EQ( + static_cast(GDALGetNoDataReplacementValue(GDT_Float32, 1.1)), + std::nextafter(float(1.1), 2.0f)); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float32, std::numeric_limits::lowest()), + std::nextafter(std::numeric_limits::lowest(), 0.0f)); + + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float32, + std::numeric_limits::max()), + static_cast( + std::nextafter(std::numeric_limits::max(), 0.0f))); + + // in range for float64 + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float64, std::numeric_limits::lowest()), + std::nextafter(std::numeric_limits::lowest(), 0.0)); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float64, + std::numeric_limits::max()), + std::nextafter(std::numeric_limits::max(), 0.0)); + + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float64, std::numeric_limits::lowest()), + std::nextafter(std::numeric_limits::lowest(), 0.0)); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float64, + std::numeric_limits::max()), + std::nextafter(std::numeric_limits::max(), 0.0)); + + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float64, double(-1.0)), + std::nextafter(double(-1.0), 0.0)); + EXPECT_EQ(GDALGetNoDataReplacementValue(GDT_Float64, double(1.1)), + std::nextafter(double(1.1), 2.0)); + + // test infinity + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float64, std::numeric_limits::infinity()), + 0.0); + EXPECT_EQ(GDALGetNoDataReplacementValue( + GDT_Float64, -std::numeric_limits::infinity()), + 0.0); +} + // Test GDALRasterBand::GetIndexColorTranslationTo() TEST_F(test_gdal, GetIndexColorTranslationTo) { diff --git a/autotest/gcore/rasterio.py b/autotest/gcore/rasterio.py index 4e1ff0885380..58409a5de7c7 100755 --- a/autotest/gcore/rasterio.py +++ b/autotest/gcore/rasterio.py @@ -1265,14 +1265,14 @@ def test_rasterio_resampled_value_is_nodata(): buf_xsize=1, buf_ysize=1, resample_alg=gdal.GRIORA_Lanczos ) data_ar = struct.unpack("f" * 1, data) - expected_ar = (1.1754943508222875e-38,) + expected_ar = (1.401298464324817e-45,) assert data_ar == expected_ar data = ds.GetRasterBand(1).ReadRaster( buf_xsize=1, buf_ysize=1, resample_alg=gdal.GRIORA_Average ) data_ar = struct.unpack("f" * 1, data) - expected_ar = (1.1754943508222875e-38,) + expected_ar = (1.401298464324817e-45,) assert data_ar == expected_ar gdal.Unlink("/vsimem/in.asc") diff --git a/autotest/gcore/vrt_read.py b/autotest/gcore/vrt_read.py index f6abca4c25c0..0bc59ed1c072 100755 --- a/autotest/gcore/vrt_read.py +++ b/autotest/gcore/vrt_read.py @@ -2586,3 +2586,43 @@ def test_vrt_read_compute_statistics_approximate(tmp_vsimem): assert "STATISTICS_MEAN" in md assert "STATISTICS_STDDEV" in md assert md["STATISTICS_VALID_PERCENT"] == "100" + + +############################################################################### +# Test that when generating virtual overviews we select them as close as +# possible to source ones + + +def test_vrt_read_virtual_overviews_match_src_overviews(tmp_vsimem): + + gtiff_filename = str(tmp_vsimem / "tmp.tif") + ds = gdal.GetDriverByName("GTiff").Create( + gtiff_filename, 4095, 2047, options=["SPARSE_OK=YES"] + ) + ds.BuildOverviews("NONE", [2, 4, 8]) + band = ds.GetRasterBand(1) + # The below assert is a pre-condition to test the behavior of the + # "vrt://{gtiff_filename}?outsize=200%,200%". If we decided to round + # differently overview dataset sizes in the GTiff driver, we'd need to + # change this source dataset + assert band.GetOverview(0).XSize == 2048 + assert band.GetOverview(0).YSize == 1024 + assert band.GetOverview(1).XSize == 1024 + assert band.GetOverview(1).YSize == 512 + ds = None + + vrt_ds = gdal.Open(f"vrt://{gtiff_filename}?outsize=200%,200%") + assert vrt_ds.RasterXSize == 4095 * 2 + assert vrt_ds.RasterYSize == 2047 * 2 + vrt_band = vrt_ds.GetRasterBand(1) + assert vrt_band.XSize == 4095 * 2 + assert vrt_band.YSize == 2047 * 2 + assert vrt_band.GetOverviewCount() == 3 + # We reuse the dimension of the source dataset + assert vrt_band.GetOverview(0).XSize == 4095 + assert vrt_band.GetOverview(0).YSize == 2047 + # We reuse the dimension of the overviews of the source dataset + assert vrt_band.GetOverview(1).XSize == 2048 + assert vrt_band.GetOverview(1).YSize == 1024 + assert vrt_band.GetOverview(2).XSize == 1024 + assert vrt_band.GetOverview(2).YSize == 512 diff --git a/autotest/gdrivers/data/ers/references_ecw.ecw b/autotest/gdrivers/data/ers/references_ecw.ecw new file mode 100644 index 000000000000..ade6498f1606 Binary files /dev/null and b/autotest/gdrivers/data/ers/references_ecw.ecw differ diff --git a/autotest/gdrivers/data/ers/references_ecw.ers b/autotest/gdrivers/data/ers/references_ecw.ers new file mode 100644 index 000000000000..7c16a1b4b20d --- /dev/null +++ b/autotest/gdrivers/data/ers/references_ecw.ers @@ -0,0 +1,14 @@ +DatasetHeader Begin + Version = "6.2" + Name = "references_ecw.ers" + DataFile = "references_ecw.ecw" + DataSetType = Translated + DataType = Raster + ByteOrder = LSBFirst + RasterInfo Begin + CellType = Unsigned8BitInteger + NrOfLines = 512 + NrOfCellsPerLine = 519 + NrOfBands = 1 + RasterInfo End +DatasetHeader End diff --git a/autotest/gdrivers/dimap.py b/autotest/gdrivers/dimap.py index 95a7e066e702..9758e256362d 100755 --- a/autotest/gdrivers/dimap.py +++ b/autotest/gdrivers/dimap.py @@ -145,6 +145,14 @@ def test_dimap_2_single_component(): "GSD_ALONG_TRACK": "CENTER_GSD_ALONG_TRACK", "IMAGE_ORIENTATION": "CENTER_IMAGE_ORIENTATION", "AZIMUTH_ANGLE": "CENTER_AZIMUTH_ANGLE", + "RADIOMETRIC_RADIOMETRIC_PROCESSING": "BASIC", + "RADIOMETRIC_INTER_DETECTOR_NORMALIZATION": "false", + "RADIOMETRIC_DETECTORS_INTERPOLATION": "true", + "RADIOMETRIC_STRAYLIGHT_CORRECTION": "false", + "RADIOMETRIC_VCTI_CORRECTION": "false", + "RADIOMETRIC_INTER_ARRAY_RECONSTRUCTION": "true", + "RADIOMETRIC_RADIOMETRIC_STRETCH": "false", + "RADIOMETRIC_OUT_OF_ORDER_THRESHOLD": "0.5", } assert md == expected_md, "metadata wrong." diff --git a/autotest/gdrivers/ers.py b/autotest/gdrivers/ers.py index 82046df69d14..701580704ac9 100755 --- a/autotest/gdrivers/ers.py +++ b/autotest/gdrivers/ers.py @@ -319,6 +319,16 @@ def test_ers_recursive_opening(): ds.GetFileList() +############################################################################### +# Test fix for https://github.com/OSGeo/gdal/issues/9352 + + +@pytest.mark.require_driver("ECW") +def test_ers_open_data_file_ecw(): + + assert gdal.Open("data/ers/references_ecw.ers") + + ############################################################################### # Cleanup diff --git a/autotest/gdrivers/georaster.py b/autotest/gdrivers/georaster.py index 661fffe4beab..d5e312164dcb 100755 --- a/autotest/gdrivers/georaster.py +++ b/autotest/gdrivers/georaster.py @@ -54,7 +54,7 @@ def get_connection_str(): if oci_dsname is None: # TODO: Spelling - informe? return "" - return "geor:" + oci_dsname.split(":")[1] + return "geor:" + oci_dsname[oci_dsname.index(":") + 1 :] ############################################################################### @@ -488,6 +488,37 @@ def test_georaster_4bit(): # +def test_georaster_genstats(): + if gdaltest.oci_ds is None: + pytest.skip() + + ds_src = gdal.Open("data/byte.tif") + + ds = gdaltest.georasterDriver.CreateCopy( + get_connection_str() + ",GDAL_TEST,RASTER", + ds_src, + 1, + [ + "DESCRIPTION=(id number, raster sdo_georaster)", + "INSERT=(1014, sdo_geor.init('GDAL_TEST_RDT',1014))", + "NBITS=4", + "GENSTATS=TRUE", + ], + ) + + ds_name = ds.GetDescription() + + ds = None + + tst = gdaltest.GDALTest("GeoRaster", ds_name, 1, 2578, filename_absolute=1) + + tst.testOpen() + + +############################################################################### +# + + def test_georaster_cleanup(): if gdaltest.oci_ds is None: pytest.skip() diff --git a/autotest/gdrivers/vrtpansharpen.py b/autotest/gdrivers/vrtpansharpen.py index 58d158734d22..5215a6a25c10 100755 --- a/autotest/gdrivers/vrtpansharpen.py +++ b/autotest/gdrivers/vrtpansharpen.py @@ -2304,3 +2304,72 @@ def test_vrtpansharpen_out_of_order_input_bands_and_nodata(): cs2 = [vrt_ds.GetRasterBand(i + 1).Checksum() for i in range(vrt_ds.RasterCount)] assert cs2 == cs[::-1] + + +############################################################################### +# Test open options for input bands + + +def test_vrtpansharpen_open_options_input_bands(): + def my_handler(typ, errno, msg): + msgs.append(msg) + + msgs = [] + with gdaltest.error_handler(my_handler): + gdal.Open( + """ + + + tmp/small_world_pan.tif + + foo + + 1 + + + data/small_world.tif + 1 + + + data/small_world.tif + 2 + + + data/small_world.tif + 3 + + + """ + ) + # Not the prettiest way to check that open options are used, but that does the job... + assert "small_world_pan.tif: Invalid value for NUM_THREADS: foo" in msgs + + msgs = [] + with gdaltest.error_handler(my_handler): + gdal.Open( + """ + + + tmp/small_world_pan.tif + 1 + + + data/small_world.tif + + foo + + 1 + + + data/small_world.tif + 2 + + + data/small_world.tif + 3 + + + """ + ) + # Not the prettiest way to check that open options are used, but that does the job... + assert "small_world.tif: Invalid value for NUM_THREADS: foo" in msgs diff --git a/autotest/ogr/ogr_geom.py b/autotest/ogr/ogr_geom.py index 9cfe8bd5f1b3..b9aeee360974 100755 --- a/autotest/ogr/ogr_geom.py +++ b/autotest/ogr/ogr_geom.py @@ -2874,14 +2874,14 @@ def test_ogr_geom_getcurvegeometry(): # Add repeated point at end of line g2 = g1.GetLinearGeometry() - g2.AddPoint_2D(2 - 1e-9, 0) + g2.AddPoint_2D(2 - 1e-14, 0) g3 = g2.GetCurveGeometry() assert g3.Equals(g1) # Add repeated point at end of line g2 = g1.GetLinearGeometry() g2_new = ogr.Geometry(ogr.wkbLineString) - g2_new.AddPoint_2D(0, 1e-9) + g2_new.AddPoint_2D(0, 1e-14) for i in range(g2.GetPointCount()): g2_new.AddPoint_2D(g2.GetX(i), g2.GetY(i)) g3 = g2_new.GetCurveGeometry() @@ -3019,6 +3019,20 @@ def test_ogr_geom_getcurvegeometry_issue_8332(): ogrtest.check_feature_geometry(g3, expected_g, 1e-10) +############################################################################### + + +def test_ogr_geom_getcurvegeometry_issue9382(): + + wkt = "POLYGON ((299517.68 6695650.45,299519.85 6695648.29,299522.85 6695645.24,299523.253879511 6695644.9271687,299523.639375687 6695644.59194529,299524.00524874 6695644.23540783,299524.350321919 6695643.8587029,299524.673485444 6695643.46304214,299524.973699995 6695643.04969797,299525.25 6695642.62,299525.27 6695642.6,299525.28 6695642.57,299525.581819649 6695642.02405493,299525.844959794 6695641.4584502,299526.068147436 6695640.87592268,299526.250302851 6695640.27928972,299526.390544832 6695639.67143822,299526.488194942 6695639.05530858,299526.542780772 6695638.43388152,299526.554038241 6695637.81016326,299526.521912903 6695637.18717098,299526.446560174 6695636.56791901,299526.328344583 6695635.95540261,299526.167838007 6695635.35258508,299525.965816945 6695634.76238274,299525.723258704 6695634.18765068,299525.441336721 6695633.63116956,299525.121414825 6695633.09563136,299524.765040724 6695632.58362663,299524.373938437 6695632.09763257,299523.95 6695631.64,299522.14 6695633.39,299522.32 6695633.59,299522.48 6695633.77,299522.63 6695633.96,299522.77 6695634.15,299522.9 6695634.35,299523.03 6695634.55,299523.15 6695634.76,299523.26 6695634.98,299523.37 6695635.19,299523.46 6695635.41,299523.55 6695635.64,299523.63 6695635.86,299523.7 6695636.09,299523.76 6695636.32,299523.81 6695636.56,299523.86 6695636.79,299523.9 6695637.03,299523.92 6695637.27,299523.94 6695637.51,299523.95 6695637.71,299523.95 6695637.97,299523.94 6695638.23,299523.93 6695638.47,299523.9 6695638.71,299523.87 6695638.95,299523.82 6695639.18,299523.77 6695639.42,299523.71 6695639.65,299523.64 6695639.88,299523.56 6695640.11,299523.47 6695640.33,299523.38 6695640.55,299523.28 6695640.77,299523.17 6695640.98,299523.05 6695641.19,299522.92 6695641.4,299522.79 6695641.6,299522.65 6695641.79,299522.5 6695641.98,299522.34 6695642.16,299522.21 6695642.31,299522.08 6695642.45,299515.85 6695648.75,299515.41 6695649.19,299514.69 6695649.95,299513.99 6695650.71,299513.29 6695651.49,299512.902658228 6695651.94,299516.254162679 6695651.94,299517.68 6695650.45))" + g = ogr.CreateGeometryFromWkt(wkt) + g = g.GetCurveGeometry() + + expected_wkt = "CURVEPOLYGON (COMPOUNDCURVE ((299517.68 6695650.45,299519.85 6695648.29,299522.85 6695645.24),CIRCULARSTRING (299522.85 6695645.24,299524.00524874 6695644.23540783,299525.25 6695642.62),(299525.25 6695642.62,299525.27 6695642.6,299525.28 6695642.57),CIRCULARSTRING (299525.28 6695642.57,299526.521912903 6695637.18717098,299523.95 6695631.64),(299523.95 6695631.64,299522.14 6695633.39,299522.32 6695633.59,299522.48 6695633.77,299522.63 6695633.96,299522.77 6695634.15,299522.9 6695634.35,299523.03 6695634.55,299523.15 6695634.76,299523.26 6695634.98,299523.37 6695635.19,299523.46 6695635.41,299523.55 6695635.64,299523.63 6695635.86,299523.7 6695636.09,299523.76 6695636.32,299523.81 6695636.56,299523.86 6695636.79,299523.9 6695637.03,299523.92 6695637.27,299523.94 6695637.51,299523.95 6695637.71,299523.95 6695637.97,299523.94 6695638.23,299523.93 6695638.47,299523.9 6695638.71,299523.87 6695638.95,299523.82 6695639.18,299523.77 6695639.42,299523.71 6695639.65,299523.64 6695639.88,299523.56 6695640.11,299523.47 6695640.33,299523.38 6695640.55,299523.28 6695640.77,299523.17 6695640.98,299523.05 6695641.19,299522.92 6695641.4,299522.79 6695641.6,299522.65 6695641.79,299522.5 6695641.98,299522.34 6695642.16,299522.21 6695642.31,299522.08 6695642.45,299515.85 6695648.75,299515.41 6695649.19,299514.69 6695649.95,299513.99 6695650.71,299513.29 6695651.49,299512.902658228 6695651.94,299516.254162679 6695651.94,299517.68 6695650.45)))" + expected_g = ogr.CreateGeometryFromWkt(expected_wkt) + ogrtest.check_feature_geometry(g, expected_g, 1e-10) + + ############################################################################### # Test OGR_GT_ functions diff --git a/autotest/ogr/ogr_ili.py b/autotest/ogr/ogr_ili.py index 67155ba7ab56..b545d4756dbb 100755 --- a/autotest/ogr/ogr_ili.py +++ b/autotest/ogr/ogr_ili.py @@ -34,6 +34,7 @@ pytestmark = [ pytest.mark.require_driver("Interlis 1"), + pytest.mark.require_driver("Interlis 2"), pytest.mark.random_order(disabled=True), ] @@ -1197,3 +1198,22 @@ def test_ogr_interlis_arc2(): for i in range(feat.GetGeomFieldCount()): geom = feat.GetGeomFieldRef(i) ogrtest.check_feature_geometry(geom, geom_field_values[i]) + + +############################################################################### +# Test failure in creation of ILI2 dataset + + +def test_ogr_interlis2_create_file_error(): + + with pytest.raises( + Exception, match="model file not specified in destination filename" + ): + ogr.GetDriverByName("Interlis 2").CreateDataSource("tmp/out.xtf") + + with pytest.raises( + Exception, match="Failed to create XTF file /i_do/not/exist/out.xtf" + ): + ogr.GetDriverByName("Interlis 2").CreateDataSource( + "/i_do/not/exist/out.xtf,data/ili/ch.bazl.sicherheitszonenplan.oereb_20131118.imd" + ) diff --git a/autotest/ogr/ogr_openfilegdb_write.py b/autotest/ogr/ogr_openfilegdb_write.py index 690a127879d4..eadd7147c8a0 100755 --- a/autotest/ogr/ogr_openfilegdb_write.py +++ b/autotest/ogr/ogr_openfilegdb_write.py @@ -54,7 +54,10 @@ def setup_driver(): if filegdb_driver is not None: filegdb_driver.Deregister() - yield + with gdaltest.config_option( + "OGR_OPENFILEGDB_ERROR_ON_INCONSISTENT_BUFFER_MAX_SIZE", "YES" + ): + yield if filegdb_driver is not None: print("Reregistering FileGDB driver") @@ -4493,3 +4496,37 @@ def test_ogr_openfilegdb_write_new_datetime_types(tmp_vsimem): f = sql_lyr.GetNextFeature() assert f["MIN_timestamp_offset"] == "1900/12/31 14:01:01" assert f["MAX_timestamp_offset"] == "2023/12/30 14:01:01" + + +############################################################################### +# Test updating an existing feature with one whose m_nRowBlobLength is +# larger than m_nHeaderBufferMaxSize + + +def test_ogr_openfilegdb_write_update_feature_larger(tmp_vsimem): + + filename = str(tmp_vsimem / "out.gdb") + ds = ogr.GetDriverByName("OpenFileGDB").CreateDataSource(filename) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + lyr = ds.CreateLayer("test", srs, ogr.wkbLineString) + f = ogr.Feature(lyr.GetLayerDefn()) + g = ogr.Geometry(ogr.wkbLineString) + g.SetPoint_2D(10, 0, 0) + f.SetGeometry(g) + lyr.CreateFeature(f) + ds = None + + ds = ogr.Open(filename, update=1) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + g = ogr.Geometry(ogr.wkbLineString) + g.SetPoint_2D(999, 0, 0) + f.SetGeometry(g) + lyr.SetFeature(f) + ds = None + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert f.GetGeometryRef().GetGeometryRef(0).GetPointCount() == 1000 diff --git a/autotest/ogr/ogr_rfc41.py b/autotest/ogr/ogr_rfc41.py index 37f0096dd0c3..9befbaa54667 100755 --- a/autotest/ogr/ogr_rfc41.py +++ b/autotest/ogr/ogr_rfc41.py @@ -400,7 +400,6 @@ def test_ogr_rfc41_6(): geomfield = ogr.GeomFieldDefn("geomfield", ogr.wkbPolygon) geomfield.SetSpatialRef(sr) lyr.CreateGeomField(geomfield) - lyr.GetLayerDefn().GetGeomFieldDefn(0).SetName("geomfield") lyr.CreateField(ogr.FieldDefn("intfield", ogr.OFTInteger)) lyr.CreateField(ogr.FieldDefn("wkt", ogr.OFTString)) feat = ogr.Feature(lyr.GetLayerDefn()) diff --git a/autotest/ogr/ogr_sql_test.py b/autotest/ogr/ogr_sql_test.py index de8ec7f0b78b..8037b6e6f08d 100755 --- a/autotest/ogr/ogr_sql_test.py +++ b/autotest/ogr/ogr_sql_test.py @@ -696,9 +696,15 @@ def test_ogr_sql_28(): ds = ogr.GetDriverByName("Memory").CreateDataSource("my_ds") lyr = ds.CreateLayer("my_layer") - lyr.GetLayerDefn().GetGeomFieldDefn(0).SetName( - "geom" - ) # a bit border line but OK for Memory driver... + + new_geom_field_defn = ogr.GeomFieldDefn("geom", ogr.wkbUnknown) + assert ( + lyr.AlterGeomFieldDefn( + 0, new_geom_field_defn, ogr.ALTER_GEOM_FIELD_DEFN_NAME_FLAG + ) + == ogr.OGRERR_NONE + ) + field_defn = ogr.FieldDefn("strfield", ogr.OFTString) lyr.CreateField(field_defn) field_defn = ogr.FieldDefn("intfield", ogr.OFTInteger) diff --git a/autotest/utilities/test_gdal_viewshed.py b/autotest/utilities/test_gdal_viewshed.py index 8e9a4755df81..01ea6688e1d1 100755 --- a/autotest/utilities/test_gdal_viewshed.py +++ b/autotest/utilities/test_gdal_viewshed.py @@ -29,6 +29,8 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import struct + import gdaltest import pytest import test_cli_utilities @@ -304,3 +306,119 @@ def test_gdal_viewshed_invalid_output_filename(gdal_viewshed_path): + " -ox -79.5 -oy 43.5 ../gdrivers/data/n43.tif i/do_not/exist.tif" ) assert "Cannot create dataset" in err + + +############################################################################### +# Test bug fix for https://github.com/OSGeo/gdal/issues/9432 + + +def test_gdal_viewshed_south_up(gdal_viewshed_path, tmp_path, viewshed_input): + + width = 7 + height = 5 + res = 1 + left_x = 1000 + top_y = 2000 + + # "Reference" case with north-up dataset + src_ds_north_up_filename = str(tmp_path / "test_gdal_viewshed_src_ds_north_up.tif") + src_ds_north_up = gdal.GetDriverByName("GTiff").Create( + src_ds_north_up_filename, width, height + ) + src_ds_north_up.SetGeoTransform([left_x, res, 0, top_y, 0, -res]) + expected_gt = src_ds_north_up.GetGeoTransform() + src_ds_north_up.GetRasterBand(1).WriteRaster(width // 2, height // 2, 1, 1, b"\x80") + src_ds_north_up.Close() + + viewshed_out = str(tmp_path / "test_gdal_viewshed_north_up_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -oz {} -ox {} -oy {} {} {}".format( + 130, + left_x + float(width) / 2 * res, + top_y - float(height) / 2 * res, + src_ds_north_up_filename, + viewshed_out, + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + assert ds.RasterXSize == width + assert ds.RasterYSize == height + assert ds.GetGeoTransform() == pytest.approx(expected_gt) + expected_data = ( + 255, + 255, + 255, + 255, + 255, + 255, + 255, # end of line + 255, + 255, + 0, + 0, + 0, + 255, + 255, # end of line + 255, + 255, + 255, + 255, + 255, + 255, + 255, # end of line + 255, + 255, + 0, + 0, + 0, + 255, + 255, # end of line + 255, + 255, + 255, + 255, + 255, + 255, + 255, + ) + assert ( + struct.unpack("B" * (width * height), ds.GetRasterBand(1).ReadRaster()) + == expected_data + ) + + # Tested case with south-up dataset + src_ds_south_up_filename = str(tmp_path / "test_gdal_viewshed_src_ds_south_up.tif") + src_ds_south_up = gdal.GetDriverByName("GTiff").Create( + src_ds_south_up_filename, width, height + ) + src_ds_south_up.SetGeoTransform([left_x, res, 0, top_y - res * height, 0, res]) + expected_gt = src_ds_south_up.GetGeoTransform() + src_ds_south_up.GetRasterBand(1).WriteRaster(width // 2, height // 2, 1, 1, b"\x80") + src_ds_south_up.Close() + + viewshed_out = str(tmp_path / "test_gdal_viewshed_south_up_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -oz {} -ox {} -oy {} {} {}".format( + 130, + left_x + float(width) / 2 * res, + top_y - float(height) / 2 * res, + src_ds_south_up_filename, + viewshed_out, + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + assert ds.RasterXSize == width + assert ds.RasterYSize == height + assert ds.GetGeoTransform() == pytest.approx(expected_gt) + assert ( + struct.unpack("B" * (width * height), ds.GetRasterBand(1).ReadRaster()) + == expected_data + ) diff --git a/autotest/utilities/test_gdalinfo_lib.py b/autotest/utilities/test_gdalinfo_lib.py index a170b880affc..af751909b8cd 100755 --- a/autotest/utilities/test_gdalinfo_lib.py +++ b/autotest/utilities/test_gdalinfo_lib.py @@ -263,3 +263,16 @@ def test_gdalinfo_lib_json_projjson_no_epsg(): ret = gdal.Info(ds, options="-json") assert ret["stac"]["proj:epsg"] is None assert ret["stac"]["proj:wkt2"] is not None + + +############################################################################### +# Test fix for https://github.com/OSGeo/gdal/issues/9337 + + +def test_gdalinfo_lib_json_proj_shape(): + + width = 2 + height = 1 + ds = gdal.GetDriverByName("MEM").Create("", width, height) + ret = gdal.Info(ds, options="-json") + assert ret["stac"]["proj:shape"] == [height, width] diff --git a/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index 0585b04cd271..e22997b97f66 100755 --- a/autotest/utilities/test_gdalwarp_lib.py +++ b/autotest/utilities/test_gdalwarp_lib.py @@ -3501,7 +3501,7 @@ def test_gdalwarp_lib_sum_preserving_multiband(): ############################################################################### -def test_gdalwarp_lib_sum_preserving_accross_antimeridian(): +def test_gdalwarp_lib_sum_preserving_across_antimeridian(): src_ds = gdal.Translate( "", @@ -4033,3 +4033,10 @@ def test_gdalwarp_lib_to_projection_without_inverse_method(): 1218250.2778614, ], ) + + +def test_gdalwarp_lib_no_crash_on_none_dst(): + + ds1 = gdal.Open("../gcore/data/byte.tif") + with pytest.raises(ValueError): + gdal.Warp(None, ds1) diff --git a/doc/source/development/building_from_source.rst b/doc/source/development/building_from_source.rst index c037301d0977..4ffce26053c9 100644 --- a/doc/source/development/building_from_source.rst +++ b/doc/source/development/building_from_source.rst @@ -415,7 +415,7 @@ CURL .. option:: CURL_LIBRARY_RELEASE - Path to a shared or static library file, such as ``libcurl.dll``, + Path to a shared or static library file, such as ``libcurl.so``, ``libcurl.lib``, or other name. .. option:: CURL_USE_STATIC_LIBS=ON/OFF @@ -632,7 +632,7 @@ If not found, an internal copy of libgeotiff can be used. .. option:: GEOTIFF_LIBRARY_RELEASE - Path to a shared or static library file, such as ``geotiff.dll``, + Path to a shared or static library file, such as ``libgeotiff.so``, ``geotiff.lib``, or other name. A similar variable ``GEOTIFF_LIBRARY_DEBUG`` can also be specified to a similar library for building Debug releases. @@ -1680,7 +1680,7 @@ PROJ .. option:: PROJ_LIBRARY_RELEASE - Path to a shared or static library file, such as ``proj.dll``, + Path to a shared or static library file, such as ``libproj.so``, ``proj.lib``, or other name. A similar variable ``PROJ_LIBRARY_DEBUG`` can also be specified to a similar library for building Debug releases. @@ -1778,7 +1778,7 @@ and the :ref:`sql_sqlite_dialect`. .. option:: SQLite3_LIBRARY - Path to a shared or static library file, such as ``sqlite3.dll``, + Path to a shared or static library file, such as ``libsqlite3.so``, ``sqlite3.lib`` or other name. .. option:: GDAL_USE_SQLITE3=ON/OFF @@ -1866,7 +1866,7 @@ If not found, an internal copy of libtiff can be used. .. option:: TIFF_LIBRARY_RELEASE - Path to a shared or static library file, such as ``tiff.dll``, + Path to a shared or static library file, such as ``libtiff.so``, ``tiff.lib``, or other name. A similar variable ``TIFF_LIBRARY_DEBUG`` can also be specified to a similar library for building Debug releases. diff --git a/doc/source/drivers/raster/georaster.rst b/doc/source/drivers/raster/georaster.rst index 397e4a19ec6c..e9a31dee6b54 100644 --- a/doc/source/drivers/raster/georaster.rst +++ b/doc/source/drivers/raster/georaster.rst @@ -185,6 +185,102 @@ Creation Options generated. If :co:`GENPYRAMID` is not informed the resample method NN (nearest neighbor) will apply. +- .. co:: GENSTATS + :choices: TRUE, FALSE + :default: FALSE + + To generate statistics from the given rasters, set this value to TRUE. + Default value is FALSE. + This option must be present in order to generate the stats, otherwise, + other GENSTATS options are ignored. + + +- .. co:: GENSTATS_SAMPLINGFACTOR + :choices: + :default: 1 + + Defines the number of cells skipped in both row and column dimensions when + the statistics are computed. + For example, when setting this value to 4, one-sixteenth of the cells are sampled. + The higher the value, the less accurate the statistics are likely to be, + but the more quickly they will be computed. + Defaults to 1, which means that all cells are sampled. + +- .. co:: GENSTATS_SAMPLINGWINDOW + :choices: + + This parameter identifies the upper-left (row, column) and lower-right + (row, column) coordinates of a rectangular window, and raster space is assumed. + The intersection of the MBR (minimum bounding rectangle) of the + GeoRaster object in raster space is used for computing statistics. + When this value is not specified, statistics are computed for the entire raster. + +- .. co:: GENSTATS_HISTOGRAM + :choices: TRUE, FALSE + :default: FALSE + + When this value is set to TRUE, a histogram will be computed and stored. + Defaults to FALSE, so a histogram won't be generated. + +- .. co:: GENSTATS_LAYERNUMBERS + :choices: , - + + Defines the numbers of the layers for which to compute the statistics. + This can include numbers, ranges (indicated by hyphens), and commas + to separate any combination of those. + For example, '1,3-5,7', '1,3,6', '1-6'. + If this value is not specified, statistics will be computed for all + layers. + +- .. co:: GENSTATS_USEBIN + :choices: TRUE, FALSE + :default: TRUE + + Defaults to TRUE. + Specifies whether or not to use a provided bin function + (specified in :co:`GENSTATS_BINFUNCTION`). + When this value is set to TRUE, the bin function to be + used follows the following sequence: (1) the bin function + specified in :co:`GENSTATS_BINFUNCTION`. (2) the bin + function specified by the element in the + GeoRaster XML metadata. (3) a dynamically generated bin + function generated as follows: + Min and max are the actual min and max values of the raster + Numbins is defined as: + * celldepth = 1, numbins = 2. + * cellDepth = 2, numbins = 4. + * cellDepth = 4, numbins = 8. + * cellDepth >= 8, numbins = 256. + + When this value is set to FALSE, then the bin function + to use is the one generated dynamically as previously + described. + +- .. co:: GENSTATS_BINFUNCTION + :choices: + + An array whose element specify the bin type, total + number of bins, first bin number, minimum cell value, + and maximum cell value. Bin type must be linear (0). + When this value is not specified, and :co:`GENSTATS_USEBIN` + is TRUE, then the bin function to use is as follows: + + 1. A valid function defined in the GeoRaster metadata. + 2. The same bin function generated when :co:`GENSTATS_USEBIN` is FALSE. + +- .. co:: GENSTATS_NODATA + :choices: TRUE, FALSE + :default: FALSE + + Specifies whether or not to compare each cell values + with NODATA values defined in the metadata when computing + statistics. When set to TRUE, all pixels with a NODATA + value won't be considered. When set to FALSE, NODATA + pixels will be considered as regular pixels. + + A NODATA value is used for cells whose values are either not known + or meaningless + - .. co:: QUALITY :choices: 0-100 :default: 75 @@ -366,23 +462,28 @@ Loading: General use of GeoRaster ------------------------ -| GeoRaster can be used in any GDAL command line tool with all the - available options. Like a image subset extraction or re-project: -| % gdal_translate -of gtiff geor:scott/tiger@dbdemo,landsat,scene,id=54 - output.tif \\ +GeoRaster can be used in any GDAL command line tool with all the available options. +Like a image subset extraction or re-project: + +.. code-block:: bash + + % gdal_translate -of gtiff geor:scott/tiger@dbdemo,landsat,scene,id=54 output.tif \   -srcwin 0 0 800 600 - % gdalwarp -of png geor:scott/tiger@dbdemo,st_rdt_1,130 output.png - -t_srs EPSG:9000913 - Two different GeoRaster can be used as input and output on the same - operation: -| % gdal_translate -of georaster - geor:scott/tiger@dbdemo,landsat,scene,id=54 - geor:scott/tiger@proj1,projview,image -co INSERT="VALUES - (102, SDO_GEOR.INIT())" - Applications that use GDAL can theoretically read and write from - GeoRaster just like any other format but most of then are more - inclined to try to access files on the file system so one alternative - is to create VRT to represent the GeoRaster description, e.g.: -| % gdal_translate -of VRT geor:scott/tiger@dbdemo,landsat,scene,id=54 - view_54.vrt - % openenv view_54.vrt + % gdalwarp -of png geor:scott/tiger@dbdemo,st_rdt_1,130 output.png + -t_srs EPSG:9000913 + +Two different GeoRaster can be used as input and output on the same operation: + +.. code-block:: bash + + % gdal_translate -of georaster geor:scott/tiger@dbdemo,landsat,scene,id=54 \ + geor:scott/tiger@proj1,projview,image -co INSERT="VALUES (102, SDO_GEOR.INIT())" + +Applications that use GDAL can theoretically read and write from GeoRaster just like +any other format but most of then are more inclined to try to access files on the file +system so one alternative is to create VRT to represent the GeoRaster description, e.g.: + +.. code-block:: bash + + % gdal_translate -of VRT geor:scott/tiger@dbdemo,landsat,scene,id=54 view_54.vrt + % openenv view_54.vrt diff --git a/doc/source/drivers/raster/gsag.rst b/doc/source/drivers/raster/gsag.rst index cbfc020357d7..138edc5343fb 100644 --- a/doc/source/drivers/raster/gsag.rst +++ b/doc/source/drivers/raster/gsag.rst @@ -10,8 +10,8 @@ GSAG -- Golden Software ASCII Grid File Format This is the ASCII-based (human-readable) version of one of the raster formats used by Golden Software products (such as the Surfer series). -This format is supported for both reading and writing (including create, -delete, and copy). Currently the associated formats for color, metadata, +This format is supported for both reading and writing (copy mode only). +Currently the associated formats for color, metadata, and shapes are not supported. NOTE: Implemented as :source_file:`frmts/gsg/gsagdataset.cpp`. @@ -19,6 +19,8 @@ NOTE: Implemented as :source_file:`frmts/gsg/gsagdataset.cpp`. Driver capabilities ------------------- +.. supports_createcopy:: + .. supports_georeferencing:: .. supports_virtualio:: diff --git a/doc/source/drivers/raster/hfa.rst b/doc/source/drivers/raster/hfa.rst index cd20cf172d52..2bc1a80c12a6 100644 --- a/doc/source/drivers/raster/hfa.rst +++ b/doc/source/drivers/raster/hfa.rst @@ -16,14 +16,15 @@ and c128. Compressed and missing tiles in Erdas files should be handled properly on read. Files between 2GiB and 4GiB in size should work on Windows NT, and may work on some Unix platforms. Files with external spill files -(needed for datasets larger than 2GiB) are also support for reading and +(needed for datasets larger than 2GiB) are also supported for reading and writing. -Metadata reading and writing is supported at the dataset level, and for -bands, but this is GDAL specific metadata - not metadata in an Imagine -recognized form. The metadata is stored in a table called GDAL_MetaData -with each column being a metadata item. The title is the key and the row -1 value is the value. +Metadata reading and writing is supported at the dataset and band level. +But this is GDAL specific metadata, not metadata in an Imagine recognized form. +The metadata is stored in a table called GDAL_MetaData which as many +columns as metadata items. +The title of the column is the ``key`` of the ``key``=``value`` metadata item pair +and the value of row 1 is ``value``. Driver capabilities ------------------- @@ -43,7 +44,7 @@ Erdas Imagine files can be created with any GDAL defined band type, including the complex types. Created files may have any number of bands. Pseudo-Color tables will be written if using the GDALDriver::CreateCopy() methodology. Most projections should be -supported though translation of unusual datums (other than WGS84, WGS72, +supported, though translation of unusual datums (other than WGS84, WGS72, NAD83, and NAD27) may be problematic. Creation Options: @@ -110,7 +111,7 @@ Creation Options: :choices: YES, NO :default: NO - Force use of ArcGIS PE String in file + Force the use of ESRI Projection Engine (PE) String in file instead of Imagine coordinate system format. In some cases this improves ArcGIS coordinate system compatibility. @@ -119,8 +120,9 @@ Creation Options: :default: NO :since: 3.7 - Disable use of ArcGIS PE String in - file. Default is NO (that is ArcGIS PE String may be written if needed). + Disable use of ESRI Projection Engine (PE) String in file. + The default value is NO, allowing the ESRI PE String + to be written if needed. Erdas Imagine supports external creation of overviews (with gdaladdo for instance). To force them to be created in an .rrd file (rather than @@ -180,7 +182,7 @@ by the HFA driver: The block size (tile width/height) used for overviews can be specified by setting this - configuration option to a power- of-two value between 32 and 2048. + configuration option to a power-of-two value between 32 and 2048. - .. config:: USE_SPILL :choices: YES, NO diff --git a/doc/source/drivers/raster/jp2ecw.rst b/doc/source/drivers/raster/jp2ecw.rst index 122fcba198d2..30f741ee574f 100644 --- a/doc/source/drivers/raster/jp2ecw.rst +++ b/doc/source/drivers/raster/jp2ecw.rst @@ -283,7 +283,7 @@ LEVELS." Create support -------------- -While the driver advertizes the Create() capability, contrary to most other +While the driver advertises the Create() capability, contrary to most other drivers that implement it, the implementation of RasterIO() and WriteBlock() in the JP2ECW driver does not support arbitrary random writing. Data must be written in the dataset from top to bottom, whole line(s) at a diff --git a/doc/source/drivers/raster/vrt.rst b/doc/source/drivers/raster/vrt.rst index 49476c354b1f..1c860c439ef2 100644 --- a/doc/source/drivers/raster/vrt.rst +++ b/doc/source/drivers/raster/vrt.rst @@ -1757,6 +1757,7 @@ PanchroBand and SpectralBand elements must have at least a **SourceFilename** ch element to specify the name of the dataset. They may also have a **SourceBand** child element to specify the number of the band in the dataset (starting with 1). If not specify, the first band will be assumed. +**OpenOptions** can also be specified The SpectralBand element must generally have a **dstBand** attribute to specify the number of the output band (starting with 1) to which the input spectral band must be mapped. @@ -1778,18 +1779,30 @@ with panchromatic.tif. panchromatic.tif + + ALL_CPUS + 1 multispectral.tif + + ALL_CPUS + 1 multispectral.tif + + ALL_CPUS + 2 multispectral.tif + + ALL_CPUS + 3 diff --git a/doc/source/programs/gdal2xyz.rst b/doc/source/programs/gdal2xyz.rst index 027990c56c5d..4c896769a625 100644 --- a/doc/source/programs/gdal2xyz.rst +++ b/doc/source/programs/gdal2xyz.rst @@ -102,5 +102,11 @@ Examples To create a text file in `xyz` format from the input file `input.tif`. The first columns, x and y, are the coordinates of the centers of each cell. -The remaining colums represent the first and second bands. +The remaining columns represent the first and second bands. We also replace the dataset nodata values with zeros. + + +Caveats +------- + +gdal2xyz output values with a limited precision. Use ``gdal_translate -of XYZ`` if more precision is desired. diff --git a/doc/source/programs/gdal_grid.rst b/doc/source/programs/gdal_grid.rst index 806905379d87..e4534a22ea4d 100644 --- a/doc/source/programs/gdal_grid.rst +++ b/doc/source/programs/gdal_grid.rst @@ -476,9 +476,26 @@ in the VRT file in the following way: The :ref:`vector.csv` description page contains details on CSV format supported by GDAL/OGR. -Creating multiband files: not directly possible with gdal_grid. +Creating multiband files +------------------------ + +Creating multiband files is not directly possible with gdal_grid. One might use gdal_grid multiple times to create one band per file, -and then use gdalbuildvrt -separate + gdal_translate to combine the one-band-files into a single one. +and then use :ref:`gdalbuildvrt` -separate and then :ref:`gdal_translate`: + +.. code-block:: bash + + gdal_grid ... 1.tif; gdal_grid ... 2.tif; gdal_grid ... 3.tif + gdalbuildvrt -separate 123.vrt 1.tif 2.tif 3.tif + gdal_translate 123.vrt 123.tif + +Or just :ref:`gdal_merge`, to combine the one-band files into a single one: + +.. code-block:: bash + + gdal_grid ... a.tif; gdal_grid ... b.tif; gdal_grid ... c.tif + gdal_merge.py -separate a.tif b.tif c.tif -o d.tif + C API ----- diff --git a/doc/source/programs/gdal_viewshed.rst b/doc/source/programs/gdal_viewshed.rst index c732ffded8c5..12d121f84a57 100644 --- a/doc/source/programs/gdal_viewshed.rst +++ b/doc/source/programs/gdal_viewshed.rst @@ -157,19 +157,22 @@ Functionality of this utility can be done from C with :cpp:func:`GDALViewshedGen Example ------- -Compute the visibility of an elevation raster data source with defaults +Screenshot of 2 combined viewshed analysis, with the yellow pixels showing the area that is +visible from the both observation locations (the green dots), while the small green area is +only visible from one location. .. figure:: ../../images/gdal_viewshed.png - A computed visibility for two separate `-ox` and `-oy` points on a DEM. -.. code-block:: - - gdal_viewshed -md 500 -ox -10147017 -oy 5108065 source.tif destination.tif +Create a viewshed raster with a radius of 500 for a person standing at location (-10147017, 5108065). +.. code-block:: bash + gdal_viewshed -md 500 -ox -10147017 -oy 5108065 source.tif destination.tif +Reference +--------- .. [Wang2000] Generating Viewsheds without Using Sightlines. Wang, Jianjun, Robinson, Gary J., and White, Kevin. Photogrammetric Engineering and Remote diff --git a/doc/source/programs/gdaltransform.rst b/doc/source/programs/gdaltransform.rst index 38242760900a..b91ef59fddbc 100644 --- a/doc/source/programs/gdaltransform.rst +++ b/doc/source/programs/gdaltransform.rst @@ -20,7 +20,7 @@ Synopsis [-s_coord_epoch ] [-t_coord_epoch ] [-ct ] [-order ] [-tps] [-rpc] [-geoloc] [-gcp [elevation]]... [-output_xy] - [srcfile [dstfile]] + [ []] Description @@ -194,8 +194,23 @@ for a coordinate at epoch 2000.0 +proj=unitconvert +xy_in=rad +xy_out=deg" 2 49 0 2000 -Produces this output measured in longitude degrees, latitude degrees and ellipsoid height in metre: +Produces this output measured in longitude degrees, latitude degrees and ellipsoid height in metres: .. code-block:: bash 2.0000005420366 49.0000003766711 -0.0222802283242345 + +Ground control points ++++++++++++++++++++++ + +Where is the address "370 S. 300 E." in Salt Lake City, given we know +some nearby corners' coordinates? + +.. code-block:: bash + + echo 300 -370 | gdaltransform \ + -gcp 0 -500 -111.89114803 40.75846686 \ + -gcp 0 0 -111.89114717 40.76932606 \ + -gcp 500 0 -111.87685039 40.76940631 + + -111.8825697384 40.761338402 0 diff --git a/doc/source/programs/gdalwarp.rst b/doc/source/programs/gdalwarp.rst index 1da8b4f5c52a..38896dab9b99 100644 --- a/doc/source/programs/gdalwarp.rst +++ b/doc/source/programs/gdalwarp.rst @@ -704,4 +704,10 @@ This utility is also callable from C with :cpp:func:`GDALWarp`. See also -------- -`Wiki page discussing options and behaviours of gdalwarp `_ +.. only:: not man + + `Wiki page discussing options and behaviours of gdalwarp `_ + +.. only:: man + + Wiki page discussing options and behaviours of gdalwarp: https://trac.osgeo.org/gdal/wiki/UserDocs/GdalWarp diff --git a/doc/source/programs/index.rst b/doc/source/programs/index.rst index 0514bbf4af60..af107ef12aa5 100644 --- a/doc/source/programs/index.rst +++ b/doc/source/programs/index.rst @@ -25,80 +25,81 @@ Raster programs :hidden: Common options - gdalinfo - gdal_translate - gdaladdo - gdalwarp - gdaltindex - gdalbuildvrt - gdal_contour - gdaldem - rgb2pct - pct2rgb - gdalattachpct - gdal_merge + + gdal-config gdal2tiles gdal2xyz - gdal_rasterize - gdaltransform - nearblack - gdal_retile + gdal_calc + gdal_contour + gdal_create + gdal_edit + gdal_fillnodata + gdal_footprint gdal_grid - gdal_proximity + gdal_merge + gdal_pansharpen gdal_polygonize + gdal_proximity + gdal_rasterize + gdal_retile gdal_sieve - gdal_fillnodata + gdal_translate + gdal_viewshed + gdaladdo + gdalattachpct + gdalbuildvrt + gdalcompare + gdaldem + gdalinfo gdallocationinfo - gdalsrsinfo - gdalmove - gdal_edit - gdal_calc - gdal_pansharpen - gdal-config gdalmanage - gdalcompare - gdal_viewshed - gdal_create - gdal_footprint + gdalmove + gdalsrsinfo + gdaltindex + gdaltransform + gdalwarp + nearblack + pct2rgb + rgb2pct .. only:: html - :ref:`Common options ` - - :ref:`gdalinfo`: Lists information about a raster dataset. - - :ref:`gdal_translate`: Converts raster data between different formats. - - :ref:`gdaladdo`: Builds or rebuilds overview images. - - :ref:`gdalwarp`: Image reprojection and warping utility. - - :ref:`gdaltindex`: Builds an OGR-supported dataset as a raster tileindex. - - :ref:`gdalbuildvrt`: Builds a VRT from a list of datasets. - - :ref:`gdal_contour`: Builds vector contour lines from a raster elevation model. - - :ref:`gdaldem`: Tools to analyze and visualize DEMs. - - :ref:`rgb2pct`: Convert a 24bit RGB image to 8bit paletted. - - :ref:`pct2rgb`: Convert an 8bit paletted image to 24bit RGB. - - :ref:`gdalattachpct`: Attach a color table to a raster file from an input file. - - :ref:`gdal_merge`: Mosaics a set of images. + - :ref:`gdal-config`: Determines various information about a GDAL installation. - :ref:`gdal2tiles`: Generates directory with TMS tiles, KMLs and simple web viewers. - :ref:`gdal2xyz`: Translates a raster file into xyz format. - - :ref:`gdal_rasterize`: Burns vector geometries into a raster. - - :ref:`gdaltransform`: Transforms coordinates. - - :ref:`nearblack`: Convert nearly black/white borders to black. - - :ref:`gdal_retile`: Retiles a set of tiles and/or build tiled pyramid levels. + - :ref:`gdal_calc`: Command line raster calculator with numpy syntax. + - :ref:`gdal_contour`: Builds vector contour lines from a raster elevation model. + - :ref:`gdal_create`: Create a raster file (without source dataset). + - :ref:`gdal_edit`: Edit in place various information of an existing GDAL dataset. + - :ref:`gdal_fillnodata`: Fill raster regions by interpolation from edges. + - :ref:`gdal_footprint`: Compute footprint of a raster. - :ref:`gdal_grid`: Creates regular grid from the scattered data. - - :ref:`gdal_proximity`: Produces a raster proximity map. + - :ref:`gdal_merge`: Mosaics a set of images. + - :ref:`gdal_pansharpen`: Perform a pansharpen operation. - :ref:`gdal_polygonize`: Produces a polygon feature layer from a raster. + - :ref:`gdal_proximity`: Produces a raster proximity map. + - :ref:`gdal_rasterize`: Burns vector geometries into a raster. + - :ref:`gdal_retile`: Retiles a set of tiles and/or build tiled pyramid levels. - :ref:`gdal_sieve`: Removes small raster polygons. - - :ref:`gdal_fillnodata`: Fill raster regions by interpolation from edges. + - :ref:`gdal_translate`: Converts raster data between different formats. + - :ref:`gdal_viewshed`: Compute a visibility mask for a raster. + - :ref:`gdaladdo`: Builds or rebuilds overview images. + - :ref:`gdalattachpct`: Attach a color table to a raster file from an input file. + - :ref:`gdalbuildvrt`: Builds a VRT from a list of datasets. + - :ref:`gdalcompare`: Compare two images. + - :ref:`gdaldem`: Tools to analyze and visualize DEMs. + - :ref:`gdalinfo`: Lists information about a raster dataset. - :ref:`gdallocationinfo`: Raster query tool - - :ref:`gdalsrsinfo`: Lists info about a given SRS in number of formats (WKT, PROJ.4, etc.) - - :ref:`gdalmove`: Transform georeferencing of raster file in place. - - :ref:`gdal_edit`: Edit in place various information of an existing GDAL dataset. - - :ref:`gdal_calc`: Command line raster calculator with numpy syntax. - - :ref:`gdal_pansharpen`: Perform a pansharpen operation. - - :ref:`gdal-config`: Determines various information about a GDAL installation. - :ref:`gdalmanage`: Identify, delete, rename and copy raster data files. - - :ref:`gdalcompare`: Compare two images. - - :ref:`gdal_viewshed`: Compute a visibility mask for a raster. - - :ref:`gdal_create`: Create a raster file (without source dataset). - - :ref:`gdal_footprint`: Compute footprint of a raster. + - :ref:`gdalmove`: Transform georeferencing of raster file in place. + - :ref:`gdalsrsinfo`: Lists info about a given SRS in number of formats (WKT, PROJ.4, etc.) + - :ref:`gdaltindex`: Builds an OGR-supported dataset as a raster tileindex. + - :ref:`gdaltransform`: Transforms coordinates. + - :ref:`gdalwarp`: Image reprojection and warping utility. + - :ref:`nearblack`: Convert nearly black/white borders to black. + - :ref:`pct2rgb`: Convert an 8bit paletted image to 24bit RGB. + - :ref:`rgb2pct`: Convert a 24bit RGB image to 8bit paletted. Multidimensional Raster programs -------------------------------- diff --git a/doc/source/programs/sozip.rst b/doc/source/programs/sozip.rst index 4913a37ac9af..0ace8a573167 100644 --- a/doc/source/programs/sozip.rst +++ b/doc/source/programs/sozip.rst @@ -81,6 +81,11 @@ The :program:`sozip` utility can be used to: ZIP reader used by GDAL. But validation of the SOZip-specific aspects is done in a more thoroughful way. +.. option:: -r +.. option:: --recurse-paths + + Travels the directory structure of the specified directory/directories recursively. + .. option:: -j .. option:: --junk-paths diff --git a/doc/source/tutorials/gdal_grid_tut.rst b/doc/source/tutorials/gdal_grid_tut.rst index 61549bfcf1d8..f71720c15f0f 100644 --- a/doc/source/tutorials/gdal_grid_tut.rst +++ b/doc/source/tutorials/gdal_grid_tut.rst @@ -34,7 +34,8 @@ That is what GDAL Grid API is about. It helps you to interpolate your data There are two ways of using this interface. Programmatically it is available through the :cpp:func:`GDALGridCreate` C function; for end users there is a -:ref:`gdal_grid` utility. The rest of this document discusses details on algorithms +:ref:`gdal_grid` utility (which see for an up-to-date list of all current features). +The rest of this document discusses details on algorithms and their parameters implemented in GDAL Grid API. Interpolation of the Scattered Data diff --git a/frmts/dimap/dimapdataset.cpp b/frmts/dimap/dimapdataset.cpp index 3247eb952d20..93c16a96a790 100644 --- a/frmts/dimap/dimapdataset.cpp +++ b/frmts/dimap/dimapdataset.cpp @@ -1515,6 +1515,8 @@ int DIMAPDataset::ReadImageInformation2() "", "Processing_Information.Product_Settings.Geometric_Settings", "GEOMETRIC_", + "Processing_Information.Product_Settings.Radiometric_Settings", + "RADIOMETRIC_", "Quality_Assessment.Imaging_Quality_Measurement", "CLOUDCOVER_", nullptr, diff --git a/frmts/ers/ersdataset.cpp b/frmts/ers/ersdataset.cpp index 919d64f7d4b4..d83ed19b4251 100644 --- a/frmts/ers/ersdataset.cpp +++ b/frmts/ers/ersdataset.cpp @@ -881,17 +881,18 @@ int ERSProxyRasterBand::GetOverviewCount() GDALDataset *ERSDataset::Open(GDALOpenInfo *poOpenInfo) { + if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) + return nullptr; + + int &nRecLevel = GetRecLevel(); // cppcheck-suppress knownConditionTrueFalse - if (GetRecLevel()) + if (nRecLevel) { CPLError(CE_Failure, CPLE_AppDefined, "Attempt at recursively opening ERS dataset"); return nullptr; } - if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) - return nullptr; - /* -------------------------------------------------------------------- */ /* Ingest the file as a tree of header nodes. */ /* -------------------------------------------------------------------- */ @@ -1020,7 +1021,6 @@ GDALDataset *ERSDataset::Open(GDALOpenInfo *poOpenInfo) /* -------------------------------------------------------------------- */ if (EQUAL(poHeader->Find("DataSetType", ""), "Translated")) { - int &nRecLevel = GetRecLevel(); nRecLevel++; poDS->poDepFile = GDALDataset::FromHandle( GDALOpen(osDataFilePath, poOpenInfo->eAccess)); diff --git a/frmts/georaster/georaster_dataset.cpp b/frmts/georaster/georaster_dataset.cpp index 453a1a9566d4..2328d645e7e6 100644 --- a/frmts/georaster/georaster_dataset.cpp +++ b/frmts/georaster/georaster_dataset.cpp @@ -789,6 +789,21 @@ char **GeoRasterDataset::GetFileList() return papszFileList; } +static bool ParseCommaSeparatedString(const char *str, double pfValues[], + int nMaxValues) +{ + int nValues = 0; + char **papszTokens = CSLTokenizeString2(str, ",", 0); + + for (int i = 0; papszTokens && papszTokens[i] && nValues < nMaxValues; i++) + { + pfValues[nValues++] = CPLAtof(papszTokens[i]); + } + + CSLDestroy(papszTokens); + return nValues == nMaxValues; +} + // --------------------------------------------------------------------------- // Create() // --------------------------------------------------------------------------- @@ -1252,6 +1267,103 @@ GDALDataset *GeoRasterDataset::Create(const char *pszFilename, int nXSize, poGRD->poGeoRaster->nPyramidLevels = atoi(pszFetched); } + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS"); + + if (pszFetched != nullptr) + { + poGRD->poGeoRaster->bGenStats = EQUAL(pszFetched, "TRUE"); + } + + bool bGenStatsOptionsUsed = false; + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_SAMPLINGFACTOR"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->nGenStatsSamplingFactor = atoi(pszFetched); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_SAMPLINGWINDOW"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + + // Sampling window contains 4 double values + const int nSize = 4; + if (!ParseCommaSeparatedString( + pszFetched, poGRD->poGeoRaster->dfGenStatsSamplingWindow, + nSize)) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Wrong comma separated string for sampling window (%s)", + pszFetched); + delete poGRD; + return nullptr; + } + + poGRD->poGeoRaster->bGenStatsUseSamplingWindow = true; + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_HISTOGRAM"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsHistogram = EQUAL(pszFetched, "TRUE"); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_LAYERNUMBERS"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->sGenStatsLayerNumbers = pszFetched; + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_USEBIN"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsUseBin = EQUAL(pszFetched, "TRUE"); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_BINFUNCTION"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + const int nSize = 5; + if (!ParseCommaSeparatedString( + pszFetched, poGRD->poGeoRaster->dfGenStatsBinFunction, nSize)) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Wrong comma separated string for bin function (%s)", + pszFetched); + delete poGRD; + return nullptr; + } + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_NODATA"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsNodata = EQUAL(pszFetched, "TRUE"); + } + + if (bGenStatsOptionsUsed && !poGRD->poGeoRaster->bGenStats) + { + CPLError( + CE_Warning, CPLE_AppDefined, + "Some GENSTATS* options were used but GENSTATS is not set. " + "Statistics won't be computed, please set GENSTATS option to true " + "if you want to generate statistics"); + } + // ------------------------------------------------------------------- // Return a new Dataset // ------------------------------------------------------------------- @@ -2421,7 +2533,7 @@ void GeoRasterDataset::SetSubdatasets(GeoRasterWrapper *poGRW) papszSubdatasets = CSLSetNameValue( papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", nCount), - CPLSPrintf("%s.Table=%s", szOwner, szTable)); + CPLSPrintf("Table=%s.%s", szOwner, szTable)); nCount++; } while (poStmt->Fetch()); diff --git a/frmts/georaster/georaster_priv.h b/frmts/georaster/georaster_priv.h index 4812d10994f1..c1eba12b16ab 100644 --- a/frmts/georaster/georaster_priv.h +++ b/frmts/georaster/georaster_priv.h @@ -391,6 +391,9 @@ class GeoRasterWrapper char *GetVAT(int nBand); bool GeneratePyramid(int nLevels, const char *pszResampling, bool bInternal = false); + bool GenerateStatistics(int nSamplingFactor, double *pdfSamplingWindow, + bool bHistogram, const char *pszLayerNumbers, + bool bUseBin, double *pdfBinFunction, bool bNodata); void DeletePyramid(); void PrepareToOverwrite(); bool InitializeMask(int nLevel, int nBlockColumns, int nBlockRows, @@ -465,6 +468,16 @@ class GeoRasterWrapper bool bHasBitmapMask; bool bUniqueFound; + bool bGenStats; + int nGenStatsSamplingFactor; + bool bGenStatsUseSamplingWindow; + double dfGenStatsSamplingWindow[4]; + bool bGenStatsHistogram; + CPLString sGenStatsLayerNumbers; + bool bGenStatsUseBin; + double dfGenStatsBinFunction[5]; + bool bGenStatsNodata; + int eModelCoordLocation; unsigned int anULTCoordinate[3]; diff --git a/frmts/georaster/georaster_wrapper.cpp b/frmts/georaster/georaster_wrapper.cpp index a0d4a7b1a17c..cdb9dbd8c9cb 100644 --- a/frmts/georaster/georaster_wrapper.cpp +++ b/frmts/georaster/georaster_wrapper.cpp @@ -43,7 +43,8 @@ // --------------------------------------------------------------------------- GeoRasterWrapper::GeoRasterWrapper() - : sPyramidResampling("NN"), sCompressionType("NONE"), sInterleaving("BSQ") + : sPyramidResampling("NN"), sCompressionType("NONE"), sInterleaving("BSQ"), + bGenStatsUseSamplingWindow(false), sGenStatsLayerNumbers("") { nRasterId = -1; phMetadata = nullptr; @@ -115,6 +116,20 @@ GeoRasterWrapper::GeoRasterWrapper() pasGCPList = nullptr; nGCPCount = 0; bFlushGCP = false; + bGenStats = false; + nGenStatsSamplingFactor = 1; + bGenStatsHistogram = false; + bGenStatsUseBin = true; + bGenStatsNodata = false; + dfGenStatsBinFunction[0] = 0.0; + dfGenStatsBinFunction[1] = 0.0; + dfGenStatsBinFunction[2] = 0.0; + dfGenStatsBinFunction[3] = 0.0; + dfGenStatsBinFunction[4] = 0.0; + dfGenStatsSamplingWindow[0] = 0.0; + dfGenStatsSamplingWindow[1] = 0.0; + dfGenStatsSamplingWindow[2] = 0.0; + dfGenStatsSamplingWindow[3] = 0.0; } // --------------------------------------------------------------------------- @@ -543,6 +558,75 @@ GeoRasterWrapper *GeoRasterWrapper::Open(const char *pszStringId, bool bUpdate) return poGRW; } +static bool ValidateInsertExpression(const CPLString &sInsertStatement) +{ + bool bInQuotes = false; + const size_t nStrLength = sInsertStatement.length(); + + for (size_t nPos = 0; nPos < nStrLength; ++nPos) + { + const char cCurrentChar = sInsertStatement[nPos]; + + if (cCurrentChar == '\'') + { + if (bInQuotes) + { + if (nPos + 1 < nStrLength && sInsertStatement[nPos + 1] == '\'') + { + ++nPos; + } + else + { + bInQuotes = false; + } + } + else + { + bInQuotes = true; + } + } + else if (!bInQuotes) + { + if (cCurrentChar == ';') + { + return false; + } + else if (nPos < nStrLength - 1) + { + const char cNextChar = sInsertStatement[nPos + 1]; + const bool bIsInvalid = + (cCurrentChar == '-' && cNextChar == '-') || + (cCurrentChar == '/' && cNextChar == '/') || + (cCurrentChar == '/' && cNextChar == '*') || + (cCurrentChar == '*' && cNextChar == '/'); + + if (bIsInvalid) + { + return false; + } + } + } + } + // Quotes must be properly closed, if not, this variable will be true + // thus having an unclosed string + return !bInQuotes; +} + +static bool ValidateDescriptionExpression(const CPLString &sInsertStatement) +{ + const char *rgpszInvalidChars[] = {";", "--", "/*", "*/", "//"}; + + for (const char *pszInvalidChar : rgpszInvalidChars) + { + if (sInsertStatement.find(pszInvalidChar) != std::string::npos) + { + return false; + } + } + + return true; +} + // --------------------------------------------------------------------------- // Create() // --------------------------------------------------------------------------- @@ -601,6 +685,12 @@ bool GeoRasterWrapper::Create(char *pszDescription, char *pszInsert, if (pszDescription) { + if (!ValidateDescriptionExpression(pszDescription)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "DESCRIPTION expression contains invalid values."); + return false; + } snprintf(szDescription, sizeof(szDescription), "%s", pszDescription); } @@ -617,6 +707,14 @@ bool GeoRasterWrapper::Create(char *pszDescription, char *pszInsert, if (pszInsert) { sValues = pszInsert; + sValues.Trim(); // Remove spaces on the left and on the right + + if (!ValidateInsertExpression(sValues)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "INSERT expression contains invalid values."); + return false; + } if (pszInsert[0] == '(' && sValues.ifind("VALUES") == std::string::npos) @@ -3618,6 +3716,22 @@ bool GeoRasterWrapper::FlushMetadata() } } + if (bGenStats) + { + if (GenerateStatistics(nGenStatsSamplingFactor, + dfGenStatsSamplingWindow, bGenStatsHistogram, + sGenStatsLayerNumbers.c_str(), bGenStatsUseBin, + dfGenStatsBinFunction, bGenStatsNodata)) + { + CPLDebug("GEOR", "Generated statistics successfully."); + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "Error generating statistics!"); + } + } + return true; } @@ -3775,6 +3889,89 @@ void GeoRasterWrapper::DeletePyramid() delete poStmt; } +// --------------------------------------------------------------------------- +// GenerateStatistics() +// --------------------------------------------------------------------------- +bool GeoRasterWrapper::GenerateStatistics(int nSamplingFactor, + double *pdfSamplingWindow, + bool bHistogram, + const char *pszLayerNumbers, + bool bUseBin, double *pdfBinFunction, + bool bNodata) +{ + // Length is 6 because it's the length of string FALSE. + char szHistogram[] = "FALSE"; + char szNodata[] = "FALSE"; + char szUseBin[] = "FALSE"; + char szUseWin[] = "FALSE"; + if (bHistogram) + { + strncpy(szHistogram, "TRUE", 5); + } + if (bNodata) + { + strncpy(szNodata, "TRUE", 5); + } + if (bUseBin) + { + strncpy(szUseBin, "TRUE", 5); + } + if (bGenStatsUseSamplingWindow) + { + strncpy(szUseWin, "TRUE", 5); + } + + char szLayerNumbers[OWTEXT]; + snprintf(szLayerNumbers, sizeof(szLayerNumbers), "%s", pszLayerNumbers); + + OWStatement *poStmt = poConnection->CreateStatement( + CPLSPrintf("DECLARE\n" + " gr sdo_georaster;\n" + " swin SDO_NUMBER_ARRAY := SDO_NUMBER_ARRAY(:1, :2, :3," + " :4);\n" + " binfunc SDO_NUMBER_ARRAY := SDO_NUMBER_ARRAY(:5, :6, :7," + " :8, :9);\n" + " res VARCHAR2(5);\n" + "BEGIN\n" + " IF :usewin = 'FALSE' THEN\n" + " swin := NULL;\n" + " END IF;" + " IF :usebin = 'FALSE' THEN\n" + " binfunc := NULL;\n" + " END IF;" + " SELECT t.%s INTO gr FROM %s t WHERE %s FOR UPDATE;\n" + " res := sdo_geor.generateStatistics(gr, " + "'samplingFactor='||:samplingfactor, swin,\n" + " :histogram, :layernums, :usebin, binfunc, :nodata);\n" + " UPDATE %s t SET t.%s = gr WHERE %s;\n" + " COMMIT;\n" + "END;\n", + sColumn.c_str(), sTable.c_str(), sWhere.c_str(), + sTable.c_str(), sColumn.c_str(), sWhere.c_str())); + + // Bind sampling window + for (int i = 0; i < 4; i++) + { + poStmt->Bind(&pdfSamplingWindow[i]); + } + // Bind bin function + for (int i = 0; i < 5; i++) + { + poStmt->Bind(&pdfBinFunction[i]); + } + poStmt->BindName(":samplingfactor", &nSamplingFactor); + poStmt->BindName(":layernums", szLayerNumbers); + poStmt->BindName(":histogram", szHistogram); + poStmt->BindName(":nodata", szNodata); + poStmt->BindName(":usebin", szUseBin); + poStmt->BindName(":usewin", szUseWin); + + bool bResult = poStmt->Execute(); + delete poStmt; + + return bResult; +} + // --------------------------------------------------------------------------- // CreateBitmapMask() // --------------------------------------------------------------------------- diff --git a/frmts/georaster/georasterdrivercore.cpp b/frmts/georaster/georasterdrivercore.cpp index 2acafdc91a8f..206358081f6e 100644 --- a/frmts/georaster/georasterdrivercore.cpp +++ b/frmts/georaster/georasterdrivercore.cpp @@ -100,6 +100,33 @@ void GEORDriverSetCommonMetadata(GDALDriver *poDriver) " " " " "