diff --git a/.github/workflows/asan/build.sh b/.github/workflows/asan/build.sh index 2b19db690d04..20385b49bc34 100755 --- a/.github/workflows/asan/build.sh +++ b/.github/workflows/asan/build.sh @@ -8,7 +8,7 @@ if [ "$NPROC" = "" ]; then NPROC=3 fi -SANITIZE_FLAGS="-DMAKE_SANITIZE_HAPPY -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" +SANITIZE_FLAGS="-DMAKE_SANITIZE_HAPPY -fsanitize=undefined -fsanitize=address -fsanitize=unsigned-integer-overflow -fno-sanitize-recover=unsigned-integer-overflow" SANITIZE_LDFLAGS="-fsanitize=undefined -fsanitize=address -shared-libasan -lstdc++" cmake ${GDAL_SOURCE_DIR:=..} \ @@ -18,11 +18,12 @@ cmake ${GDAL_SOURCE_DIR:=..} \ -DCMAKE_C_FLAGS="${SANITIZE_FLAGS}" \ -DCMAKE_CXX_FLAGS="${SANITIZE_FLAGS}" \ -DCMAKE_SHARED_LINKER_FLAGS="${SANITIZE_LDFLAGS}" \ - -DCMAKE_SHARED_LINKER_FLAGS="${SANITIZE_LDFLAGS}" \ + -DCMAKE_EXE_LINKER_FLAGS="${SANITIZE_LDFLAGS}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DUSE_CCACHE=ON \ -DGDAL_USE_GEOTIFF_INTERNAL=ON \ -DGDAL_USE_TIFF_INTERNAL=ON \ + -DGDAL_USE_LIBKML=OFF -DOGR_ENABLE_DRIVER_LIBKML=OFF \ -DFileGDB_ROOT=/usr/local/FileGDB_API make -j$NPROC diff --git a/.github/workflows/asan/test.sh b/.github/workflows/asan/test.sh index 2458b7455dc9..8ab93bb59724 100755 --- a/.github/workflows/asan/test.sh +++ b/.github/workflows/asan/test.sh @@ -4,13 +4,14 @@ set -ex . ../scripts/setdevenv.sh -export LD_LIBRARY_PATH=/usr/lib/llvm-10/lib/clang/10.0.0/lib/linux:${LD_LIBRARY_PATH} -export PATH=/usr/lib/llvm-10/bin:${PATH} +export LD_LIBRARY_PATH=/usr/lib/llvm-14/lib/clang/14.0.0/lib/linux:${LD_LIBRARY_PATH} +export PATH=/usr/lib/llvm-14/bin:${PATH} export SKIP_MEM_INTENSIVE_TEST=YES export SKIP_VIRTUALMEM=YES export LD_PRELOAD=$(clang -print-file-name=libclang_rt.asan-x86_64.so) export ASAN_OPTIONS=allocator_may_return_null=1:symbolize=1:suppressions=$PWD/../autotest/asan_suppressions.txt export LSAN_OPTIONS=detect_leaks=1,print_suppressions=0,suppressions=$PWD/../autotest/lsan_suppressions.txt +export PYTHONMALLOC=malloc gdalinfo autotest/gcore/data/byte.tif python3 -c "from osgeo import gdal; print('yes')" @@ -44,6 +45,8 @@ find -L \ ! -name ogr_gpsbabel.py `# new-delete-type-mismatch error in gpsbabel binary that we can't suppress` \ ! -name "__init__.py" \ ! -path 'ogr/data/*' \ + ! -name test_gdal_merge.py \ + ! -name test_gdal_retile.py \ -print \ -exec ./pytest_wrapper.sh {} \; \ | tee ./test-output.txt diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index d0aa3d14695f..4552559cd9c4 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -214,6 +214,7 @@ jobs: export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/install-gdal/lib $GITHUB_WORKSPACE/install-gdal/bin/gdalinfo --version PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3/dist-packages python3 -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3/dist-packages python3 $GITHUB_WORKSPACE/scripts/check_doc.py - name: CMake with rpath run: | export PATH=$CMAKE_DIR:/usr/local/bin:/usr/bin:/bin # Avoid CMake config from brew etc. diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index 64a90f5c5db4..1e5020588163 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -63,6 +63,7 @@ jobs: id: alpine container: alpine build_script: build.sh + os: ubuntu-22.04 - name: Alpine, gcc 32-bit id: alpine_32bit @@ -70,12 +71,14 @@ jobs: build_script: build.sh test_script: test.sh travis_branch: alpine_32bit + os: ubuntu-22.04 - name: Fedora Rawhide, clang++ id: fedora_rawhide travis_branch: sanitize container: fedora_rawhide build_script: build.sh + os: ubuntu-22.04 - name: Ubuntu 22.04, gcc id: ubuntu_22.04 @@ -84,13 +87,16 @@ jobs: before_test_script: services.sh build_script: build.sh test_script: test.sh + os: ubuntu-22.04 - - name: Ubuntu 20.04, clang ASAN + - name: Ubuntu 22.04, clang ASAN id: asan travis_branch: sanitize - container: ubuntu_20.04 + container: ubuntu_22.04 build_script: build.sh test_script: test.sh + # We force the host OS to be 20.04 to avoid "AddressSanitizer:DEADLYSIGNAL" + os: ubuntu-20.04 - name: Ubuntu 20.04, gcc id: ubuntu_20.04 @@ -99,6 +105,7 @@ jobs: use_avx2: true build_script: build.sh test_script: test.sh + os: ubuntu-22.04 - name: Ubuntu 20.04, coverage id: coverage @@ -107,6 +114,7 @@ jobs: before_test_script: services.sh build_script: build.sh test_script: test.sh + os: ubuntu-22.04 - name: Ubuntu 20.04, benchmarks id: benchmarks @@ -114,15 +122,17 @@ jobs: container: ubuntu_20.04 build_script: build.sh test_script: test.sh + os: ubuntu-22.04 - name: Ubuntu 20.04, Intel compiler id: icc container: icc build_script: build.sh + os: ubuntu-22.04 name: ${{ matrix.name }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} defaults: run: diff --git a/.github/workflows/ubuntu_22.04/Dockerfile.ci b/.github/workflows/ubuntu_22.04/Dockerfile.ci index a983e3aceb49..394f7448c20c 100644 --- a/.github/workflows/ubuntu_22.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_22.04/Dockerfile.ci @@ -9,6 +9,7 @@ RUN apt-get update && \ bash \ ccache \ cmake \ + clang \ curl \ doxygen \ fossil \ diff --git a/MIGRATION_GUIDE.TXT b/MIGRATION_GUIDE.TXT index 6680a2dd45ae..f753bd3fb528 100644 --- a/MIGRATION_GUIDE.TXT +++ b/MIGRATION_GUIDE.TXT @@ -6,6 +6,31 @@ MIGRATION GUIDE FROM GDAL 3.8 to GDAL 3.9 OGRFieldDefn*. * OGRLayer::CreateGeomField() now takes a const OGRGeomFieldDefn* instead of a OGRGeomFieldDefn*. + * OGRLayer::ICreateLayer() has a new prototype, due to RFC 99 "Geometry + coordinate precision" changes. + + The fastest migration path is from: + + OGRLayer * + MyDataset::ICreateLayer(const char* pszLayerName, + const OGRSpatialReference *poSpatialRef, + OGRwkbGeometryType eGType, char **papszOptions) + { + ... + } + + to + + OGRLayer * + MyDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) + { + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + ... + } - Sealed feature and field definition (RFC 97). A number of drivers now "seal" their layer definition, which might cause issue to user code currently diff --git a/alg/gdal_crs.cpp b/alg/gdal_crs.cpp index 0ae771881faa..74ea4ceff9a9 100644 --- a/alg/gdal_crs.cpp +++ b/alg/gdal_crs.cpp @@ -78,31 +78,29 @@ struct Control_Points }; } // namespace -typedef struct +struct GCPTransformInfo { - GDALTransformerInfo sTI; - - double adfToGeoX[20]; - double adfToGeoY[20]; - - double adfFromGeoX[20]; - double adfFromGeoY[20]; - double x1_mean; - double y1_mean; - double x2_mean; - double y2_mean; - int nOrder; - int bReversed; - - int nGCPCount; - GDAL_GCP *pasGCPList; - int bRefine; - int nMinimumGcps; - double dfTolerance; - - volatile int nRefCount; - -} GCPTransformInfo; + GDALTransformerInfo sTI{}; + + double adfToGeoX[20]{}; + double adfToGeoY[20]{}; + + double adfFromGeoX[20]{}; + double adfFromGeoY[20]{}; + double x1_mean{}; + double y1_mean{}; + double x2_mean{}; + double y2_mean{}; + int nOrder{}; + int bReversed{}; + + std::vector asGCPs{}; + int bRefine{}; + int nMinimumGcps{}; + double dfTolerance{}; + + volatile int nRefCount{}; +}; CPL_C_START CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg); @@ -138,7 +136,6 @@ static const char *const CRS_error_message[] = { static void *GDALCreateSimilarGCPTransformer(void *hTransformArg, double dfRatioX, double dfRatioY) { - GDAL_GCP *pasGCPList = nullptr; GCPTransformInfo *psInfo = static_cast(hTransformArg); VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarGCPTransformer", @@ -152,18 +149,17 @@ static void *GDALCreateSimilarGCPTransformer(void *hTransformArg, } else { - pasGCPList = GDALDuplicateGCPs(psInfo->nGCPCount, psInfo->pasGCPList); - for (int i = 0; i < psInfo->nGCPCount; i++) + auto newGCPs = psInfo->asGCPs; + for (auto &gcp : newGCPs) { - pasGCPList[i].dfGCPPixel /= dfRatioX; - pasGCPList[i].dfGCPLine /= dfRatioY; + gcp.Pixel() /= dfRatioX; + gcp.Line() /= dfRatioY; } /* As remove_outliers modifies the provided GCPs we don't need to * reapply it */ psInfo = static_cast(GDALCreateGCPTransformer( - psInfo->nGCPCount, pasGCPList, psInfo->nOrder, psInfo->bReversed)); - GDALDeinitGCPs(psInfo->nGCPCount, pasGCPList); - CPLFree(pasGCPList); + static_cast(newGCPs.size()), gdal::GCP::c_ptr(newGCPs), + psInfo->nOrder, psInfo->bReversed)); } return psInfo; @@ -214,8 +210,7 @@ static void *GDALCreateGCPTransformerEx(int nGCPCount, nReqOrder = 1; } - psInfo = - static_cast(CPLCalloc(sizeof(GCPTransformInfo), 1)); + psInfo = new GCPTransformInfo(); psInfo->bReversed = bReversed; psInfo->nOrder = nReqOrder; psInfo->bRefine = bRefine; @@ -224,8 +219,7 @@ static void *GDALCreateGCPTransformerEx(int nGCPCount, psInfo->nRefCount = 1; - psInfo->pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPList); - psInfo->nGCPCount = nGCPCount; + psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE, strlen(GDAL_GTI2_SIGNATURE)); @@ -386,10 +380,7 @@ void GDALDestroyGCPTransformer(void *pTransformArg) if (CPLAtomicDec(&(psInfo->nRefCount)) == 0) { - GDALDeinitGCPs(psInfo->nGCPCount, psInfo->pasGCPList); - CPLFree(psInfo->pasGCPList); - - CPLFree(pTransformArg); + delete psInfo; } } @@ -493,15 +484,14 @@ CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg) /* -------------------------------------------------------------------- */ /* Attach GCP List. */ /* -------------------------------------------------------------------- */ - if (psInfo->nGCPCount > 0) + if (!psInfo->asGCPs.empty()) { if (psInfo->bRefine) { remove_outliers(psInfo); } - GDALSerializeGCPListToXML(psTree, psInfo->pasGCPList, psInfo->nGCPCount, - nullptr); + GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr); } return psTree; @@ -514,8 +504,7 @@ CPLXMLNode *GDALSerializeGCPTransformer(void *pTransformArg) void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) { - GDAL_GCP *pasGCPList = nullptr; - int nGCPCount = 0; + std::vector asGCPs; void *pResult = nullptr; int nReqOrder = 0; int bReversed = 0; @@ -530,8 +519,7 @@ void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) if (psGCPList != nullptr) { - GDALDeserializeGCPListFromXML(psGCPList, &pasGCPList, &nGCPCount, - nullptr); + GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr); } /* -------------------------------------------------------------------- */ @@ -548,22 +536,17 @@ void *GDALDeserializeGCPTransformer(CPLXMLNode *psTree) /* -------------------------------------------------------------------- */ if (bRefine) { - pResult = GDALCreateGCPRefineTransformer(nGCPCount, pasGCPList, - nReqOrder, bReversed, - dfTolerance, nMinimumGcps); + pResult = GDALCreateGCPRefineTransformer( + static_cast(asGCPs.size()), gdal::GCP::c_ptr(asGCPs), + nReqOrder, bReversed, dfTolerance, nMinimumGcps); } else { - pResult = GDALCreateGCPTransformer(nGCPCount, pasGCPList, nReqOrder, + pResult = GDALCreateGCPTransformer(static_cast(asGCPs.size()), + gdal::GCP::c_ptr(asGCPs), nReqOrder, bReversed); } - /* -------------------------------------------------------------------- */ - /* Cleanup GCP copy. */ - /* -------------------------------------------------------------------- */ - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - return pResult; } @@ -1121,7 +1104,7 @@ static int remove_outliers(GCPTransformInfo *psInfo) double y2_sum = 0; memset(&sPoints, 0, sizeof(sPoints)); - nGCPCount = psInfo->nGCPCount; + nGCPCount = static_cast(psInfo->asGCPs.size()); nMinimumGcps = psInfo->nMinimumGcps; nReqOrder = psInfo->nOrder; dfTolerance = psInfo->dfTolerance; @@ -1137,14 +1120,14 @@ static int remove_outliers(GCPTransformInfo *psInfo) for (int nI = 0; nI < nGCPCount; nI++) { panStatus[nI] = 1; - padfGeoX[nI] = psInfo->pasGCPList[nI].dfGCPX; - padfGeoY[nI] = psInfo->pasGCPList[nI].dfGCPY; - padfRasterX[nI] = psInfo->pasGCPList[nI].dfGCPPixel; - padfRasterY[nI] = psInfo->pasGCPList[nI].dfGCPLine; - x1_sum += psInfo->pasGCPList[nI].dfGCPPixel; - y1_sum += psInfo->pasGCPList[nI].dfGCPLine; - x2_sum += psInfo->pasGCPList[nI].dfGCPX; - y2_sum += psInfo->pasGCPList[nI].dfGCPY; + padfGeoX[nI] = psInfo->asGCPs[nI].X(); + padfGeoY[nI] = psInfo->asGCPs[nI].Y(); + padfRasterX[nI] = psInfo->asGCPs[nI].Pixel(); + padfRasterY[nI] = psInfo->asGCPs[nI].Line(); + x1_sum += psInfo->asGCPs[nI].Pixel(); + y1_sum += psInfo->asGCPs[nI].Line(); + x2_sum += psInfo->asGCPs[nI].X(); + y2_sum += psInfo->asGCPs[nI].Y(); } psInfo->x1_mean = x1_sum / nGCPCount; psInfo->y1_mean = y1_sum / nGCPCount; @@ -1174,18 +1157,14 @@ static int remove_outliers(GCPTransformInfo *psInfo) break; } - CPLFree(psInfo->pasGCPList[nIndex].pszId); - CPLFree(psInfo->pasGCPList[nIndex].pszInfo); - for (int nI = nIndex; nI < sPoints.count - 1; nI++) { sPoints.e1[nI] = sPoints.e1[nI + 1]; sPoints.n1[nI] = sPoints.n1[nI + 1]; sPoints.e2[nI] = sPoints.e2[nI + 1]; sPoints.n2[nI] = sPoints.n2[nI + 1]; - psInfo->pasGCPList[nI].pszId = psInfo->pasGCPList[nI + 1].pszId; - psInfo->pasGCPList[nI].pszInfo = - psInfo->pasGCPList[nI + 1].pszInfo; + psInfo->asGCPs[nI].SetId(psInfo->asGCPs[nI + 1].Id()); + psInfo->asGCPs[nI].SetInfo(psInfo->asGCPs[nI + 1].Info()); } sPoints.count = sPoints.count - 1; @@ -1197,12 +1176,12 @@ static int remove_outliers(GCPTransformInfo *psInfo) for (int nI = 0; nI < sPoints.count; nI++) { - psInfo->pasGCPList[nI].dfGCPX = sPoints.e2[nI]; - psInfo->pasGCPList[nI].dfGCPY = sPoints.n2[nI]; - psInfo->pasGCPList[nI].dfGCPPixel = sPoints.e1[nI]; - psInfo->pasGCPList[nI].dfGCPLine = sPoints.n1[nI]; + psInfo->asGCPs[nI].X() = sPoints.e2[nI]; + psInfo->asGCPs[nI].Y() = sPoints.n2[nI]; + psInfo->asGCPs[nI].Pixel() = sPoints.e1[nI]; + psInfo->asGCPs[nI].Line() = sPoints.n1[nI]; } - psInfo->nGCPCount = sPoints.count; + psInfo->asGCPs.resize(sPoints.count); } catch (const std::exception &e) { diff --git a/alg/gdal_tps.cpp b/alg/gdal_tps.cpp index 5d53d8bbb8ad..be7159bc50aa 100644 --- a/alg/gdal_tps.cpp +++ b/alg/gdal_tps.cpp @@ -52,24 +52,22 @@ CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg); void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree); CPL_C_END -typedef struct +struct TPSTransformInfo { - GDALTransformerInfo sTI; + GDALTransformerInfo sTI{}; - VizGeorefSpline2D *poForward; - VizGeorefSpline2D *poReverse; - bool bForwardSolved; - bool bReverseSolved; - double dfSrcApproxErrorReverse; + VizGeorefSpline2D *poForward{}; + VizGeorefSpline2D *poReverse{}; + bool bForwardSolved{}; + bool bReverseSolved{}; + double dfSrcApproxErrorReverse{}; - bool bReversed; + bool bReversed{}; - int nGCPCount; - GDAL_GCP *pasGCPList; + std::vector asGCPs{}; - volatile int nRefCount; - -} TPSTransformInfo; + volatile int nRefCount{}; +}; /************************************************************************/ /* GDALCreateSimilarTPSTransformer() */ @@ -91,17 +89,15 @@ static void *GDALCreateSimilarTPSTransformer(void *hTransformArg, } else { - GDAL_GCP *pasGCPList = - GDALDuplicateGCPs(psInfo->nGCPCount, psInfo->pasGCPList); - for (int i = 0; i < psInfo->nGCPCount; i++) + auto newGCPs = psInfo->asGCPs; + for (auto &gcp : newGCPs) { - pasGCPList[i].dfGCPPixel /= dfRatioX; - pasGCPList[i].dfGCPLine /= dfRatioY; + gcp.Pixel() /= dfRatioX; + gcp.Line() /= dfRatioY; } psInfo = static_cast(GDALCreateTPSTransformer( - psInfo->nGCPCount, pasGCPList, psInfo->bReversed)); - GDALDeinitGCPs(psInfo->nGCPCount, pasGCPList); - CPLFree(pasGCPList); + static_cast(newGCPs.size()), gdal::GCP::c_ptr(newGCPs), + psInfo->bReversed)); } return psInfo; @@ -160,11 +156,9 @@ void *GDALCreateTPSTransformerInt(int nGCPCount, const GDAL_GCP *pasGCPList, /* -------------------------------------------------------------------- */ /* Allocate transform info. */ /* -------------------------------------------------------------------- */ - TPSTransformInfo *psInfo = - static_cast(CPLCalloc(sizeof(TPSTransformInfo), 1)); + TPSTransformInfo *psInfo = new TPSTransformInfo(); - psInfo->pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPList); - psInfo->nGCPCount = nGCPCount; + psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); psInfo->bReversed = CPL_TO_BOOL(bReversed); psInfo->poForward = new VizGeorefSpline2D(2); @@ -320,10 +314,7 @@ void GDALDestroyTPSTransformer(void *pTransformArg) delete psInfo->poForward; delete psInfo->poReverse; - GDALDeinitGCPs(psInfo->nGCPCount, psInfo->pasGCPList); - CPLFree(psInfo->pasGCPList); - - CPLFree(pTransformArg); + delete psInfo; } } @@ -428,10 +419,9 @@ CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg) /* -------------------------------------------------------------------- */ /* Attach GCP List. */ /* -------------------------------------------------------------------- */ - if (psInfo->nGCPCount > 0) + if (!psInfo->asGCPs.empty()) { - GDALSerializeGCPListToXML(psTree, psInfo->pasGCPList, psInfo->nGCPCount, - nullptr); + GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr); } if (psInfo->dfSrcApproxErrorReverse > 0) @@ -455,13 +445,11 @@ void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree) /* Check for GCPs. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"); - GDAL_GCP *pasGCPList = nullptr; - int nGCPCount = 0; + std::vector asGCPs; if (psGCPList != nullptr) { - GDALDeserializeGCPListFromXML(psGCPList, &pasGCPList, &nGCPCount, - nullptr); + GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr); } /* -------------------------------------------------------------------- */ @@ -477,14 +465,9 @@ void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree) /* -------------------------------------------------------------------- */ /* Generate transformation. */ /* -------------------------------------------------------------------- */ - void *pResult = GDALCreateTPSTransformerInt(nGCPCount, pasGCPList, + void *pResult = GDALCreateTPSTransformerInt(static_cast(asGCPs.size()), + gdal::GCP::c_ptr(asGCPs), bReversed, aosOptions.List()); - /* -------------------------------------------------------------------- */ - /* Cleanup GCP copy. */ - /* -------------------------------------------------------------------- */ - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - return pResult; } diff --git a/alg/gdalwarper.h b/alg/gdalwarper.h index 7c93bcf667ab..5be66f482029 100644 --- a/alg/gdalwarper.h +++ b/alg/gdalwarper.h @@ -507,13 +507,6 @@ class CPL_DLL GDALWarpOperation double &dfMinXOut, double &dfMinYOut, double &dfMaxXOut, double &dfMaxYOut, int &nSamplePoints, int &nFailedCount); - CPLErr ComputeSourceWindow(int nDstXOff, int nDstYOff, int nDstXSize, - int nDstYSize, int *pnSrcXOff, int *pnSrcYOff, - int *pnSrcXSize, int *pnSrcYSize, - double *pdfSrcXExtraSize, - double *pdfSrcYExtraSize, - double *pdfSrcFillRatio); - void ComputeSourceWindowStartingFromSource(int nDstXOff, int nDstYOff, int nDstXSize, int nDstYSize, double *padfSrcMinX, @@ -584,6 +577,18 @@ class CPL_DLL GDALWarpOperation int nSrcYOff, int nSrcXSize, int nSrcYSize, double dfSrcXExtraSize, double dfSrcYExtraSize, double dfProgressBase, double dfProgressScale); + + protected: + friend class VRTWarpedDataset; + CPLErr ComputeSourceWindow(int nDstXOff, int nDstYOff, int nDstXSize, + int nDstYSize, int *pnSrcXOff, int *pnSrcYOff, + int *pnSrcXSize, int *pnSrcYSize, + double *pdfSrcXExtraSize, + double *pdfSrcYExtraSize, + double *pdfSrcFillRatio); + + double GetWorkingMemoryForWindow(int nSrcXSize, int nSrcYSize, + int nDstXSize, int nDstYSize) const; }; #endif /* def __cplusplus */ diff --git a/alg/gdalwarpoperation.cpp b/alg/gdalwarpoperation.cpp index 11907640aa0e..74082c5cfc2e 100644 --- a/alg/gdalwarpoperation.cpp +++ b/alg/gdalwarpoperation.cpp @@ -1259,46 +1259,18 @@ void GDALWarpOperation::WipeChunkList() } /************************************************************************/ -/* CollectChunkListInternal() */ +/* GetWorkingMemoryForWindow() */ /************************************************************************/ -CPLErr GDALWarpOperation::CollectChunkListInternal(int nDstXOff, int nDstYOff, - int nDstXSize, int nDstYSize) - +/** Retrurns the amount of working memory, in bytes, required to process + * a warped window of source dimensions nSrcXSize x nSrcYSize and target + * dimensions nDstXSize x nDstYSize. + */ +double GDALWarpOperation::GetWorkingMemoryForWindow(int nSrcXSize, + int nSrcYSize, + int nDstXSize, + int nDstYSize) const { - /* -------------------------------------------------------------------- */ - /* Compute the bounds of the input area corresponding to the */ - /* output area. */ - /* -------------------------------------------------------------------- */ - int nSrcXOff = 0; - int nSrcYOff = 0; - int nSrcXSize = 0; - int nSrcYSize = 0; - double dfSrcXExtraSize = 0.0; - double dfSrcYExtraSize = 0.0; - double dfSrcFillRatio = 0.0; - CPLErr eErr = - ComputeSourceWindow(nDstXOff, nDstYOff, nDstXSize, nDstYSize, &nSrcXOff, - &nSrcYOff, &nSrcXSize, &nSrcYSize, &dfSrcXExtraSize, - &dfSrcYExtraSize, &dfSrcFillRatio); - - if (eErr != CE_None) - { - CPLError(CE_Warning, CPLE_AppDefined, - "Unable to compute source region for " - "output window %d,%d,%d,%d, skipping.", - nDstXOff, nDstYOff, nDstXSize, nDstYSize); - return eErr; - } - - /* -------------------------------------------------------------------- */ - /* If we are allowed to drop no-source regions, do so now if */ - /* appropriate. */ - /* -------------------------------------------------------------------- */ - if ((nSrcXSize == 0 || nSrcYSize == 0) && - CPLFetchBool(psOptions->papszWarpOptions, "SKIP_NOSOURCE", false)) - return CE_None; - /* -------------------------------------------------------------------- */ /* Based on the types of masks in use, how many bits will each */ /* source pixel cost us? */ @@ -1343,24 +1315,62 @@ CPLErr GDALWarpOperation::CollectChunkListInternal(int nDstXOff, int nDstYOff, if (psOptions->nDstAlphaBand > 0) nDstPixelCostInBits += 32; // DstDensity float mask. - /* -------------------------------------------------------------------- */ - /* Does the cost of the current rectangle exceed our memory */ - /* limit? If so, split the destination along the longest */ - /* dimension and recurse. */ - /* -------------------------------------------------------------------- */ - double dfTotalMemoryUse = + const double dfTotalMemoryUse = (static_cast(nSrcPixelCostInBits) * nSrcXSize * nSrcYSize + static_cast(nDstPixelCostInBits) * nDstXSize * nDstYSize) / 8.0; + return dfTotalMemoryUse; +} + +/************************************************************************/ +/* CollectChunkListInternal() */ +/************************************************************************/ + +CPLErr GDALWarpOperation::CollectChunkListInternal(int nDstXOff, int nDstYOff, + int nDstXSize, int nDstYSize) - int nBlockXSize = 1; - int nBlockYSize = 1; - if (psOptions->hDstDS) +{ + /* -------------------------------------------------------------------- */ + /* Compute the bounds of the input area corresponding to the */ + /* output area. */ + /* -------------------------------------------------------------------- */ + int nSrcXOff = 0; + int nSrcYOff = 0; + int nSrcXSize = 0; + int nSrcYSize = 0; + double dfSrcXExtraSize = 0.0; + double dfSrcYExtraSize = 0.0; + double dfSrcFillRatio = 0.0; + CPLErr eErr = + ComputeSourceWindow(nDstXOff, nDstYOff, nDstXSize, nDstYSize, &nSrcXOff, + &nSrcYOff, &nSrcXSize, &nSrcYSize, &dfSrcXExtraSize, + &dfSrcYExtraSize, &dfSrcFillRatio); + + if (eErr != CE_None) { - GDALGetBlockSize(GDALGetRasterBand(psOptions->hDstDS, 1), &nBlockXSize, - &nBlockYSize); + CPLError(CE_Warning, CPLE_AppDefined, + "Unable to compute source region for " + "output window %d,%d,%d,%d, skipping.", + nDstXOff, nDstYOff, nDstXSize, nDstYSize); + return eErr; } + /* -------------------------------------------------------------------- */ + /* If we are allowed to drop no-source regions, do so now if */ + /* appropriate. */ + /* -------------------------------------------------------------------- */ + if ((nSrcXSize == 0 || nSrcYSize == 0) && + CPLFetchBool(psOptions->papszWarpOptions, "SKIP_NOSOURCE", false)) + return CE_None; + + /* -------------------------------------------------------------------- */ + /* Does the cost of the current rectangle exceed our memory */ + /* limit? If so, split the destination along the longest */ + /* dimension and recurse. */ + /* -------------------------------------------------------------------- */ + const double dfTotalMemoryUse = + GetWorkingMemoryForWindow(nSrcXSize, nSrcYSize, nDstXSize, nDstYSize); + // If size of working buffers need exceed the allow limit, then divide // the target area // Do it also if the "fill ratio" of the source is too low (#3120), but @@ -1382,6 +1392,14 @@ CPLErr GDALWarpOperation::CollectChunkListInternal(int nDstXOff, int nDstYOff, CPLFetchBool(psOptions->papszWarpOptions, "SRC_FILL_RATIO_HEURISTICS", true))) { + int nBlockXSize = 1; + int nBlockYSize = 1; + if (psOptions->hDstDS) + { + GDALGetBlockSize(GDALGetRasterBand(psOptions->hDstDS, 1), + &nBlockXSize, &nBlockYSize); + } + int bStreamableOutput = CPLFetchBool(psOptions->papszWarpOptions, "STREAMABLE_OUTPUT", false); const char *pszOptimizeSize = @@ -2838,6 +2856,20 @@ bool GDALWarpOperation::ComputeSourceWindowTransformPoints( /* ComputeSourceWindow() */ /************************************************************************/ +/** Given a target window starting at pixel (nDstOff, nDstYOff) and of + * dimension (nDstXSize, nDstYSize), compute the corresponding window in + * the source raster, and return the source position in (*pnSrcXOff, *pnSrcYOff), + * the source dimension in (*pnSrcXSize, *pnSrcYSize). + * If pdfSrcXExtraSize is not null, its pointed value will be filled with the + * number of extra source pixels in X dimension to acquire to take into account + * the size of the resampling kernel. Similarly for pdfSrcYExtraSize for the + * Y dimension. + * If pdfSrcFillRatio is not null, its pointed value will be filled with the + * the ratio of the clamped source raster window size over the unclamped source + * raster window size. When this ratio is too low, this might be an indication + * that it might be beneficial to split the target window to avoid requesting + * too many source pixels. + */ CPLErr GDALWarpOperation::ComputeSourceWindow( int nDstXOff, int nDstYOff, int nDstXSize, int nDstYSize, int *pnSrcXOff, int *pnSrcYOff, int *pnSrcXSize, int *pnSrcYSize, double *pdfSrcXExtraSize, 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/ogrinfo_output.schema.json b/apps/data/ogrinfo_output.schema.json index 75c3bee14cdb..af23826d79eb 100644 --- a/apps/data/ogrinfo_output.schema.json +++ b/apps/data/ogrinfo_output.schema.json @@ -265,6 +265,18 @@ } ] } + }, + "xyCoordinateResolution": { + "type": "number" + }, + "zCoordinateResolution": { + "type": "number" + }, + "mCoordinateResolution": { + "type": "number" + }, + "coordinatePrecisionFormatSpecificOptions": { + "type": "object" } }, "required": [ diff --git a/apps/gdal_grid_lib.cpp b/apps/gdal_grid_lib.cpp index ebfce2376313..777861f924b8 100644 --- a/apps/gdal_grid_lib.cpp +++ b/apps/gdal_grid_lib.cpp @@ -820,20 +820,24 @@ GDALDatasetH GDALGrid(const char *pszDest, GDALDatasetH hSrcDataset, { OGRLayer *poLayer = poSrcDS->ExecuteSQL( psOptions->pszSQL, psOptions->poSpatialFilter, nullptr); - if (poLayer != nullptr) + if (poLayer == nullptr) { - // Custom layer will be rasterized in the first band. - eErr = ProcessLayer( - OGRLayer::ToHandle(poLayer), hDstDS, psOptions->poSpatialFilter, - nXSize, nYSize, 1, bIsXExtentSet, bIsYExtentSet, dfXMin, dfXMax, - dfYMin, dfYMax, psOptions->pszBurnAttribute, - psOptions->dfIncreaseBurnValue, psOptions->dfMultiplyBurnValue, - psOptions->eOutputType, psOptions->eAlgorithm, - psOptions->pOptions, psOptions->bQuiet, psOptions->pfnProgress, - psOptions->pProgressData); - - poSrcDS->ReleaseResultSet(poLayer); + GDALGridOptionsFree(psOptionsToFree); + GDALClose(hDstDS); + return nullptr; } + + // Custom layer will be rasterized in the first band. + eErr = ProcessLayer( + OGRLayer::ToHandle(poLayer), hDstDS, psOptions->poSpatialFilter, + nXSize, nYSize, 1, bIsXExtentSet, bIsYExtentSet, dfXMin, dfXMax, + dfYMin, dfYMax, psOptions->pszBurnAttribute, + psOptions->dfIncreaseBurnValue, psOptions->dfMultiplyBurnValue, + psOptions->eOutputType, psOptions->eAlgorithm, psOptions->pOptions, + psOptions->bQuiet, psOptions->pfnProgress, + psOptions->pProgressData); + + poSrcDS->ReleaseResultSet(poLayer); } /* -------------------------------------------------------------------- */ @@ -850,18 +854,22 @@ GDALDatasetH GDALGrid(const char *pszDest, GDALDatasetH hSrcDataset, if (hLayer == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, - "Unable to find layer \"%s\", skipping.", + "Unable to find layer \"%s\".", psOptions->papszLayers && psOptions->papszLayers[i] ? psOptions->papszLayers[i] : "null"); - continue; + eErr = CE_Failure; + break; } if (psOptions->pszWHERE) { if (OGR_L_SetAttributeFilter(hLayer, psOptions->pszWHERE) != OGRERR_NONE) + { + eErr = CE_Failure; break; + } } if (psOptions->poSpatialFilter != nullptr) diff --git a/apps/gdal_translate_lib.cpp b/apps/gdal_translate_lib.cpp index 3f89858554cf..3490d8234d87 100644 --- a/apps/gdal_translate_lib.cpp +++ b/apps/gdal_translate_lib.cpp @@ -1917,6 +1917,8 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset, /* ==================================================================== */ /* Process all bands. */ /* ==================================================================== */ + GDALDataType eOutputType = psOptions->eOutputType; + for (int i = 0; i < psOptions->nBandCount; i++) { int nComponent = 0; @@ -1947,13 +1949,44 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset, GDALRasterBand *poRealSrcBand = (nSrcBand < 0) ? poSrcBand->GetMaskBand() : poSrcBand; GDALDataType eBandType; - if (psOptions->eOutputType == GDT_Unknown) + if (eOutputType == GDT_Unknown) { eBandType = poRealSrcBand->GetRasterDataType(); + if (eBandType != GDT_Byte && psOptions->nRGBExpand != 0) + { + // Use case of https://github.com/OSGeo/gdal/issues/9402 + if (const auto poColorTable = poRealSrcBand->GetColorTable()) + { + bool bIn0To255Range = true; + const int nColorCount = poColorTable->GetColorEntryCount(); + for (int nColor = 0; nColor < nColorCount; nColor++) + { + const GDALColorEntry *poEntry = + poColorTable->GetColorEntry(nColor); + if (poEntry->c1 > 255 || poEntry->c2 > 255 || + poEntry->c3 > 255 || poEntry->c4 > 255) + { + bIn0To255Range = false; + break; + } + } + if (bIn0To255Range) + { + if (!psOptions->bQuiet) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Using Byte output data type due to range " + "of values in color table"); + } + eBandType = GDT_Byte; + } + } + eOutputType = eBandType; + } } else { - eBandType = psOptions->eOutputType; + eBandType = eOutputType; // Check that we can copy existing statistics GDALDataType eSrcBandType = poRealSrcBand->GetRasterDataType(); diff --git a/apps/gdalinfo_lib.cpp b/apps/gdalinfo_lib.cpp index 13524b5b507e..2ca2466dde8c 100644 --- a/apps/gdalinfo_lib.cpp +++ b/apps/gdalinfo_lib.cpp @@ -717,15 +717,20 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions) { OGRSpatialReferenceH hLatLong = nullptr; - OGRErr eErr = OGRERR_NONE; - // Check that it looks like Earth before trying to reproject to wgs84... - if (bJson && - fabs(OSRGetSemiMajor(hProj, &eErr) - 6378137.0) < 10000.0 && - eErr == OGRERR_NONE) - { - bTransformToWGS84 = true; - hLatLong = OSRNewSpatialReference(nullptr); - OSRSetWellKnownGeogCS(hLatLong, "WGS84"); + if (bJson) + { + // Check that it looks like Earth before trying to reproject to wgs84... + // OSRGetSemiMajor() may raise an error on CRS like Engineering CRS + CPLErrorHandlerPusher oPusher(CPLQuietErrorHandler); + CPLErrorStateBackuper oCPLErrorHandlerPusher; + OGRErr eErr = OGRERR_NONE; + if (fabs(OSRGetSemiMajor(hProj, &eErr) - 6378137.0) < 10000.0 && + eErr == OGRERR_NONE) + { + bTransformToWGS84 = true; + hLatLong = OSRNewSpatialReference(nullptr); + OSRSetWellKnownGeogCS(hLatLong, "WGS84"); + } } else { diff --git a/apps/gdallocationinfo.cpp b/apps/gdallocationinfo.cpp index b46468e05b09..22e54269348b 100644 --- a/apps/gdallocationinfo.cpp +++ b/apps/gdallocationinfo.cpp @@ -35,6 +35,8 @@ #include "ogr_spatialref.h" #include +#include + #ifdef _WIN32 #include #else @@ -52,6 +54,8 @@ static void Usage(bool bIsError) bIsError ? stderr : stdout, "Usage: gdallocationinfo [--help] [--help-general]\n" " [-xml] [-lifonly] [-valonly]\n" + " [-E] [-field_sep ] " + "[-ignore_extra_input]\n" " [-b ]... [-overview ]\n" " [-l_srs ] [-geoloc] [-wgs84]\n" " [-oo =]... [ ]\n" @@ -100,6 +104,9 @@ MAIN_START(argc, argv) bool bQuiet = false, bValOnly = false; int nOverview = -1; char **papszOpenOptions = nullptr; + std::string osFieldSep; + bool bIgnoreExtraInput = false; + bool bEcho = false; GDALAllRegister(); argc = GDALGeneralCmdLineProcessor(argc, &argv, 0); @@ -162,6 +169,21 @@ MAIN_START(argc, argv) bValOnly = true; bQuiet = true; } + else if (i < argc - 1 && EQUAL(argv[i], "-field_sep")) + { + osFieldSep = CPLString(argv[++i]) + .replaceAll("\\t", '\t') + .replaceAll("\\r", '\r') + .replaceAll("\\n", '\n'); + } + else if (EQUAL(argv[i], "-ignore_extra_input")) + { + bIgnoreExtraInput = true; + } + else if (EQUAL(argv[i], "-E")) + { + bEcho = true; + } else if (i < argc - 1 && EQUAL(argv[i], "-oo")) { papszOpenOptions = CSLAddString(papszOpenOptions, argv[++i]); @@ -186,6 +208,28 @@ MAIN_START(argc, argv) if (pszSrcFilename == nullptr || (pszLocX != nullptr && pszLocY == nullptr)) Usage(true); + if (bEcho && !bValOnly) + { + fprintf(stderr, "-E can only be used with -valonly\n"); + exit(1); + } + if (bEcho && osFieldSep.empty()) + { + fprintf(stderr, "-E can only be used if -field_sep is specified (to a " + "non-newline value)\n"); + exit(1); + } + + if (osFieldSep.empty()) + { + osFieldSep = "\n"; + } + else if (!bValOnly) + { + fprintf(stderr, "-field_sep can only be used with -valonly\n"); + exit(1); + } + /* -------------------------------------------------------------------- */ /* Open source file. */ /* -------------------------------------------------------------------- */ @@ -226,10 +270,13 @@ MAIN_START(argc, argv) /* -------------------------------------------------------------------- */ /* Turn the location into a pixel and line location. */ /* -------------------------------------------------------------------- */ - int inputAvailable = 1; - double dfGeoX; - double dfGeoY; + bool inputAvailable = true; + double dfGeoX = 0; + double dfGeoY = 0; CPLString osXML; + char szLine[1024]; + int nLine = 0; + std::string osExtraContent; if (pszLocX == nullptr && pszLocY == nullptr) { @@ -249,9 +296,40 @@ MAIN_START(argc, argv) } } - if (fscanf(stdin, "%lf %lf", &dfGeoX, &dfGeoY) != 2) + if (fgets(szLine, sizeof(szLine) - 1, stdin)) { - inputAvailable = 0; + const CPLStringList aosTokens(CSLTokenizeString(szLine)); + const int nCount = aosTokens.size(); + + ++nLine; + if (nCount < 2) + { + fprintf(stderr, "Not enough values at line %d\n", nLine); + inputAvailable = false; + } + else + { + dfGeoX = CPLAtof(aosTokens[0]); + dfGeoY = CPLAtof(aosTokens[1]); + if (!bIgnoreExtraInput) + { + for (int i = 2; i < nCount; ++i) + { + if (!osExtraContent.empty()) + osExtraContent += ' '; + osExtraContent += aosTokens[i]; + } + while (!osExtraContent.empty() && + isspace(static_cast(osExtraContent.back()))) + { + osExtraContent.pop_back(); + } + } + } + } + else + { + inputAvailable = false; } } else @@ -313,11 +391,28 @@ MAIN_START(argc, argv) { osLine.Printf("", iPixel, iLine); osXML += osLine; + if (!osExtraContent.empty()) + { + char *pszEscaped = + CPLEscapeString(osExtraContent.c_str(), -1, CPLES_XML); + osXML += CPLString().Printf(" %s", + pszEscaped); + CPLFree(pszEscaped); + } } else if (!bQuiet) { printf("Report:\n"); printf(" Location: (%dP,%dL)\n", iPixel, iLine); + if (!osExtraContent.empty()) + { + printf(" Extra input: %s\n", osExtraContent.c_str()); + } + } + else if (bEcho) + { + printf("%d%s%d%s", iPixel, osFieldSep.c_str(), iLine, + osFieldSep.c_str()); } bool bPixelReport = true; @@ -468,7 +563,11 @@ MAIN_START(argc, argv) else if (!bQuiet) printf(" Value: %s\n", osValue.c_str()); else if (bValOnly) - printf("%s\n", osValue.c_str()); + { + if (i > 0) + printf("%s", osFieldSep.c_str()); + printf("%s", osValue.c_str()); + } // Report unscaled if we have scale/offset values. int bSuccess; @@ -522,10 +621,51 @@ MAIN_START(argc, argv) osXML += ""; - if ((pszLocX != nullptr && pszLocY != nullptr) || - (fscanf(stdin, "%lf %lf", &dfGeoX, &dfGeoY) != 2)) + if (bValOnly) + { + if (!osExtraContent.empty() && osFieldSep != "\n") + printf("%s%s", osFieldSep.c_str(), osExtraContent.c_str()); + printf("\n"); + } + + if (pszLocX != nullptr && pszLocY != nullptr) + break; + + osExtraContent.clear(); + if (fgets(szLine, sizeof(szLine) - 1, stdin)) + { + const CPLStringList aosTokens(CSLTokenizeString(szLine)); + const int nCount = aosTokens.size(); + + ++nLine; + if (nCount < 2) + { + fprintf(stderr, "Not enough values at line %d\n", nLine); + continue; + } + else + { + dfGeoX = CPLAtof(aosTokens[0]); + dfGeoY = CPLAtof(aosTokens[1]); + if (!bIgnoreExtraInput) + { + for (int i = 2; i < nCount; ++i) + { + if (!osExtraContent.empty()) + osExtraContent += ' '; + osExtraContent += aosTokens[i]; + } + while (!osExtraContent.empty() && + isspace(static_cast(osExtraContent.back()))) + { + osExtraContent.pop_back(); + } + } + } + } + else { - inputAvailable = 0; + break; } } diff --git a/apps/gdaltransform.cpp b/apps/gdaltransform.cpp index e161638b9432..11e605fe1e02 100644 --- a/apps/gdaltransform.cpp +++ b/apps/gdaltransform.cpp @@ -31,6 +31,7 @@ #include #include +#include #include "cpl_conv.h" #include "cpl_error.h" @@ -65,8 +66,8 @@ static void Usage(bool bIsError, const char *pszErrorMsg = nullptr) ">NAME>=]...\n" " [-s_coord_epoch ] [-t_coord_epoch ]\n" " [-ct ] [-order ] [-tps] [-rpc] [-geoloc] \n" - " [-gcp [elevation]]... " - "[-output_xy]\n" + " [-gcp [elevation]]...\n" + " [-output_xy] [-E] [-field_sep ] [-ignore_extra_input]\n" " [ []]\n" "\n"); @@ -147,6 +148,9 @@ MAIN_START(argc, argv) double dfZ = 0.0; double dfT = 0.0; bool bCoordOnCommandLine = false; + bool bIgnoreExtraInput = false; + bool bEchoInput = false; + std::string osFieldSep = " "; /* -------------------------------------------------------------------- */ /* Parse arguments. */ @@ -266,6 +270,21 @@ MAIN_START(argc, argv) { bOutputXY = TRUE; } + else if (EQUAL(argv[i], "-ignore_extra_input")) + { + bIgnoreExtraInput = true; + } + else if (EQUAL(argv[i], "-E")) + { + bEchoInput = true; + } + else if (i < argc - 1 && EQUAL(argv[i], "-field_sep")) + { + osFieldSep = CPLString(argv[++i]) + .replaceAll("\\t", '\t') + .replaceAll("\\r", '\r') + .replaceAll("\\n", '\n'); + } else if (EQUAL(argv[i], "-coord") && i + 2 < argc) { bCoordOnCommandLine = true; @@ -372,8 +391,10 @@ MAIN_START(argc, argv) } } + int nLine = 0; while (bCoordOnCommandLine || !feof(stdin)) { + std::string osExtraContent; if (!bCoordOnCommandLine) { char szLine[1024]; @@ -381,26 +402,62 @@ MAIN_START(argc, argv) if (fgets(szLine, sizeof(szLine) - 1, stdin) == nullptr) break; - char **papszTokens = CSLTokenizeString(szLine); - const int nCount = CSLCount(papszTokens); + const CPLStringList aosTokens(CSLTokenizeString(szLine)); + const int nCount = aosTokens.size(); + ++nLine; if (nCount < 2) { - CSLDestroy(papszTokens); + fprintf(stderr, "Not enough values at line %d\n", nLine); continue; } - dfX = CPLAtof(papszTokens[0]); - dfY = CPLAtof(papszTokens[1]); + dfX = CPLAtof(aosTokens[0]); + dfY = CPLAtof(aosTokens[1]); + dfZ = 0.0; + dfT = 0.0; + int iStartExtraContent = nCount; if (nCount >= 3) - dfZ = CPLAtof(papszTokens[2]); - else - dfZ = 0.0; - if (nCount == 4) - dfT = CPLAtof(papszTokens[3]); - else - dfT = 0.0; - CSLDestroy(papszTokens); + { + if (CPLGetValueType(aosTokens[2]) == CPL_VALUE_STRING) + { + iStartExtraContent = 2; + } + else + { + dfZ = CPLAtof(aosTokens[2]); + + if (nCount >= 4) + { + if (CPLGetValueType(aosTokens[3]) == CPL_VALUE_STRING) + { + iStartExtraContent = 3; + } + else + { + dfT = CPLAtof(aosTokens[3]); + iStartExtraContent = 4; + } + } + } + } + + if (!bIgnoreExtraInput) + { + for (int i = iStartExtraContent; i < nCount; ++i) + { + if (!osExtraContent.empty()) + osExtraContent += ' '; + osExtraContent += aosTokens[i]; + } + while (!osExtraContent.empty() && + isspace(static_cast(osExtraContent.back()))) + { + osExtraContent.pop_back(); + } + if (!osExtraContent.empty()) + osExtraContent = osFieldSep + osExtraContent; + } } if (dfT != dfLastT && nGCPCount == 0) { @@ -418,14 +475,29 @@ MAIN_START(argc, argv) } int bSuccess = TRUE; + const double dfXBefore = dfX; + const double dfYBefore = dfY; + const double dfZBefore = dfZ; if (pfnTransformer(hTransformArg, bInverse, 1, &dfX, &dfY, &dfZ, &bSuccess) && bSuccess) { + if (bEchoInput) + { + if (bOutputXY) + CPLprintf("%.15g%s%.15g%s", dfXBefore, osFieldSep.c_str(), + dfYBefore, osFieldSep.c_str()); + else + CPLprintf("%.15g%s%.15g%s%.15g%s", dfXBefore, + osFieldSep.c_str(), dfYBefore, osFieldSep.c_str(), + dfZBefore, osFieldSep.c_str()); + } if (bOutputXY) - CPLprintf("%.15g %.15g\n", dfX, dfY); + CPLprintf("%.15g%s%.15g%s\n", dfX, osFieldSep.c_str(), dfY, + osExtraContent.c_str()); else - CPLprintf("%.15g %.15g %.15g\n", dfX, dfY, dfZ); + CPLprintf("%.15g%s%.15g%s%.15g%s\n", dfX, osFieldSep.c_str(), + dfY, osFieldSep.c_str(), dfZ, osExtraContent.c_str()); } else { diff --git a/apps/gdalwarp_lib.cpp b/apps/gdalwarp_lib.cpp index 695c2bcd4b59..289929f981fc 100644 --- a/apps/gdalwarp_lib.cpp +++ b/apps/gdalwarp_lib.cpp @@ -1244,8 +1244,11 @@ static GDALDatasetH GDALWarpIndirect(const char *pszDest, GDALDriverH hDriver, CPLStringList aosCreateOptions(psOptions->aosCreateOptions); psOptions->aosCreateOptions.Clear(); - if (nSrcCount == 1 && !(EQUAL(psOptions->osFormat.c_str(), "COG") && - COGHasWarpingOptions(aosCreateOptions.List()))) + // Do not use a warped VRT input for COG output, because that would cause + // warping to be done both during overview computation and creation of + // full resolution image. Better materialize a temporary GTiff a bit later + // in that method. + if (nSrcCount == 1 && !EQUAL(psOptions->osFormat.c_str(), "COG")) { psOptions->osFormat = "VRT"; auto pfnProgress = psOptions->pfnProgress; diff --git a/apps/ogr2ogr_bin.cpp b/apps/ogr2ogr_bin.cpp index 18fc96b38e1b..bf63cb028964 100644 --- a/apps/ogr2ogr_bin.cpp +++ b/apps/ogr2ogr_bin.cpp @@ -119,6 +119,9 @@ static void Usage(bool bIsError, const char *pszAdditionalMsg = nullptr, " [-explodecollections] [-zfield ]\n" " [-gcp " "[]]... [[-order ]|[-tps]]\n" + " [-xyRes \"[ m|mm|deg]\"] [-zRes \"[ m|mm]\"] " + "[-mRes ]\n" + " [-unsetCoordPrecision]\n" " [-s_coord_epoch ] [-t_coord_epoch ] " "[-a_coord_epoch ]\n" " [-nomd] [-mo =]... [-noNativeData]\n"); diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index a3c404c53133..57f961f17c93 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -443,6 +443,24 @@ struct GDALVectorTranslateOptions /*! Wished offset w.r.t UTC of dateTime */ int nTZOffsetInSec = TZ_OFFSET_INVALID; + + /*! Geometry X,Y coordinate resolution */ + double dfXYRes = OGRGeomCoordinatePrecision::UNKNOWN; + + /*! Unit of dXYRes. empty string, "m", "mm" or "deg" */ + std::string osXYResUnit{}; + + /*! Geometry Z coordinate resolution */ + double dfZRes = OGRGeomCoordinatePrecision::UNKNOWN; + + /*! Unit of dfZRes. empty string, "m" or "mm" */ + std::string osZResUnit{}; + + /*! Geometry M coordinate resolution */ + double dfMRes = OGRGeomCoordinatePrecision::UNKNOWN; + + /*! Whether to unset geometry coordinate precision */ + bool bUnsetCoordPrecision = false; }; struct TargetLayerInfo @@ -3875,6 +3893,7 @@ bool SetupTargetLayer::CanUseWriteArrowBatch( !m_bUnsetFieldWidth && !m_bExplodeCollections && !m_pszZField && m_bExactFieldNameMatch && !m_bForceNullable && !m_bResolveDomains && !m_bUnsetDefault && psOptions->nFIDToFetch == OGRNullFID && + psOptions->dfXYRes == OGRGeomCoordinatePrecision::UNKNOWN && !psOptions->bMakeValid) { struct ArrowArrayStream streamSrc; @@ -4235,6 +4254,60 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, eGCreateLayerType = wkbNone; } + OGRGeomCoordinatePrecision oCoordPrec; + std::string osGeomFieldName; + bool bGeomFieldNullable = true; + + { + int iSrcGeomField = -1; + if (anRequestedGeomFields.empty() && + (nSrcGeomFieldCount == 1 || + (!m_poDstDS->TestCapability( + ODsCCreateGeomFieldAfterCreateLayer) && + nSrcGeomFieldCount > 1))) + { + iSrcGeomField = 0; + } + else if (anRequestedGeomFields.size() == 1) + { + iSrcGeomField = anRequestedGeomFields[0]; + } + + if (iSrcGeomField >= 0) + { + const auto poSrcGeomFieldDefn = + poSrcFDefn->GetGeomFieldDefn(iSrcGeomField); + if (!psOptions->bUnsetCoordPrecision) + { + oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision() + .ConvertToOtherSRS( + poSrcGeomFieldDefn->GetSpatialRef(), + poOutputSRS); + } + + bGeomFieldNullable = + CPL_TO_BOOL(poSrcGeomFieldDefn->IsNullable()); + + const char *pszGFldName = poSrcGeomFieldDefn->GetNameRef(); + if (pszGFldName != nullptr && !EQUAL(pszGFldName, "") && + poSrcFDefn->GetFieldIndex(pszGFldName) < 0) + { + osGeomFieldName = pszGFldName; + + // Use source geometry field name as much as possible + if (eGType != wkbNone && pszDestCreationOptions && + strstr(pszDestCreationOptions, "GEOMETRY_NAME") != + nullptr && + CSLFetchNameValue(m_papszLCO, "GEOMETRY_NAME") == + nullptr) + { + papszLCOTemp = CSLSetNameValue( + papszLCOTemp, "GEOMETRY_NAME", pszGFldName); + } + } + } + } + // If the source feature first geometry column is not nullable // and that GEOMETRY_NULLABLE creation option is available, use it // so as to be able to set the not null constraint (if the driver @@ -4247,41 +4320,107 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, CSLFetchNameValue(m_papszLCO, "GEOMETRY_NULLABLE") == nullptr && !m_bForceNullable) { + bGeomFieldNullable = false; papszLCOTemp = CSLSetNameValue(papszLCOTemp, "GEOMETRY_NULLABLE", "NO"); CPLDebug("GDALVectorTranslate", "Using GEOMETRY_NULLABLE=NO"); } - // Use source geometry field name as much as possible - if (eGType != wkbNone && pszDestCreationOptions && - strstr(pszDestCreationOptions, "GEOMETRY_NAME") != nullptr && - CSLFetchNameValue(m_papszLCO, "GEOMETRY_NAME") == nullptr) + if (psOptions->dfXYRes != OGRGeomCoordinatePrecision::UNKNOWN) { - int iSrcGeomField = -1; - if (anRequestedGeomFields.empty() && - (nSrcGeomFieldCount == 1 || - (!m_poDstDS->TestCapability( - ODsCCreateGeomFieldAfterCreateLayer) && - nSrcGeomFieldCount > 1))) + if (m_poDstDS->GetDriver()->GetMetadataItem( + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr && + !OGRGeometryFactory::haveGEOS()) { - iSrcGeomField = 0; + CPLError(CE_Warning, CPLE_AppDefined, + "-xyRes specified, but driver does not expose the " + "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, " + "and this build has no GEOS support"); } - else if (anRequestedGeomFields.size() == 1) + + oCoordPrec.dfXYResolution = psOptions->dfXYRes; + if (!psOptions->osXYResUnit.empty()) { - iSrcGeomField = anRequestedGeomFields[0]; + if (!poOutputSRS) + { + CSLDestroy(papszLCOTemp); + CPLError(CE_Failure, CPLE_AppDefined, + "Unit suffix for -xyRes cannot be used with an " + "unknown destination SRS"); + return nullptr; + } + + if (psOptions->osXYResUnit == "mm") + { + oCoordPrec.dfXYResolution *= 1e-3; + } + else if (psOptions->osXYResUnit == "deg") + { + double dfFactorDegToMeter = + poOutputSRS->GetSemiMajor(nullptr) * M_PI / 180; + oCoordPrec.dfXYResolution *= dfFactorDegToMeter; + } + else + { + // Checked at argument parsing time + CPLAssert(psOptions->osXYResUnit == "m"); + } + + OGRGeomCoordinatePrecision tmp; + tmp.SetFromMeter(poOutputSRS, oCoordPrec.dfXYResolution, 0, 0); + oCoordPrec.dfXYResolution = tmp.dfXYResolution; } + } - if (iSrcGeomField >= 0) + if (psOptions->dfZRes != OGRGeomCoordinatePrecision::UNKNOWN) + { + if (m_poDstDS->GetDriver()->GetMetadataItem( + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr) { - const char *pszGFldName = - poSrcFDefn->GetGeomFieldDefn(iSrcGeomField)->GetNameRef(); - if (pszGFldName != nullptr && !EQUAL(pszGFldName, "") && - poSrcFDefn->GetFieldIndex(pszGFldName) < 0) + CPLError(CE_Warning, CPLE_AppDefined, + "-zRes specified, but driver does not expose the " + "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability"); + } + + oCoordPrec.dfZResolution = psOptions->dfZRes; + if (!psOptions->osZResUnit.empty()) + { + if (!poOutputSRS) { - papszLCOTemp = CSLSetNameValue( - papszLCOTemp, "GEOMETRY_NAME", pszGFldName); + CSLDestroy(papszLCOTemp); + CPLError(CE_Failure, CPLE_AppDefined, + "Unit suffix for -zRes cannot be used with an " + "unknown destination SRS"); + return nullptr; } + + if (psOptions->osZResUnit == "mm") + { + oCoordPrec.dfZResolution *= 1e-3; + } + else + { + // Checked at argument parsing time + CPLAssert(psOptions->osZResUnit == "m"); + } + + OGRGeomCoordinatePrecision tmp; + tmp.SetFromMeter(poOutputSRS, 0, oCoordPrec.dfZResolution, 0); + oCoordPrec.dfZResolution = tmp.dfZResolution; + } + } + + if (psOptions->dfMRes != OGRGeomCoordinatePrecision::UNKNOWN) + { + if (m_poDstDS->GetDriver()->GetMetadataItem( + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr) + { + CPLError(CE_Warning, CPLE_AppDefined, + "-mRes specified, but driver does not expose the " + "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability"); } + + oCoordPrec.dfMResolution = psOptions->dfMRes; } // Force FID column as 64 bit if the source feature has a 64 bit FID, @@ -4420,21 +4559,16 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, } } - OGRSpatialReference *poOutputSRSClone = nullptr; - if (poOutputSRS != nullptr) - { - poOutputSRSClone = poOutputSRS->Clone(); - } - poDstLayer = m_poDstDS->CreateLayer( - pszNewLayerName, poOutputSRSClone, - static_cast(eGCreateLayerType), papszLCOTemp); + OGRGeomFieldDefn oGeomFieldDefn( + osGeomFieldName.c_str(), + static_cast(eGCreateLayerType)); + oGeomFieldDefn.SetSpatialRef(poOutputSRS); + oGeomFieldDefn.SetCoordinatePrecision(oCoordPrec); + oGeomFieldDefn.SetNullable(bGeomFieldNullable); + poDstLayer = m_poDstDS->CreateLayer(pszNewLayerName, &oGeomFieldDefn, + papszLCOTemp); CSLDestroy(papszLCOTemp); - if (poOutputSRSClone != nullptr) - { - poOutputSRSClone->Release(); - } - if (poDstLayer == nullptr) { return nullptr; @@ -4504,7 +4638,7 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, poSrcFDefn->GetGeomFieldDefn(iSrcGeomField)); if (m_poOutputSRS != nullptr) { - poOutputSRSClone = m_poOutputSRS->Clone(); + auto poOutputSRSClone = m_poOutputSRS->Clone(); oGFldDefn.SetSpatialRef(poOutputSRSClone); poOutputSRSClone->Release(); } @@ -5658,6 +5792,8 @@ bool LayerTranslator::Translate( int nFeaturesInTransaction = 0; GIntBig nCount = 0; /* written + failed */ GIntBig nFeaturesWritten = 0; + bool bRunSetPrecisionEvaluated = false; + bool bRunSetPrecision = false; bool bRet = true; CPLErrorReset(); @@ -6247,6 +6383,34 @@ bool LayerTranslator::Translate( poDstGeometry = poClipped.release(); } + if (psOptions->dfXYRes != + OGRGeomCoordinatePrecision::UNKNOWN && + OGRGeometryFactory::haveGEOS() && + !poDstGeometry->hasCurveGeometry()) + { + // OGR_APPLY_GEOM_SET_PRECISION default value for + // OGRLayer::CreateFeature() purposes, but here in the + // ogr2ogr -xyRes context, we force calling SetPrecision(), + // unless the user explicitly asks not to do it by + // setting the config option to NO. + if (!bRunSetPrecisionEvaluated) + { + bRunSetPrecisionEvaluated = true; + bRunSetPrecision = CPLTestBool(CPLGetConfigOption( + "OGR_APPLY_GEOM_SET_PRECISION", "YES")); + } + if (bRunSetPrecision) + { + OGRGeometry *poRoundedGeom = + poDstGeometry->SetPrecision(psOptions->dfXYRes, + /* nFlags = */ 0); + delete poDstGeometry; + poDstGeometry = poRoundedGeom; + if (!poDstGeometry) + goto end_loop; + } + } + if (m_bMakeValid) { const bool bIsGeomCollection = @@ -7297,6 +7461,76 @@ GDALVectorTranslateOptions *GDALVectorTranslateOptionsNew( return nullptr; } } + else if (EQUAL(papszArgv[i], "-xyRes")) + { + CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + const char *pszVal = papszArgv[++i]; + + char *endptr = nullptr; + psOptions->dfXYRes = CPLStrtod(pszVal, &endptr); + if (!endptr) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Invalid value for -xyRes. Must be of the form " + "{numeric_value}[ ]?[m|mm|deg]?"); + return nullptr; + } + if (*endptr == ' ') + ++endptr; + if (*endptr != 0 && strcmp(endptr, "m") != 0 && + strcmp(endptr, "mm") != 0 && strcmp(endptr, "deg") != 0) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Invalid value for -xyRes. Must be of the form " + "{numeric_value}[ ]?[m|mm|deg]?"); + return nullptr; + } + psOptions->osXYResUnit = endptr; + } + else if (EQUAL(papszArgv[i], "-zRes")) + { + CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + const char *pszVal = papszArgv[++i]; + + char *endptr = nullptr; + psOptions->dfZRes = CPLStrtod(pszVal, &endptr); + if (!endptr) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Invalid value for -zRes. Must be of the form " + "{numeric_value}[ ]?[m|mm]?"); + return nullptr; + } + if (*endptr == ' ') + ++endptr; + if (*endptr != 0 && strcmp(endptr, "m") != 0 && + strcmp(endptr, "mm") != 0 && strcmp(endptr, "deg") != 0) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Invalid value for -zRes. Must be of the form " + "{numeric_value}[ ]?[m|mm]?"); + return nullptr; + } + psOptions->osZResUnit = endptr; + } + else if (EQUAL(papszArgv[i], "-mRes")) + { + CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + const char *pszVal = papszArgv[++i]; + + char *endptr = nullptr; + psOptions->dfMRes = CPLStrtod(pszVal, &endptr); + if (!endptr || endptr != pszVal + strlen(pszVal)) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Invalid value for -mRes"); + return nullptr; + } + } + else if (EQUAL(papszArgv[i], "-unsetCoordPrecision")) + { + psOptions->bUnsetCoordPrecision = true; + } else if (papszArgv[i][0] == '-') { CPLError(CE_Failure, CPLE_NotSupported, "Unknown option name '%s'", diff --git a/apps/ogrinfo_bin.cpp b/apps/ogrinfo_bin.cpp index 214ab3fb0b38..9eb09d166483 100644 --- a/apps/ogrinfo_bin.cpp +++ b/apps/ogrinfo_bin.cpp @@ -138,6 +138,10 @@ MAIN_START(argc, argv) else if (psOptionsForBinary->osSQLStatement.empty()) { nFlags |= GDAL_OF_READONLY; + // GDALIdentifyDriverEx() might emit an error message, e.g. + // when opening "/vsizip/foo.zip/" and the zip has more than one + // file. Cf https://github.com/OSGeo/gdal/issues/9459 + CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); if (GDALIdentifyDriverEx( psOptionsForBinary->osFilename.c_str(), GDAL_OF_VECTOR, psOptionsForBinary->aosAllowInputDrivers.List(), nullptr)) diff --git a/apps/ogrinfo_lib.cpp b/apps/ogrinfo_lib.cpp index ed1785c22636..1450cb307945 100644 --- a/apps/ogrinfo_lib.cpp +++ b/apps/ogrinfo_lib.cpp @@ -888,7 +888,7 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, oLayer.Add("geometryFields", oGeometryFields); for (int iGeom = 0; iGeom < nGeomFieldCount; iGeom++) { - OGRGeomFieldDefn *poGFldDefn = + const OGRGeomFieldDefn *poGFldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(iGeom); if (bJson) { @@ -1034,6 +1034,72 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, oGeometryField.Add("supportedSRSList", oSupportedSRSList); } + + const auto &oCoordPrec = + poGFldDefn->GetCoordinatePrecision(); + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + oGeometryField.Add("xyCoordinateResolution", + oCoordPrec.dfXYResolution); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + oGeometryField.Add("zCoordinateResolution", + oCoordPrec.dfZResolution); + } + if (oCoordPrec.dfMResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + oGeometryField.Add("mCoordinateResolution", + oCoordPrec.dfMResolution); + } + + // For example set by OpenFileGDB driver + if (!oCoordPrec.oFormatSpecificOptions.empty()) + { + CPLJSONObject oFormatSpecificOptions; + for (const auto &formatOptionsPair : + oCoordPrec.oFormatSpecificOptions) + { + CPLJSONObject oThisFormatSpecificOptions; + for (int i = 0; i < formatOptionsPair.second.size(); + ++i) + { + char *pszKey = nullptr; + const char *pszValue = CPLParseNameValue( + formatOptionsPair.second[i], &pszKey); + if (pszKey && pszValue) + { + const auto eValueType = + CPLGetValueType(pszValue); + if (eValueType == CPL_VALUE_INTEGER) + { + oThisFormatSpecificOptions.Add( + pszKey, CPLAtoGIntBig(pszValue)); + } + else if (eValueType == CPL_VALUE_REAL) + { + oThisFormatSpecificOptions.Add( + pszKey, CPLAtof(pszValue)); + } + else + { + oThisFormatSpecificOptions.Add( + pszKey, pszValue); + } + } + CPLFree(pszKey); + } + oFormatSpecificOptions.Add( + formatOptionsPair.first, + oThisFormatSpecificOptions); + } + oGeometryField.Add( + "coordinatePrecisionFormatSpecificOptions", + oFormatSpecificOptions); + } } else { @@ -1494,6 +1560,36 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, } } + const auto GetGeoJSONOptions = [poLayer](int iGeomField) + { + CPLStringList aosGeoJSONOptions; + const auto &oCoordPrec = + poLayer->GetLayerDefn() + ->GetGeomFieldDefn(iGeomField) + ->GetCoordinatePrecision(); + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + aosGeoJSONOptions.SetNameValue( + "XY_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision:: + ResolutionToPrecision( + oCoordPrec.dfXYResolution))); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + aosGeoJSONOptions.SetNameValue( + "Z_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision:: + ResolutionToPrecision( + oCoordPrec.dfZResolution))); + } + return aosGeoJSONOptions; + }; + if (nGeomFields == 0) oFeature.SetNull("geometry"); else @@ -1503,7 +1599,8 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, char *pszSerialized = wkbFlatten(poGeom->getGeometryType()) <= wkbGeometryCollection - ? poGeom->exportToJson() + ? poGeom->exportToJson( + GetGeoJSONOptions(0).List()) : nullptr; if (pszSerialized) { @@ -1535,7 +1632,8 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, char *pszSerialized = wkbFlatten(poGeom->getGeometryType()) <= wkbGeometryCollection - ? poGeom->exportToJson() + ? poGeom->exportToJson( + GetGeoJSONOptions(i).List()) : nullptr; if (pszSerialized) { diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 5828da330ef5..5d7a861ebae1 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) { @@ -3597,4 +3784,141 @@ TEST_F(test_gdal, drop_cache) } } +// Test gdal::gcp class +TEST_F(test_gdal, gdal_gcp_class) +{ + { + gdal::GCP gcp; + EXPECT_STREQ(gcp.Id(), ""); + EXPECT_STREQ(gcp.Info(), ""); + EXPECT_EQ(gcp.Pixel(), 0.0); + EXPECT_EQ(gcp.Line(), 0.0); + EXPECT_EQ(gcp.X(), 0.0); + EXPECT_EQ(gcp.Y(), 0.0); + EXPECT_EQ(gcp.Z(), 0.0); + } + { + gdal::GCP gcp("id", "info", 1.5, 2.5, 3.5, 4.5, 5.5); + EXPECT_STREQ(gcp.Id(), "id"); + EXPECT_STREQ(gcp.Info(), "info"); + EXPECT_EQ(gcp.Pixel(), 1.5); + EXPECT_EQ(gcp.Line(), 2.5); + EXPECT_EQ(gcp.X(), 3.5); + EXPECT_EQ(gcp.Y(), 4.5); + EXPECT_EQ(gcp.Z(), 5.5); + + gcp.SetId("id2"); + gcp.SetInfo("info2"); + gcp.Pixel() = -1.5; + gcp.Line() = -2.5; + gcp.X() = -3.5; + gcp.Y() = -4.5; + gcp.Z() = -5.5; + EXPECT_STREQ(gcp.Id(), "id2"); + EXPECT_STREQ(gcp.Info(), "info2"); + EXPECT_EQ(gcp.Pixel(), -1.5); + EXPECT_EQ(gcp.Line(), -2.5); + EXPECT_EQ(gcp.X(), -3.5); + EXPECT_EQ(gcp.Y(), -4.5); + EXPECT_EQ(gcp.Z(), -5.5); + + { + gdal::GCP gcp_copy(gcp); + EXPECT_STREQ(gcp_copy.Id(), "id2"); + EXPECT_STREQ(gcp_copy.Info(), "info2"); + EXPECT_EQ(gcp_copy.Pixel(), -1.5); + EXPECT_EQ(gcp_copy.Line(), -2.5); + EXPECT_EQ(gcp_copy.X(), -3.5); + EXPECT_EQ(gcp_copy.Y(), -4.5); + EXPECT_EQ(gcp_copy.Z(), -5.5); + } + + { + gdal::GCP gcp_copy; + gcp_copy = gcp; + EXPECT_STREQ(gcp_copy.Id(), "id2"); + EXPECT_STREQ(gcp_copy.Info(), "info2"); + EXPECT_EQ(gcp_copy.Pixel(), -1.5); + EXPECT_EQ(gcp_copy.Line(), -2.5); + EXPECT_EQ(gcp_copy.X(), -3.5); + EXPECT_EQ(gcp_copy.Y(), -4.5); + EXPECT_EQ(gcp_copy.Z(), -5.5); + } + + { + gdal::GCP gcp_copy(gcp); + gdal::GCP gcp_from_moved(std::move(gcp_copy)); + EXPECT_STREQ(gcp_from_moved.Id(), "id2"); + EXPECT_STREQ(gcp_from_moved.Info(), "info2"); + EXPECT_EQ(gcp_from_moved.Pixel(), -1.5); + EXPECT_EQ(gcp_from_moved.Line(), -2.5); + EXPECT_EQ(gcp_from_moved.X(), -3.5); + EXPECT_EQ(gcp_from_moved.Y(), -4.5); + EXPECT_EQ(gcp_from_moved.Z(), -5.5); + } + + { + gdal::GCP gcp_copy(gcp); + gdal::GCP gcp_from_moved; + gcp_from_moved = std::move(gcp_copy); + EXPECT_STREQ(gcp_from_moved.Id(), "id2"); + EXPECT_STREQ(gcp_from_moved.Info(), "info2"); + EXPECT_EQ(gcp_from_moved.Pixel(), -1.5); + EXPECT_EQ(gcp_from_moved.Line(), -2.5); + EXPECT_EQ(gcp_from_moved.X(), -3.5); + EXPECT_EQ(gcp_from_moved.Y(), -4.5); + EXPECT_EQ(gcp_from_moved.Z(), -5.5); + } + + { + const GDAL_GCP *c_gcp = gcp.c_ptr(); + EXPECT_STREQ(c_gcp->pszId, "id2"); + EXPECT_STREQ(c_gcp->pszInfo, "info2"); + EXPECT_EQ(c_gcp->dfGCPPixel, -1.5); + EXPECT_EQ(c_gcp->dfGCPLine, -2.5); + EXPECT_EQ(c_gcp->dfGCPX, -3.5); + EXPECT_EQ(c_gcp->dfGCPY, -4.5); + EXPECT_EQ(c_gcp->dfGCPZ, -5.5); + + const gdal::GCP gcp_from_c(*c_gcp); + EXPECT_STREQ(gcp_from_c.Id(), "id2"); + EXPECT_STREQ(gcp_from_c.Info(), "info2"); + EXPECT_EQ(gcp_from_c.Pixel(), -1.5); + EXPECT_EQ(gcp_from_c.Line(), -2.5); + EXPECT_EQ(gcp_from_c.X(), -3.5); + EXPECT_EQ(gcp_from_c.Y(), -4.5); + EXPECT_EQ(gcp_from_c.Z(), -5.5); + } + } + + { + const std::vector gcps{ + gdal::GCP{nullptr, nullptr, 0, 0, 0, 0, 0}, + gdal::GCP{"id", "info", 1.5, 2.5, 3.5, 4.5, 5.5}}; + + const GDAL_GCP *c_gcps = gdal::GCP::c_ptr(gcps); + EXPECT_STREQ(c_gcps[1].pszId, "id"); + EXPECT_STREQ(c_gcps[1].pszInfo, "info"); + EXPECT_EQ(c_gcps[1].dfGCPPixel, 1.5); + EXPECT_EQ(c_gcps[1].dfGCPLine, 2.5); + EXPECT_EQ(c_gcps[1].dfGCPX, 3.5); + EXPECT_EQ(c_gcps[1].dfGCPY, 4.5); + EXPECT_EQ(c_gcps[1].dfGCPZ, 5.5); + + const auto gcps_from_c = + gdal::GCP::fromC(c_gcps, static_cast(gcps.size())); + ASSERT_EQ(gcps_from_c.size(), gcps.size()); + for (size_t i = 0; i < gcps.size(); ++i) + { + EXPECT_STREQ(gcps_from_c[i].Id(), gcps[i].Id()); + EXPECT_STREQ(gcps_from_c[i].Info(), gcps[i].Info()); + EXPECT_EQ(gcps_from_c[i].Pixel(), gcps[i].Pixel()); + EXPECT_EQ(gcps_from_c[i].Line(), gcps[i].Line()); + EXPECT_EQ(gcps_from_c[i].X(), gcps[i].X()); + EXPECT_EQ(gcps_from_c[i].Y(), gcps[i].Y()); + EXPECT_EQ(gcps_from_c[i].Z(), gcps[i].Z()); + } + } +} + } // namespace diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index 0d3044633a28..6fe859df02f8 100644 --- a/autotest/cpp/test_ogr.cpp +++ b/autotest/cpp/test_ogr.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #ifdef HAVE_SQLITE3 #include @@ -1406,7 +1407,7 @@ TEST_F(test_ogr, DatasetFeature_and_LayerFeature_iterators) ASSERT_EQ(nCountLayers, 0); poDS->CreateLayer("foo"); - poDS->CreateLayer("bar"); + poDS->CreateLayer("bar", nullptr); for (auto poLayer : poDS->GetLayers()) { if (nCountLayers == 0) @@ -3362,4 +3363,402 @@ TEST_F(test_ogr, OGRFeatureDefn_sealing) } } +// Test wkbExportOptions +TEST_F(test_ogr, wkbExportOptions_default) +{ + OGRwkbExportOptions *psOptions = OGRwkbExportOptionsCreate(); + ASSERT_TRUE(psOptions != nullptr); + OGRPoint p(1.23456789012345678, 2.23456789012345678, 3); + std::vector abyWKB(p.WkbSize()); + OGR_G_ExportToWkbEx(OGRGeometry::ToHandle(&p), &abyWKB[0], psOptions); + OGRwkbExportOptionsDestroy(psOptions); + + std::vector abyRegularWKB(p.WkbSize()); + OGR_G_ExportToWkb(OGRGeometry::ToHandle(&p), wkbNDR, &abyRegularWKB[0]); + + EXPECT_TRUE(abyWKB == abyRegularWKB); +} + +// Test wkbExportOptions +TEST_F(test_ogr, wkbExportOptions) +{ + OGRwkbExportOptions *psOptions = OGRwkbExportOptionsCreate(); + ASSERT_TRUE(psOptions != nullptr); + OGRwkbExportOptionsSetByteOrder(psOptions, wkbXDR); + OGRwkbExportOptionsSetVariant(psOptions, wkbVariantIso); + + auto hPrec = OGRGeomCoordinatePrecisionCreate(); + OGRGeomCoordinatePrecisionSet(hPrec, 1e-1, 1e-2, 1e-4); + OGRwkbExportOptionsSetPrecision(psOptions, hPrec); + OGRGeomCoordinatePrecisionDestroy(hPrec); + + OGRPoint p(1.23456789012345678, -1.23456789012345678, 1.23456789012345678, + 1.23456789012345678); + std::vector abyWKB(p.WkbSize()); + OGR_G_ExportToWkbEx(OGRGeometry::ToHandle(&p), &abyWKB[0], psOptions); + OGRwkbExportOptionsDestroy(psOptions); + + const std::vector expectedWKB{ + 0x00, 0x00, 0x00, 0x0B, 0xB9, 0x3F, 0xF3, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xBF, 0xF3, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0xF3, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, + 0xF3, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00}; + EXPECT_TRUE(abyWKB == expectedWKB); + + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_NEAR(poGeom->toPoint()->getX(), 1.2, 1e-1); + EXPECT_NEAR(poGeom->toPoint()->getY(), -1.2, 1e-1); + EXPECT_NEAR(poGeom->toPoint()->getZ(), 1.23, 1e-2); + EXPECT_NEAR(poGeom->toPoint()->getM(), 1.2346, 1e-4); + delete poGeom; +} + +// Test OGRGeometry::roundCoordinatesIEEE754() +TEST_F(test_ogr, roundCoordinatesIEEE754) +{ + OGRLineString oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234, -1.2345678901234, 0.012345); + oLS.addPoint(-1.2345678901234, 1.2345678901234, 1.2345678901234, -0.012345); + oLS.addPoint(std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN()); + OGRGeomCoordinateBinaryPrecision sBinaryPrecision; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sPrecision.dfZResolution = 1e-3; + sPrecision.dfMResolution = 1e-5; + sBinaryPrecision.SetFrom(sPrecision); + OGRLineString oLSOri(oLS); + oLS.roundCoordinatesIEEE754(sBinaryPrecision); + EXPECT_NE(oLS.getX(0), oLSOri.getX(0)); + EXPECT_NE(oLS.getY(0), oLSOri.getY(0)); + EXPECT_NE(oLS.getZ(0), oLSOri.getZ(0)); + EXPECT_NE(oLS.getM(0), oLSOri.getM(0)); + EXPECT_NEAR(oLS.getX(0), oLSOri.getX(0), sPrecision.dfXYResolution); + EXPECT_NEAR(oLS.getY(0), oLSOri.getY(0), sPrecision.dfXYResolution); + EXPECT_NEAR(oLS.getZ(0), oLSOri.getZ(0), sPrecision.dfZResolution); + EXPECT_NEAR(oLS.getM(0), oLSOri.getM(0), sPrecision.dfMResolution); + EXPECT_NEAR(oLS.getX(1), oLSOri.getX(1), sPrecision.dfXYResolution); + EXPECT_NEAR(oLS.getY(1), oLSOri.getY(1), sPrecision.dfXYResolution); + EXPECT_NEAR(oLS.getZ(1), oLSOri.getZ(1), sPrecision.dfZResolution); + EXPECT_NEAR(oLS.getM(1), oLSOri.getM(1), sPrecision.dfMResolution); + EXPECT_EQ(oLS.getX(2), std::numeric_limits::infinity()); + EXPECT_TRUE(std::isnan(oLS.getY(2))); +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_linestring_2d_xy_precision) +{ + OGRLineString oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234); + oLS.addPoint(-1.2345678901234, 1.2345678901234); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oLS.WkbSize()); + oLS.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toLineString()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toLineString()->getY(0), oLS.getY(0)); + EXPECT_NEAR(poGeom->toLineString()->getX(0), oLS.getX(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(0), oLS.getY(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getX(1), oLS.getX(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(1), oLS.getY(1), + sPrecision.dfXYResolution); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_linestring_3d_discard_lsb_bits) +{ + OGRLineString oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234, -1.2345678901234); + oLS.addPoint(-1.2345678901234, 1.2345678901234, 1.2345678901234); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sPrecision.dfZResolution = 1e-3; + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oLS.WkbSize()); + oLS.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toLineString()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toLineString()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toLineString()->getZ(0), oLS.getZ(0)); + EXPECT_NEAR(poGeom->toLineString()->getX(0), oLS.getX(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(0), oLS.getY(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getZ(0), oLS.getZ(0), + sPrecision.dfZResolution); + EXPECT_NEAR(poGeom->toLineString()->getX(1), oLS.getX(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(1), oLS.getY(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getZ(1), oLS.getZ(1), + sPrecision.dfZResolution); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_linestring_xym_discard_lsb_bits) +{ + OGRLineString oLS; + oLS.addPointM(1.2345678901234, -1.2345678901234, -1.2345678901234); + oLS.addPointM(-1.2345678901234, 1.2345678901234, 1.2345678901234); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sPrecision.dfMResolution = 1e-3; + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oLS.WkbSize()); + oLS.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toLineString()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toLineString()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toLineString()->getM(0), oLS.getM(0)); + EXPECT_NEAR(poGeom->toLineString()->getX(0), oLS.getX(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(0), oLS.getY(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getM(0), oLS.getM(0), + sPrecision.dfMResolution); + EXPECT_NEAR(poGeom->toLineString()->getX(1), oLS.getX(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(1), oLS.getY(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getM(1), oLS.getM(1), + sPrecision.dfMResolution); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_linestring_xyzm_discard_lsb_bits) +{ + OGRLineString oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234, -1.2345678901234, 0.012345); + oLS.addPoint(-1.2345678901234, 1.2345678901234, 1.2345678901234, 0.012345); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sPrecision.dfZResolution = 1e-3; + sPrecision.dfMResolution = 1e-5; + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oLS.WkbSize()); + oLS.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toLineString()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toLineString()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toLineString()->getZ(0), oLS.getZ(0)); + EXPECT_NE(poGeom->toLineString()->getM(0), oLS.getM(0)); + EXPECT_NEAR(poGeom->toLineString()->getX(0), oLS.getX(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(0), oLS.getY(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getZ(0), oLS.getZ(0), + sPrecision.dfZResolution); + EXPECT_NEAR(poGeom->toLineString()->getM(0), oLS.getM(0), + sPrecision.dfMResolution); + EXPECT_NEAR(poGeom->toLineString()->getX(1), oLS.getX(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getY(1), oLS.getY(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toLineString()->getZ(1), oLS.getZ(1), + sPrecision.dfZResolution); + EXPECT_NEAR(poGeom->toLineString()->getM(1), oLS.getM(1), + sPrecision.dfMResolution); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_polygon_2d_xy_precision) +{ + OGRLinearRing oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234); + oLS.addPoint(-1.2345678901234, -1.2345678901234); + oLS.addPoint(-2.2345678901234, 1.2345678901234); + oLS.addPoint(1.2345678901234, -1.2345678901234); + OGRPolygon oPoly; + oPoly.addRing(&oLS); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = 1e-10; + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oPoly.WkbSize()); + oPoly.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0)); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(1), oLS.getX(1), + sPrecision.dfXYResolution); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(1), oLS.getY(1), + sPrecision.dfXYResolution); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_polygon_3d_discard_lsb_bits) +{ + OGRLinearRing oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234, 1.2345678901234); + oLS.addPoint(-1.2345678901234, -1.2345678901234, -1.2345678901234); + oLS.addPoint(-2.2345678901234, 1.2345678901234, -1.2345678901234); + oLS.addPoint(1.2345678901234, -1.2345678901234, 1.2345678901234); + OGRPolygon oPoly; + oPoly.addRing(&oLS); + OGRSpatialReference oSRS; + oSRS.importFromEPSG(4326); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.SetFromMeter(&oSRS, 1e-3, 1e-3, 0); + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oPoly.WkbSize()); + oPoly.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getZ(0), oLS.getZ(0)); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getZ(0), oLS.getZ(0), + 1e-3); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(1), oLS.getX(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(1), oLS.getY(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getZ(1), oLS.getZ(1), + 1e-3); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_polygon_xym_discard_lsb_bits) +{ + OGRLinearRing oLS; + oLS.addPointM(1.2345678901234, -1.2345678901234, 1.2345678901234); + oLS.addPointM(-1.2345678901234, -1.2345678901234, -1.2345678901234); + oLS.addPointM(-2.2345678901234, 1.2345678901234, -1.2345678901234); + oLS.addPointM(1.2345678901234, -1.2345678901234, 1.2345678901234); + OGRPolygon oPoly; + oPoly.addRing(&oLS); + OGRSpatialReference oSRS; + oSRS.importFromEPSG(4326); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.SetFromMeter(&oSRS, 1e-3, 0, 1e-3); + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oPoly.WkbSize()); + oPoly.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getM(0), oLS.getM(0)); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getM(0), oLS.getM(0), + 1e-3); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(1), oLS.getX(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(1), oLS.getY(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getM(1), oLS.getM(1), + 1e-3); + delete poGeom; +} + +// Test discarding of bits in WKB export +TEST_F(test_ogr, wkb_polygon_xyzm_discard_lsb_bits) +{ + OGRLinearRing oLS; + oLS.addPoint(1.2345678901234, -1.2345678901234, 1.2345678901234, 0.012345); + oLS.addPoint(-1.2345678901234, -1.2345678901234, -1.2345678901234, 12345); + oLS.addPoint(-2.2345678901234, 1.2345678901234, -1.2345678901234, 0.012345); + oLS.addPoint(1.2345678901234, -1.2345678901234, 1.2345678901234, 0.012345); + OGRPolygon oPoly; + oPoly.addRing(&oLS); + OGRSpatialReference oSRS; + oSRS.importFromEPSG(4326); + OGRwkbExportOptions sOptions; + OGRGeomCoordinatePrecision sPrecision; + sPrecision.SetFromMeter(&oSRS, 1e-3, 1e-3, 1e-4); + sOptions.sPrecision.SetFrom(sPrecision); + std::vector abyWKB(oPoly.WkbSize()); + oPoly.exportToWkb(&abyWKB[0], &sOptions); + for (int i = 0; i < oLS.getDimension() * oLS.getNumPoints(); ++i) + { + EXPECT_EQ(abyWKB[5 + 4 + 4 + 0 + 8 * i], 0); + } + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkb(abyWKB.data(), nullptr, &poGeom); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getZ(0), oLS.getZ(0)); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing()->getM(0), oLS.getM(0)); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(0), oLS.getX(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(0), oLS.getY(0), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getZ(0), oLS.getZ(0), + 1e-3); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getM(0), oLS.getM(0), + 1e-4); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getX(1), oLS.getX(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getY(1), oLS.getY(1), + 8.9e-9); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getZ(1), oLS.getZ(1), + 1e-3); + EXPECT_NEAR(poGeom->toPolygon()->getExteriorRing()->getM(1), oLS.getM(1), + 1e-4); + delete poGeom; +} + } // namespace 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/tiff_write.py b/autotest/gcore/tiff_write.py index 9a69d284a80c..cff5ddc4aecf 100755 --- a/autotest/gcore/tiff_write.py +++ b/autotest/gcore/tiff_write.py @@ -8051,6 +8051,9 @@ def test_tiff_write_166(): ) s = gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml") if s is not None: + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + # Failure related to the change of https://github.com/OSGeo/gdal/pull/9040 # But the above code *does* not go through the modified code path... # Not reproduced locally on a minimum Windows build diff --git a/autotest/gdrivers/vrtwarp.py b/autotest/gdrivers/vrtwarp.py index 62deeafe19b0..3f37e1d0a6c1 100755 --- a/autotest/gdrivers/vrtwarp.py +++ b/autotest/gdrivers/vrtwarp.py @@ -670,3 +670,65 @@ def test_vrtwarp_float32_max_nodata(nodata): finally: gdal.Unlink(in_filename) gdal.Unlink(out_filename) + + +############################################################################### +# Test VRTWarpedDataset::IRasterIO() code path + + +def test_vrtwarp_irasterio_optim_single_band(): + + src_ds = gdal.Translate("", "data/byte.tif", format="MEM", width=1000) + warped_vrt_ds = gdal.Warp("", src_ds, format="VRT") + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster() + + assert warped_vrt_ds.ReadRaster() == expected_data + assert warped_vrt_ds.GetRasterBand(1).ReadRaster() == expected_data + + +############################################################################### +# Test VRTWarpedDataset::IRasterIO() code path + + +def test_vrtwarp_irasterio_optim_three_band(): + + src_ds = gdal.Translate("", "data/rgbsmall.tif", format="MEM", width=1000) + warped_vrt_ds = gdal.Warp("", src_ds, format="VRT") + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster() + assert warped_vrt_ds.ReadRaster() == expected_data + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster(band_list=[3, 2, 1]) + assert warped_vrt_ds.ReadRaster(band_list=[3, 2, 1]) == expected_data + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster(band_list=[1, 2, 1]) + assert warped_vrt_ds.ReadRaster(band_list=[1, 2, 1]) == expected_data + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster(buf_type=gdal.GDT_UInt16) + assert warped_vrt_ds.ReadRaster(buf_type=gdal.GDT_UInt16) == expected_data + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster(buf_xsize=20, buf_ysize=20) + assert warped_vrt_ds.ReadRaster(buf_xsize=20, buf_ysize=20) == expected_data + + +############################################################################### +# Test VRTWarpedDataset::IRasterIO() code path + + +def test_vrtwarp_irasterio_optim_window_splitting(): + + src_ds = gdal.Translate( + "", "data/rgbsmall.tif", format="MEM", width=1000, height=2000 + ) + warped_vrt_ds = gdal.Warp("", src_ds, format="VRT", warpMemoryLimit=1) # 1 MB + + with gdaltest.config_option("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "NO"): + expected_data = warped_vrt_ds.ReadRaster() + assert warped_vrt_ds.ReadRaster() == expected_data diff --git a/autotest/lsan_suppressions.txt b/autotest/lsan_suppressions.txt index 9b7611d69c35..9c3329c37dbd 100644 --- a/autotest/lsan_suppressions.txt +++ b/autotest/lsan_suppressions.txt @@ -11,3 +11,4 @@ leak:/usr/lib/python3/dist-packages/numpy/core/_multiarray_umath.cpython-38-x86_ leak:PyDataMem_NEW leak:PyArray_NewFromDescr_int leak:/usr/bin/python3.8 +leak:/usr/bin/python3.10 diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 9008bec734ba..60ee4122d6c6 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -3016,6 +3016,79 @@ def test_ogr_csv_read_header_with_line_break(): ] +############################################################################### +# Test geometry coordinate precision support + + +@pytest.mark.parametrize("geometry_format", ["AS_WKT", "AS_XYZ"]) +def test_ogr_csv_geom_coord_precision(tmp_vsimem, geometry_format): + + filename = str(tmp_vsimem / "test.csv") + ds = gdal.GetDriverByName("CSV").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 1e-2) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn( + "test", geom_fld, ["GEOMETRY=" + geometry_format] + ) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321 -1.23456789)") + ) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + if geometry_format == "AS_WKT": + assert b"POINT ZM (1.23457 2.34568 9.877 -1.23)" in data + else: + assert b"1.23457,2.34568,9.877" in data + + +############################################################################### +# Test geometry coordinate precision support + + +@pytest.mark.require_geos +def test_ogr_csv_geom_coord_precision_OGR_APPLY_GEOM_SET_PRECISION(tmp_vsimem): + + filename = str(tmp_vsimem / "test.csv") + ds = gdal.GetDriverByName("CSV").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(0.5, 0, 0) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld, ["GEOMETRY=AS_WKT"]) + f = ogr.Feature(lyr.GetLayerDefn()) + # We create an initial polygon, which is valid if the precision is infinite, + # but when rounding coordinates to 0.5 resolution, it would become invalid. + f.SetGeometry( + ogr.CreateGeometryFromWkt("POLYGON((0 0,0.5 0.4,1 0,1 1,0.5 0.6,0 1,0 0))") + ) + with gdaltest.config_option("OGR_APPLY_GEOM_SET_PRECISION", "YES"): + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + # We just check that GEOS did its job by turning the polygon into a + # multipolygon made of 2 parts. To avoid being dependent on GEOS version, + # we just check for the MULTIPOLYGON keyword. + assert b"MULTIPOLYGON" in data + + ############################################################################### diff --git a/autotest/ogr/ogr_fgdb.py b/autotest/ogr/ogr_fgdb.py index f0c483500dcd..739ca11fe5ad 100755 --- a/autotest/ogr/ogr_fgdb.py +++ b/autotest/ogr/ogr_fgdb.py @@ -3029,3 +3029,104 @@ def test_ogr_filegdb_read_cdf(): ds = ogr.Open("data/filegdb/with_cdf.gdb") lyr = ds.GetLayer(0) assert lyr.GetFeatureCount() == 3 + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_filegdb_write_geom_coord_precision(tmp_path): + + filename = str(tmp_path / "test.gdb") + ds = gdal.GetDriverByName("FileGDB").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbPointZM) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 1e-2) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert prec.GetFormats() == ["FileGeodatabase"] + opts = prec.GetFormatSpecificOptions("FileGeodatabase") + for key in opts: + opts[key] = float(opts[key]) + assert opts == { + "MOrigin": -100000.0, + "MScale": 100.0, + "MTolerance": 0.001, + "XOrigin": -2147483647.0, + "XYScale": 100000.0, + "XYTolerance": 1e-06, + "YOrigin": -2147483647.0, + "ZOrigin": -100000.0, + "ZScale": 1000.0, + "ZTolerance": 0.0001, + } + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321 -9.87654321)") + ) + lyr.CreateFeature(f) + ds.Close() + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert prec.GetFormats() == ["FileGeodatabase"] + opts = prec.GetFormatSpecificOptions("FileGeodatabase") + for key in opts: + try: + opts[key] = float(opts[key]) + except ValueError: + pass + assert opts == { + "MOrigin": -100000.0, + "MScale": 100.0, + "MTolerance": 0.001, + "XOrigin": -2147483647.0, + "XYScale": 100000.0, + "XYTolerance": 1e-06, + "YOrigin": -2147483647.0, + "ZOrigin": -100000.0, + "ZScale": 1000.0, + "ZTolerance": 0.0001, + "HighPrecision": "true", + } + f = lyr.GetNextFeature() + g = f.GetGeometryRef() + assert g.GetX(0) == pytest.approx(1.23456789, abs=1e-5) + assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) + assert g.GetY(0) == pytest.approx(2.34567891, abs=1e-5) + assert g.GetZ(0) == pytest.approx(9.87654321, abs=1e-3) + assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) + assert g.GetM(0) == pytest.approx(-9.87654321, abs=1e-2) + assert g.GetM(0) != pytest.approx(-9.87654321, abs=1e-8) + ds.Close() + + j = gdal.VectorInfo(filename, format="json") + j_geom_field = j["layers"][0]["geometryFields"][0] + assert j_geom_field["xyCoordinateResolution"] == 1e-5 + assert j_geom_field["zCoordinateResolution"] == 1e-3 + assert j_geom_field["mCoordinateResolution"] == 1e-2 + assert j_geom_field["coordinatePrecisionFormatSpecificOptions"] == { + "FileGeodatabase": { + "XOrigin": -2147483647, + "YOrigin": -2147483647, + "XYScale": 100000, + "ZOrigin": -100000, + "ZScale": 1000, + "MOrigin": -100000, + "MScale": 100, + "XYTolerance": 1e-06, + "ZTolerance": 0.0001, + "MTolerance": 0.001, + "HighPrecision": "true", + } + } diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 8fad3db57da7..04333712afa5 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -1906,7 +1906,7 @@ def test_ogr_geojson_50(): ############################################################################### -# Test writing empty geometries +# Test writing and reading empty geometries def test_ogr_geojson_51(): @@ -1949,8 +1949,6 @@ def test_ogr_geojson_51(): data = gdal.VSIFReadL(1, 10000, fp).decode("ascii") gdal.VSIFCloseL(fp) - gdal.Unlink("/vsimem/ogr_geojson_51.json") - assert '{ "id": 1 }, "geometry": null' in data assert ( @@ -1982,6 +1980,14 @@ def test_ogr_geojson_51(): in data ) + ds = ogr.Open("/vsimem/ogr_geojson_51.json") + lyr = ds.GetLayer(0) + for f in lyr: + if f.GetFID() >= 2: + assert f.GetGeometryRef().IsEmpty() + + gdal.Unlink("/vsimem/ogr_geojson_51.json") + ############################################################################### # Test NULL type detection @@ -4349,12 +4355,18 @@ def test_ogr_geojson_coordinate_precision(): data = gdal.VSIFReadL(1, 10000, fp).decode("ascii") gdal.VSIFCloseL(fp) - gdal.Unlink(filename) - assert '"bbox": [ 1.2, 2.3, 1.2, 2.3 ]' in data assert '"coordinates": [ 1.2, 2.3 ]' in data assert "3456" not in data + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-1 + + gdal.Unlink(filename) + ############################################################################### # Test fix for https://github.com/OSGeo/gdal/issues/7319 @@ -5056,3 +5068,115 @@ def test_ogr_json_getextent3d(tmp_vsimem): assert lyr.GetExtent() == (3.0, 6.0, 4.0, 7.0) assert lyr.GetExtent3D() == (3.0, 6.0, 4.0, 7.0, 2.0, 5.0) + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_geojson_geom_coord_precision(tmp_vsimem): + + filename = str(tmp_vsimem / "test.geojson") + ds = gdal.GetDriverByName("GeoJSON").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321)")) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"xy_coordinate_resolution"' in data + assert b'"z_coordinate_resolution"' in data + assert b'"coordinates": [ 1.23457, 2.34568, 9.877 ]' in data + + # Test appending feature + ds = ogr.Open(filename, update=1) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(2.23456789 3.34567891 8.87654321)")) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"coordinates": [ 1.23457, 2.34568, 9.877 ]' in data + assert b'"coordinates": [ 2.23457, 3.34568, 8.877 ]' in data + + # Test modifying existing feature + ds = ogr.Open(filename, update=1) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + f = lyr.GetNextFeature() + f.SetGeometry( + ogr.CreateGeometryFromWkt("POINT(-2.23456789 -3.34567891 -8.87654321)") + ) + lyr.SetFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"coordinates": [ -2.23457, -3.34568, -8.877 ]' in data + assert b'"coordinates": [ 2.23457, 3.34568, 8.877 ]' in data + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + ds = None + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_geojson_geom_coord_precision_RFC7946(tmp_vsimem): + + filename = str(tmp_vsimem / "test.geojson") + ds = gdal.GetDriverByName("GeoJSON").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-3, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + srs = osr.SpatialReference() + srs.SetFromUserInput("EPSG:32631+3855") + geom_fld.SetSpatialRef(srs) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld, ["RFC7946=YES"]) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(8.983152841195214e-09) + assert prec.GetZResolution() == 1e-3 + ds.Close() + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(8.983152841195214e-09) + assert prec.GetZResolution() == 1e-3 diff --git a/autotest/ogr/ogr_geojsonseq.py b/autotest/ogr/ogr_geojsonseq.py index d6d2ef424256..3cbf57210a6a 100755 --- a/autotest/ogr/ogr_geojsonseq.py +++ b/autotest/ogr/ogr_geojsonseq.py @@ -396,3 +396,95 @@ def test_ogr_geojsonseq_vsigzip(): ds = None gdal.Unlink(filename) + + +############################################################################### +# Test COORDINATE_PRECISION option + + +def test_ogr_geojsonseq_COORDINATE_PRECISION(tmp_vsimem): + + filename = str(tmp_vsimem / "test.geojsonl") + ds = gdal.GetDriverByName("GeoJSONSeq").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + lyr = ds.CreateLayer("test", options=["COORDINATE_PRECISION=3"]) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-3 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321)")) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"coordinates": [ 1.235, 2.346, 9.877 ]' in data + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_geojsonseq_geom_coord_precision_already_4326(tmp_vsimem): + + filename = str(tmp_vsimem / "test.geojsonl") + ds = gdal.GetDriverByName("GeoJSONSeq").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + geom_fld.SetSpatialRef(srs) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321)")) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"coordinates": [ 1.23457, 2.34568, 9.877 ]' in data + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_geojsonseq_geom_coord_precision_not_4326(tmp_vsimem): + + filename = str(tmp_vsimem / "test.geojsonl") + ds = gdal.GetDriverByName("GeoJSONSeq").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + srs = osr.SpatialReference() + srs.ImportFromEPSG(32631) + geom_fld.SetSpatialRef(srs) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(8.983152841195214e-06) + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(450000 5000000 9.87654321)")) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b'"coordinates": [ 2.363925, 45.151706, 9.877 ]' in data diff --git a/autotest/ogr/ogr_geomcoordinateprecision.py b/autotest/ogr/ogr_geomcoordinateprecision.py new file mode 100755 index 000000000000..bb070410c47f --- /dev/null +++ b/autotest/ogr/ogr_geomcoordinateprecision.py @@ -0,0 +1,107 @@ +#!/usr/bin/env pytest +# -*- coding: utf-8 -*- +############################################################################### +# Project: GDAL/OGR Test Suite +# Purpose: Test ogr.GeomCoordinatePrecision +# Author: Even Rouault +# +############################################################################### +# Copyright (c) 2024, Even Rouault +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +############################################################################### + +import pytest + +from osgeo import ogr, osr + + +def test_ogr_geomcoordinate_precision(): + + prec = ogr.CreateGeomCoordinatePrecision() + assert prec.GetXYResolution() == 0 + assert prec.GetZResolution() == 0 + assert prec.GetMResolution() == 0 + + prec.Set(1e-9, 1e-3, 1e-2) + assert prec.GetXYResolution() == 1e-9 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + + with pytest.raises(Exception, match="Received a NULL pointer"): + prec.SetFromMeter(None, 0, 0, 0) + + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + prec.SetFromMeter(srs, 1e-3, 1e-3, 1e-1) + assert prec.GetXYResolution() == pytest.approx(8.983152841195213e-09) + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-1 + + srs = osr.SpatialReference() + srs.ImportFromEPSG(4979) + prec.SetFromMeter(srs, 1e-3, 1e-3, 1e-1) + assert prec.GetXYResolution() == pytest.approx(8.983152841195213e-09) + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-1 + + srs = osr.SpatialReference() + srs.SetFromUserInput("EPSG:4269+8228") # "NAD83 + NAVD88 height (ft)" + prec.SetFromMeter(srs, 1e-3, 1e-3, 1e-1) + assert prec.GetXYResolution() == pytest.approx(8.983152841195213e-09) + assert prec.GetZResolution() == pytest.approx(0.0032808398950131233) + assert prec.GetMResolution() == 1e-1 + + assert prec.GetFormats() is None + assert prec.GetFormatSpecificOptions("foo") == {} + with pytest.raises(Exception, match="Received a NULL pointer"): + prec.GetFormatSpecificOptions(None) + prec.SetFormatSpecificOptions("my_format", {"key": "value"}) + assert prec.GetFormats() == ["my_format"] + assert prec.GetFormatSpecificOptions("my_format") == {"key": "value"} + + +def test_ogr_geomcoordinate_precision_geom_field(): + + geom_fld = ogr.GeomFieldDefn("foo") + assert geom_fld.GetCoordinatePrecision().GetXYResolution() == 0 + + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-9, 1e-3, 1e-2) + geom_fld.SetCoordinatePrecision(prec) + assert geom_fld.GetCoordinatePrecision().GetXYResolution() == 1e-9 + assert geom_fld.GetCoordinatePrecision().GetZResolution() == 1e-3 + assert geom_fld.GetCoordinatePrecision().GetMResolution() == 1e-2 + + feature_defn = ogr.FeatureDefn("test") + feature_defn.AddGeomFieldDefn(geom_fld) + assert feature_defn.IsSame(feature_defn) + + for (xy, z, m) in [ + (1e-9 * 10, 1e-3, 1e-2), + (1e-9, 1e-3 * 10, 1e-2), + (1e-9, 1e-3, 1e-2 * 10), + ]: + geom_fld2 = ogr.GeomFieldDefn("foo") + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(xy, z, m) + geom_fld2.SetCoordinatePrecision(prec) + feature_defn2 = ogr.FeatureDefn("test") + feature_defn2.AddGeomFieldDefn(geom_fld2) + assert not feature_defn.IsSame(feature_defn2) diff --git a/autotest/ogr/ogr_geos.py b/autotest/ogr/ogr_geos.py index 33ab6386a0e4..2d010efdc94f 100755 --- a/autotest/ogr/ogr_geos.py +++ b/autotest/ogr/ogr_geos.py @@ -624,3 +624,13 @@ def test_ogr_geos_prepared_geom(): # Test workaround for https://github.com/libgeos/geos/pull/423 assert not pg.Intersects(ogr.CreateGeometryFromWkt("POINT EMPTY")) assert not pg.Contains(ogr.CreateGeometryFromWkt("POINT EMPTY")) + + +############################################################################### + + +def test_ogr_geos_set_precision(): + + g = ogr.CreateGeometryFromWkt("LINESTRING (1 1,9 9)") + g = g.SetPrecision(10) + assert g.ExportToWkt() == "LINESTRING (0 0,10 10)" diff --git a/autotest/ogr/ogr_gml_read.py b/autotest/ogr/ogr_gml.py similarity index 98% rename from autotest/ogr/ogr_gml_read.py rename to autotest/ogr/ogr_gml.py index 01e6d6525799..83651bcc7fe7 100755 --- a/autotest/ogr/ogr_gml_read.py +++ b/autotest/ogr/ogr_gml.py @@ -4,7 +4,7 @@ # $Id$ # # Project: GDAL/OGR Test Suite -# Purpose: GML Reading Driver testing. +# Purpose: GML driver testing. # Author: Frank Warmerdam # ############################################################################### @@ -4242,3 +4242,73 @@ def test_ogr_gml_ignore_old_gfs(tmp_path): width = defn.GetFieldDefn(1).GetWidth() assert width == 10 + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_gml_geom_coord_precision(tmp_vsimem): + + filename = str(tmp_vsimem / "test.gml") + ds = gdal.GetDriverByName("GML").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("my_geom_field", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + geom_fld.SetNullable(False) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891)")) + lyr.CreateFeature(f) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321)")) + lyr.CreateFeature(f) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt( + "LINESTRING(1.23456789 2.34567891 9.87654321,1.23456789 2.34567891 9.87654321)" + ) + ) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert ( + b"""\n 1.23457 2.34568 0.01.23457 2.34568 9.877""" + in data + ) + assert ( + b"""\n 1.23457 2.34568 9.8771.23457 2.34568 9.877""" + in data + ) + assert b"""1.23457 2.34568""" in data + assert b"""1.23457 2.34568 9.877""" in data + assert ( + b"""1.23457 2.34568 9.877 1.23457 2.34568 9.877""" + in data + ) + + f = gdal.VSIFOpenL(filename[0:-3] + "xsd", "rb") + assert f + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + assert b"" in data + assert b"" in data + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + assert geom_fld.GetName() == "my_geom_field" + assert not geom_fld.IsNullable() + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 6d58c77795dd..67fb786af1ba 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -10140,3 +10140,172 @@ def test_ogr_gpkg_creation_with_foreign_key_constraint_enabled(tmp_vsimem): with gdaltest.config_option("OGR_SQLITE_PRAGMA", "FOREIGN_KEYS=1"): out_filename = str(tmp_vsimem / "out.gpkg") gdal.VectorTranslate(out_filename, "data/poly.shp") + + +############################################################################### +# Test geometry coordinate precision support + + +@gdaltest.enable_exceptions() +@pytest.mark.parametrize( + "with_metadata,DISCARD_COORD_LSB,UNDO_DISCARD_COORD_LSB_ON_READING", + [(True, "YES", "YES"), (False, "YES", "NO"), (False, "NO", "NO")], +) +def test_ogr_gpkg_geom_coord_precision( + tmp_vsimem, with_metadata, DISCARD_COORD_LSB, UNDO_DISCARD_COORD_LSB_ON_READING +): + + filename = str(tmp_vsimem / "test.gpkg") + ds = gdal.GetDriverByName("GPKG").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("my_geom", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 1e-2) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn( + "test", + geom_fld, + [ + "DISCARD_COORD_LSB=" + DISCARD_COORD_LSB, + "UNDO_DISCARD_COORD_LSB_ON_READING=" + UNDO_DISCARD_COORD_LSB_ON_READING, + ], + ) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt( + "POINT ZM (1.23456789 2.34567891 9.87654321 -1.23456789)" + ) + ) + lyr.CreateFeature(f) + if with_metadata: + lyr.SetMetadataItem("FOO", "BAR") + ds.Close() + + ds = ogr.Open(filename, update=1) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert ds.GetMetadata() == {} + assert lyr.GetMetadata() == ({"FOO": "BAR"} if with_metadata else {}) + f = lyr.GetNextFeature() + + g = f.GetGeometryRef() + assert g.GetX(0) == pytest.approx(1.23456789, abs=1e-5) + if DISCARD_COORD_LSB == "YES": + assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetX(0) == pytest.approx(1.23457, abs=1e-8) + else: + assert g.GetX(0) != pytest.approx(1.23457, abs=1e-8) + + assert g.GetY(0) == pytest.approx(2.34567891, abs=1e-5) + if DISCARD_COORD_LSB == "YES": + assert g.GetY(0) != pytest.approx(2.34567891, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetY(0) == pytest.approx(2.34568, abs=1e-8) + + assert g.GetZ(0) == pytest.approx(9.87654321, abs=1e-3) + if DISCARD_COORD_LSB == "YES": + assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetZ(0) == pytest.approx(9.876, abs=1e-8) + + assert g.GetM(0) == pytest.approx(-1.23456789, abs=1e-2) + if DISCARD_COORD_LSB == "YES": + assert g.GetM(0) != pytest.approx(-1.23456789, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetM(0) == pytest.approx(-1.23, abs=1e-8) + + # Test Arrow interface + lyr.ResetReading() + mem_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + mem_lyr = mem_ds.CreateLayer("test", geom_type=ogr.wkbNone) + mem_lyr.CreateGeomField(ogr.GeomFieldDefn("my_geom")) + mem_lyr.WriteArrow(lyr) + f = mem_lyr.GetNextFeature() + + g = f.GetGeometryRef() + assert g.GetX(0) == pytest.approx(1.23456789, abs=1e-5) + if DISCARD_COORD_LSB == "YES": + assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetX(0) == pytest.approx(1.23457, abs=1e-8) + else: + assert g.GetX(0) != pytest.approx(1.23457, abs=1e-8) + + assert g.GetY(0) == pytest.approx(2.34567891, abs=1e-5) + if DISCARD_COORD_LSB == "YES": + assert g.GetY(0) != pytest.approx(2.34567891, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetY(0) == pytest.approx(2.34568, abs=1e-8) + + assert g.GetZ(0) == pytest.approx(9.87654321, abs=1e-3) + if DISCARD_COORD_LSB == "YES": + assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetZ(0) == pytest.approx(9.876, abs=1e-8) + + assert g.GetM(0) == pytest.approx(-1.23456789, abs=1e-2) + if DISCARD_COORD_LSB == "YES": + assert g.GetM(0) != pytest.approx(-1.23456789, abs=1e-8) + if UNDO_DISCARD_COORD_LSB_ON_READING == "YES": + assert g.GetM(0) == pytest.approx(-1.23, abs=1e-8) + + lyr.ResetReading() + f = lyr.GetNextFeature() + # Update geometry to check existing precision settings are used + f.SetGeometry( + ogr.CreateGeometryFromWkt( + "POINT ZM (-1.23456789 -2.34567891 -9.87654321 1.23456789)" + ) + ) + lyr.SetFeature(f) + + lyr.SetMetadataItem("FOO", "BAZ") + + new_geom_field_defn = ogr.GeomFieldDefn("new_geom_name", ogr.wkbNone) + assert ( + lyr.AlterGeomFieldDefn( + 0, new_geom_field_defn, ogr.ALTER_GEOM_FIELD_DEFN_NAME_FLAG + ) + == ogr.OGRERR_NONE + ) + + assert lyr.Rename("test_renamed") == ogr.OGRERR_NONE + + ds.Close() + + ds = ogr.Open(filename, update=1) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert lyr.GetMetadata() == {"FOO": "BAZ"} + f = lyr.GetNextFeature() + g = f.GetGeometryRef() + assert g.GetX(0) == pytest.approx(-1.23456789, abs=1e-5) + if DISCARD_COORD_LSB == "YES": + assert g.GetX(0) != pytest.approx(-1.23456789, abs=1e-8) + assert g.GetY(0) == pytest.approx(-2.34567891, abs=1e-5) + assert g.GetZ(0) == pytest.approx(-9.87654321, abs=1e-3) + if DISCARD_COORD_LSB == "YES": + assert g.GetZ(0) != pytest.approx(-9.87654321, abs=1e-8) + assert g.GetM(0) == pytest.approx(1.23456789, abs=1e-2) + if DISCARD_COORD_LSB == "YES": + assert g.GetM(0) != pytest.approx(1.23456789, abs=1e-8) + + ds.DeleteLayer(0) + + with ds.ExecuteSQL("SELECT * FROM gpkg_metadata") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + ds.Close() diff --git a/autotest/ogr/ogr_jsonfg.py b/autotest/ogr/ogr_jsonfg.py index 649514fc780f..5f288c0270a7 100755 --- a/autotest/ogr/ogr_jsonfg.py +++ b/autotest/ogr/ogr_jsonfg.py @@ -1140,7 +1140,9 @@ def test_jsonfg_write_COORDINATE_PRECISION(): filename = "/vsimem/test_jsonfg_write_COORDINATE_PRECISION.json" try: - ds = ogr.GetDriverByName("JSONFG").CreateDataSource(filename) + ds = ogr.GetDriverByName("JSONFG").CreateDataSource( + filename, options=["SINGLE_LAYER=YES"] + ) lyr = ds.CreateLayer( "test", srs=_get_epsg_crs(32631), @@ -1157,6 +1159,8 @@ def test_jsonfg_write_COORDINATE_PRECISION(): gdal.VSIFCloseL(f) j = json.loads(data) + assert j["xy_coordinate_resolution"] == 1e-3 + assert j["xy_coordinate_resolution_place"] == 1e-2 feature = j["features"][0] assert feature["geometry"]["coordinates"] == pytest.approx( [3.0, 40.651], abs=1e-3 @@ -1207,3 +1211,60 @@ def test_jsonfg_write_flushcache(): finally: if gdal.VSIStatL(filename): gdal.Unlink(filename) + + +############################################################################### +# Test geometry coordinate precision support + + +@pytest.mark.parametrize("single_layer", [True, False]) +def test_ogr_jsonfg_geom_coord_precision(tmp_vsimem, single_layer): + + filename = str(tmp_vsimem / "test.json") + ds = gdal.GetDriverByName("JSONFG").Create( + filename, + 0, + 0, + 0, + gdal.GDT_Unknown, + options=["SINGLE_LAYER=" + ("YES" if single_layer else "NO")], + ) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbUnknown) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-2, 1e-3, 0) + geom_fld.SetCoordinatePrecision(prec) + srs = osr.SpatialReference() + srs.SetFromUserInput("EPSG:32631+3855") + geom_fld.SetSpatialRef(srs) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-2 + assert prec.GetZResolution() == 1e-3 + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt("POINT(4500000.23456789 5000000.34567891 9.87654321)") + ) + lyr.CreateFeature(f) + ds.Close() + + f = gdal.VSIFOpenL(filename, "rb") + if f: + data = gdal.VSIFReadL(1, 10000, f) + gdal.VSIFCloseL(f) + + assert b"[ 46.44289405, 36.08688377, 9.877 ]" in data + assert b"[ 4500000.23, 5000000.35, 9.877 ]" in data + + j = json.loads(data) + assert j["xy_coordinate_resolution"] == pytest.approx(8.98315e-08) + assert j["z_coordinate_resolution"] == 1e-3 + assert j["xy_coordinate_resolution_place"] == 1e-2 + assert j["z_coordinate_resolution_place"] == 1e-3 + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-2 + assert prec.GetZResolution() == 1e-3 diff --git a/autotest/ogr/ogr_openfilegdb_write.py b/autotest/ogr/ogr_openfilegdb_write.py index eadd7147c8a0..f49478e73332 100755 --- a/autotest/ogr/ogr_openfilegdb_write.py +++ b/autotest/ogr/ogr_openfilegdb_write.py @@ -4530,3 +4530,130 @@ def test_ogr_openfilegdb_write_update_feature_larger(tmp_vsimem): lyr = ds.GetLayer(0) f = lyr.GetNextFeature() assert f.GetGeometryRef().GetGeometryRef(0).GetPointCount() == 1000 + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_openfilegdb_write_geom_coord_precision(tmp_vsimem): + + filename = str(tmp_vsimem / "test.gdb") + ds = gdal.GetDriverByName("OpenFileGDB").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbPointZM) + prec = ogr.CreateGeomCoordinatePrecision() + prec.Set(1e-5, 1e-3, 1e-2) + geom_fld.SetCoordinatePrecision(prec) + lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert prec.GetFormats() == ["FileGeodatabase"] + opts = prec.GetFormatSpecificOptions("FileGeodatabase") + for key in opts: + opts[key] = float(opts[key]) + assert opts == { + "MOrigin": -100000.0, + "MScale": 100.0, + "MTolerance": 0.001, + "XOrigin": -2147483647.0, + "XYScale": 100000.0, + "XYTolerance": 1e-06, + "YOrigin": -2147483647.0, + "ZOrigin": -100000.0, + "ZScale": 1000.0, + "ZTolerance": 0.0001, + } + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometry( + ogr.CreateGeometryFromWkt("POINT(1.23456789 2.34567891 9.87654321 -9.87654321)") + ) + lyr.CreateFeature(f) + ds.Close() + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 + assert prec.GetFormats() == ["FileGeodatabase"] + opts = prec.GetFormatSpecificOptions("FileGeodatabase") + for key in opts: + try: + opts[key] = float(opts[key]) + except ValueError: + pass + assert opts == { + "MOrigin": -100000.0, + "MScale": 100.0, + "MTolerance": 0.001, + "XOrigin": -2147483647.0, + "XYScale": 100000.0, + "XYTolerance": 1e-06, + "YOrigin": -2147483647.0, + "ZOrigin": -100000.0, + "ZScale": 1000.0, + "ZTolerance": 0.0001, + "HighPrecision": "true", + } + f = lyr.GetNextFeature() + g = f.GetGeometryRef() + assert g.GetX(0) == pytest.approx(1.23456789, abs=1e-5) + assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) + assert g.GetY(0) == pytest.approx(2.34567891, abs=1e-5) + assert g.GetZ(0) == pytest.approx(9.87654321, abs=1e-3) + assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) + assert g.GetM(0) == pytest.approx(-9.87654321, abs=1e-2) + assert g.GetM(0) != pytest.approx(-9.87654321, abs=1e-8) + ds.Close() + + j = gdal.VectorInfo(filename, format="json") + j_geom_field = j["layers"][0]["geometryFields"][0] + assert j_geom_field["xyCoordinateResolution"] == 1e-5 + assert j_geom_field["zCoordinateResolution"] == 1e-3 + assert j_geom_field["mCoordinateResolution"] == 1e-2 + assert j_geom_field["coordinatePrecisionFormatSpecificOptions"] == { + "FileGeodatabase": { + "XOrigin": -2147483647, + "YOrigin": -2147483647, + "XYScale": 100000, + "ZOrigin": -100000, + "ZScale": 1000, + "MOrigin": -100000, + "MScale": 100, + "XYTolerance": 1e-06, + "ZTolerance": 0.0001, + "MTolerance": 0.001, + "HighPrecision": "true", + } + } + + filename2 = str(tmp_vsimem / "test2.gdb") + gdal.VectorTranslate(filename2, filename, format="OpenFileGDB") + + ds = ogr.Open(filename2) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + opts = prec.GetFormatSpecificOptions("FileGeodatabase") + for key in opts: + try: + opts[key] = float(opts[key]) + except ValueError: + pass + assert opts == { + "MOrigin": -100000.0, + "MScale": 100.0, + "MTolerance": 0.001, + "XOrigin": -2147483647.0, + "XYScale": 100000.0, + "XYTolerance": 1e-06, + "YOrigin": -2147483647.0, + "ZOrigin": -100000.0, + "ZScale": 1000.0, + "ZTolerance": 0.0001, + "HighPrecision": "true", + } diff --git a/autotest/ogr/ogr_shape_sbn.py b/autotest/ogr/ogr_shape_sbn.py index 6fe5cd0ca991..f528308b6e6e 100755 --- a/autotest/ogr/ogr_shape_sbn.py +++ b/autotest/ogr/ogr_shape_sbn.py @@ -142,3 +142,22 @@ def test_ogr_shape_sbn_2(): ds = ogr.Open("data/shp/CoHI_GCS12.shp") lyr = ds.GetLayer(0) return search_all_features(lyr) + + +############################################################################### +# Test bugfix for https://github.com/OSGeo/gdal/issues/9430 + + +@pytest.mark.require_curl() +def test_ogr_shape_sbn_out_of_order_bin_start(): + + srv = "https://github.com/OSGeo/gdal-test-datasets/raw/master/shapefile/65sv5l285i_GLWD_level2/README.TXT" + if gdaltest.gdalurlopen(srv, timeout=5) is None: + pytest.skip(reason=f"{srv} is down") + + ds = ogr.Open( + "/vsizip//vsicurl/https://github.com/OSGeo/gdal-test-datasets/raw/master/shapefile/65sv5l285i_GLWD_level2/65sv5l285i_GLWD_level2_sozip.zip" + ) + lyr = ds.GetLayer(0) + lyr.SetSpatialFilterRect(5, 5, 6, 6) + assert lyr.GetFeatureCount() == 13 diff --git a/autotest/ogr/ogr_vrt.py b/autotest/ogr/ogr_vrt.py index f6b1d8659c86..996bcf11cc89 100755 --- a/autotest/ogr/ogr_vrt.py +++ b/autotest/ogr/ogr_vrt.py @@ -3330,3 +3330,31 @@ def test_ogr_vrt_field_names_same_case(): f = lyr.GetNextFeature() assert f["id"] == "foo" assert f["id_from_uc"] == "bar" + + +############################################################################### +# Test geometry coordinate precision support + + +def test_ogr_vrt_geom_coordinate_precision(): + + ds = ogr.Open( + """ + + data/poly.shp + + wkbPolygon + 1e-5 + 1e-3 + 1e-2 + + + +""" + ) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-2 diff --git a/autotest/osr/osr_basic.py b/autotest/osr/osr_basic.py index c392dad5c70a..5c188ce4e27c 100755 --- a/autotest/osr/osr_basic.py +++ b/autotest/osr/osr_basic.py @@ -1840,6 +1840,9 @@ def threaded_function(arg): def test_Set_PROJ_DATA_config_option_sub_proccess_config_option_ok(): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + backup_search_paths = osr.GetPROJSearchPaths() # conftest.py set 2 paths: autotest/gcore/tmp/proj_db_tmpdir and autotest/proj_grids assert len(backup_search_paths) == 2 @@ -1858,6 +1861,9 @@ def test_Set_PROJ_DATA_config_option_sub_proccess_config_option_ok(): def test_Set_PROJ_DATA_config_option_sub_proccess_config_option_ko(): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + backup_search_paths = osr.GetPROJSearchPaths() # conftest.py set 2 paths: autotest/gcore/tmp/proj_db_tmpdir and autotest/proj_grids assert len(backup_search_paths) == 2 diff --git a/autotest/osr/osr_url.py b/autotest/osr/osr_url.py index 9d491eaecdc4..06ae66d3c392 100755 --- a/autotest/osr/osr_url.py +++ b/autotest/osr/osr_url.py @@ -108,3 +108,12 @@ def test_osr_opengis_https_4326(): srs = osr.SpatialReference() assert srs.SetFromUserInput("https://opengis.net/def/crs/EPSG/0/4326") == 0 assert gdaltest.equal_srs_from_wkt(expected_wkt, srs.ExportToWkt()) + + +def test_osr_SetFromUserInput_http_disabled(): + srs = osr.SpatialReference() + with pytest.raises(Exception, match="due to ALLOW_NETWORK_ACCESS=NO"): + srs.SetFromUserInput( + "https://spatialreference.org/ref/epsg/4326/", + options=["ALLOW_NETWORK_ACCESS=NO"], + ) diff --git a/autotest/pyscripts/test_gdal_calc.py b/autotest/pyscripts/test_gdal_calc.py index 1ab9a0d48d00..06dbc765042b 100755 --- a/autotest/pyscripts/test_gdal_calc.py +++ b/autotest/pyscripts/test_gdal_calc.py @@ -36,6 +36,7 @@ from collections import defaultdict from copy import copy +import gdaltest import pytest import test_py_scripts @@ -69,6 +70,9 @@ def script_path(): def test_gdal_calc_help(script_path): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + assert "ERROR" not in test_py_scripts.run_py_script( script_path, "gdal_calc", "--help" ) @@ -80,6 +84,9 @@ def test_gdal_calc_help(script_path): def test_gdal_calc_version(script_path): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + assert "ERROR" not in test_py_scripts.run_py_script( script_path, "gdal_calc", "--version" ) diff --git a/autotest/pyscripts/test_gdal_edit.py b/autotest/pyscripts/test_gdal_edit.py index 6d10bf9aaafc..7488f5541be9 100755 --- a/autotest/pyscripts/test_gdal_edit.py +++ b/autotest/pyscripts/test_gdal_edit.py @@ -33,6 +33,7 @@ import shutil import sys +import gdaltest import pytest import test_py_scripts @@ -143,6 +144,9 @@ def test_gdal_edit_py_1(script_path, read_only): def test_gdal_edit_py_1b(script_path): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + filename = "tmp/test_gdal_edit_py.tif" shutil.copy(test_py_scripts.get_data_path("gcore") + "byte.tif", filename) diff --git a/autotest/pyscripts/test_gdal_pansharpen.py b/autotest/pyscripts/test_gdal_pansharpen.py index 411eb11082dd..27e731d1ad59 100755 --- a/autotest/pyscripts/test_gdal_pansharpen.py +++ b/autotest/pyscripts/test_gdal_pansharpen.py @@ -30,6 +30,7 @@ ############################################################################### +import gdaltest import pytest import test_py_scripts @@ -75,6 +76,9 @@ def small_world_pan_tif(tmp_path_factory): def test_gdal_pansharpen_help(script_path): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + assert "ERROR" not in test_py_scripts.run_py_script( script_path, "gdal_pansharpen", "--help" ) diff --git a/autotest/pyscripts/test_ogrmerge.py b/autotest/pyscripts/test_ogrmerge.py index 4c784874405b..d0e593d1ac70 100755 --- a/autotest/pyscripts/test_ogrmerge.py +++ b/autotest/pyscripts/test_ogrmerge.py @@ -31,6 +31,7 @@ import os import sys +import gdaltest import pytest import test_py_scripts from test_py_scripts import samples_path @@ -244,6 +245,9 @@ def test_ogrmerge_6(script_path): def test_ogrmerge_7(script_path): + if gdaltest.is_travis_branch("sanitize"): + pytest.skip("fails on sanitize for unknown reason") + # No match in -single mode test_py_scripts.run_py_script( script_path, diff --git a/autotest/requirements.txt b/autotest/requirements.txt index 61697e503875..5767d6511013 100644 --- a/autotest/requirements.txt +++ b/autotest/requirements.txt @@ -6,3 +6,5 @@ pytest-benchmark lxml jsonschema filelock +# importlib 6.2 and 6.3 break pytest with jsonschema. Cf https://github.com/python/importlib_resources/issues/299 +importlib-resources<6.2.0 diff --git a/autotest/utilities/test_gdal_grid_lib.py b/autotest/utilities/test_gdal_grid_lib.py index 8c02b76c1dd3..6ad09bd42d45 100755 --- a/autotest/utilities/test_gdal_grid_lib.py +++ b/autotest/utilities/test_gdal_grid_lib.py @@ -97,6 +97,8 @@ def test_gdal_grid_lib_1(n43_tif, n43_shp): outputType=gdal.GDT_Int16, algorithm="nearest:radius1=0.0:radius2=0.0:angle=0.0", spatFilter=spatFilter, + layers=[n43_shp.GetLayer(0).GetName()], + where="1=1", ) # We should get the same values as in n43.td0 @@ -147,6 +149,7 @@ def test_gdal_grid_lib_2(tmp_vsimem, env): width=1, height=1, outputType=gdal.GDT_Byte, + SQLStatement="select * from test_gdal_grid_lib_2", ) ds2 = gdal.Grid( @@ -1050,3 +1053,51 @@ def test_gdal_grid_lib_dict_arguments(): ind = opt.index("-co") assert opt[ind : ind + 4] == ["-co", "COMPRESS=DEFLATE", "-co", "LEVEL=4"] + + +############################################################################### +# Test various error conditions + + +def test_gdal_grid_lib_errors(): + + mem_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + mem_ds.CreateLayer("test") + + with pytest.raises(Exception, match='Unable to find layer "invalid"'): + gdal.Grid( + "", + mem_ds, + width=3, + height=3, + outputBounds=[-0.25, -0.25, 1.25, 1.25], + format="MEM", + algorithm="invdist", + layers=["invalid"], + ) + + with pytest.raises( + Exception, match='"invalid" not recognised as an available field' + ): + gdal.Grid( + "", + mem_ds, + width=3, + height=3, + outputBounds=[-0.25, -0.25, 1.25, 1.25], + format="MEM", + algorithm="invdist", + where="invalid", + ) + + with pytest.raises(Exception, match="SQL Expression Parsing Error"): + gdal.Grid( + "", + mem_ds, + width=3, + height=3, + outputBounds=[-0.25, -0.25, 1.25, 1.25], + format="MEM", + algorithm="invdist", + SQLStatement="invalid", + ) diff --git a/autotest/utilities/test_gdal_translate_lib.py b/autotest/utilities/test_gdal_translate_lib.py index 41254b219f00..b7dc11c1b144 100755 --- a/autotest/utilities/test_gdal_translate_lib.py +++ b/autotest/utilities/test_gdal_translate_lib.py @@ -1249,3 +1249,34 @@ def test_gdal_translate_ovr_rpc(): assert float(ovr_rpc["SAMP_SCALE"]) == pytest.approx( 0.5 * float(src_rpc["SAMP_SCALE"]) ) + + +############################################################################### +# Test scenario of https://github.com/OSGeo/gdal/issues/9402 + + +def test_gdal_translate_lib_raster_uint16_ct_0_255_range(): + + for (r, g, b, a) in [ + (255 + 1, 255, 255, 255), + (255, 255 + 1, 255, 255), + (255, 255, 255 + 1, 255), + (255, 255, 255, 255 + 1), + ]: + src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 1, gdal.GDT_UInt16) + ct = gdal.ColorTable() + ct.SetColorEntry(0, (r, g, b, a)) + src_ds.GetRasterBand(1).SetRasterColorTable(ct) + out_ds = gdal.Translate("", src_ds, format="MEM", rgbExpand="rgb") + assert out_ds.GetRasterBand(1).DataType == gdal.GDT_UInt16 + assert out_ds.GetRasterBand(2).DataType == gdal.GDT_UInt16 + assert out_ds.GetRasterBand(3).DataType == gdal.GDT_UInt16 + + src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 1, gdal.GDT_UInt16) + ct = gdal.ColorTable() + ct.SetColorEntry(0, (255, 255, 255, 255)) + src_ds.GetRasterBand(1).SetRasterColorTable(ct) + out_ds = gdal.Translate("", src_ds, format="MEM", rgbExpand="rgb") + assert out_ds.GetRasterBand(1).DataType == gdal.GDT_Byte + assert out_ds.GetRasterBand(2).DataType == gdal.GDT_Byte + assert out_ds.GetRasterBand(3).DataType == gdal.GDT_Byte 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.py b/autotest/utilities/test_gdalinfo.py index 84792d39e806..07054b9a036b 100755 --- a/autotest/utilities/test_gdalinfo.py +++ b/autotest/utilities/test_gdalinfo.py @@ -32,6 +32,7 @@ import json import os import shutil +import stat import gdaltest import pytest @@ -1018,3 +1019,28 @@ def test_gdalinfo_stac_eo_bands(gdalinfo_path, tmp_path): data = json.loads(ret) assert data["stac"]["eo:cloud_cover"] == 2 + + +def test_gdalinfo_access_to_file_without_permission(gdalinfo_path, tmp_path): + + tmpfilename = str(tmp_path / "test.bin") + with open(tmpfilename, "wb") as f: + f.write(b"\x00" * 1024) + os.chmod(tmpfilename, 0) + + # Test that file is not accessible + try: + f = open(tmpfilename, "rb") + f.close() + pytest.skip("could not set non accessible permission") + except IOError: + pass + + _, err = gdaltest.runexternal_out_and_err( + gdalinfo_path + " " + tmpfilename, + encoding="UTF-8", + ) + lines = list(filter(lambda x: len(x) > 0, err.split("\n"))) + assert (len(lines)) == 3 + + os.chmod(tmpfilename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) diff --git a/autotest/utilities/test_gdalinfo_lib.py b/autotest/utilities/test_gdalinfo_lib.py index af751909b8cd..57358612d7f6 100755 --- a/autotest/utilities/test_gdalinfo_lib.py +++ b/autotest/utilities/test_gdalinfo_lib.py @@ -276,3 +276,32 @@ def test_gdalinfo_lib_json_proj_shape(): ds = gdal.GetDriverByName("MEM").Create("", width, height) ret = gdal.Info(ds, options="-json") assert ret["stac"]["proj:shape"] == [height, width] + + +############################################################################### +# Test fix for https://github.com/OSGeo/gdal/issues/9396 + + +def test_gdalinfo_lib_json_engineering_crs(): + + ds = gdal.GetDriverByName("MEM").Create("", 1, 1) + srs = osr.SpatialReference() + srs.SetFromUserInput( + """ENGCRS["Arbitrary (m)", + EDATUM["Unknown engineering datum"], + CS[Cartesian,2], + AXIS["(E)",east, + ORDER[1], + LENGTHUNIT["metre",1, + ID["EPSG",9001]]], + AXIS["(N)",north, + ORDER[2], + LENGTHUNIT["metre",1, + ID["EPSG",9001]]]]""" + ) + ds.SetSpatialRef(srs) + ds.SetGeoTransform([0, 1, 0, 0, 0, 1]) + ret = gdal.Info(ds, format="json") + assert "coordinateSystem" in ret + assert "cornerCoordinates" in ret + assert "wgs84Extent" not in ret diff --git a/autotest/utilities/test_gdallocationinfo.py b/autotest/utilities/test_gdallocationinfo.py index 449c15c0c8f4..2b8c80cd7a2e 100755 --- a/autotest/utilities/test_gdallocationinfo.py +++ b/autotest/utilities/test_gdallocationinfo.py @@ -161,3 +161,96 @@ def test_gdallocationinfo_wgs84(gdallocationinfo_path): expected_ret = """115""" assert expected_ret in ret + + +############################################################################### + + +def test_gdallocationinfo_field_sep(gdallocationinfo_path): + + ret = gdaltest.runexternal( + gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/byte.tif', + strin="0 0", + ) + + assert "107" in ret + assert "," not in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/rgbsmall.tif', + strin="15 16", + ) + + assert "72,102,16" in ret + + +############################################################################### + + +def test_gdallocationinfo_extra_input(gdallocationinfo_path): + + ret = gdaltest.runexternal( + gdallocationinfo_path + " ../gcore/data/byte.tif", strin="0 0 foo bar" + ) + + assert "Extra input: foo bar" in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + " -valonly ../gcore/data/byte.tif", strin="0 0 foo bar" + ) + + assert "107" in ret + assert "foo bar" not in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + ' -valonly -field_sep "," ../gcore/data/byte.tif', + strin="0 0 foo bar", + ) + + assert "107,foo bar" in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + " -xml ../gcore/data/byte.tif", strin="0 0 foo bar" + ) + + assert "foo bar" in ret + + +############################################################################### + + +def test_gdallocationinfo_extra_input_ignored(gdallocationinfo_path): + + ret = gdaltest.runexternal( + gdallocationinfo_path + + ' -valonly -field_sep "," -ignore_extra_input ../gcore/data/byte.tif', + strin="0 0 foo bar", + ) + + assert "107" in ret + assert "foo bar" not in ret + + +############################################################################### +# Test echo mode + + +def test_gdallocationinfo_echo(gdallocationinfo_path): + + _, err = gdaltest.runexternal_out_and_err( + gdallocationinfo_path + " -E ../gcore/data/byte.tif 1 2" + ) + assert "-E can only be used with -valonly" in err + + _, err = gdaltest.runexternal_out_and_err( + gdallocationinfo_path + " -E -valonly ../gcore/data/byte.tif 1 2" + ) + assert ( + "-E can only be used if -field_sep is specified (to a non-newline value)" in err + ) + + ret = gdaltest.runexternal( + gdallocationinfo_path + ' -E -valonly -field_sep "," ../gcore/data/byte.tif', + strin="1 2", + ) + assert "1,2,132" in ret diff --git a/autotest/utilities/test_gdaltransform.py b/autotest/utilities/test_gdaltransform.py index 3ff00950abac..667f12209b9f 100755 --- a/autotest/utilities/test_gdaltransform.py +++ b/autotest/utilities/test_gdaltransform.py @@ -285,3 +285,60 @@ def test_gdaltransform_s_t_coord_epoch(gdaltransform_path): assert len(values) == 3, ret assert abs(values[0] - -79.499999630188) < 1e-8, ret assert abs(values[1] - 60.4999999378478) < 1e-8, ret + + +############################################################################### +# Test extra input + + +def test_gdaltransform_extra_input(gdaltransform_path): + + strin = ( + "2 49 1 my first point\n" + "3 50 second point\n" + "4 51 10 2 third point\n" + ) + ret = gdaltest.runexternal( + gdaltransform_path + " -field_sep ,", + strin, + ) + + assert "2,49,1,my first point" in ret + assert "3,50,0,second point" in ret + assert "4,51,10,third point" in ret + + +############################################################################### +# Test ignoring extra input + + +def test_gdaltransform_extra_input_ignored(gdaltransform_path): + + strin = "2 49 1 my first point\n" + ret = gdaltest.runexternal( + gdaltransform_path + " -ignore_extra_input", + strin, + ) + + assert "my first point" not in ret + + +############################################################################### +# Test echo mode + + +def test_gdaltransform_echo(gdaltransform_path): + + strin = "0 0 1 my first point\n" + + ret = gdaltest.runexternal( + gdaltransform_path + " -s_srs EPSG:4326 -t_srs EPSG:4978 -E -field_sep ,", + strin, + ) + + assert "0,0,1,6378138,0,0,my first point" in ret + + ret = gdaltest.runexternal( + gdaltransform_path + " -s_srs EPSG:4326 -t_srs EPSG:4978 -E -output_xy", + strin, + ) + + assert "0 0 6378138 0 my first point" in ret diff --git a/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index e22997b97f66..939db2185acb 100755 --- a/autotest/utilities/test_gdalwarp_lib.py +++ b/autotest/utilities/test_gdalwarp_lib.py @@ -2726,7 +2726,7 @@ def test_gdalwarp_lib_to_cog(tmp_vsimem): ds = gdal.Warp( tmpfilename, "../gcore/data/byte.tif", - options="-f COG -t_srs EPSG:3857 -ts 20 20", + options="-f COG -t_srs EPSG:3857 -ts 20 20 -r near", ) assert ds.RasterCount == 1 assert ds.GetRasterBand(1).Checksum() == 4672 diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index b29a5ee33b0b..1f1a6999e1e4 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -2464,3 +2464,178 @@ def my_handler(errorClass, errno, msg): f = out_lyr.GetNextFeature() assert f["shortname"] == "foo" assert f["too_long_f"] == "bar" + + +############################################################################### + + +@pytest.mark.require_driver("GPKG") +def test_ogr2ogr_lib_coordinate_precision(tmp_vsimem): + + # Source layer without SRS + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + src_ds.CreateLayer("test") + + out_filename = str(tmp_vsimem / "test_ogr2ogr_lib_coordinate_precision.gpkg") + + with pytest.raises( + Exception, + match="Invalid value for -xyRes", + ): + gdal.VectorTranslate(out_filename, src_ds, xyRes="invalid") + + with pytest.raises( + Exception, + match="Invalid value for -xyRes", + ): + gdal.VectorTranslate(out_filename, src_ds, xyRes="1 invalid") + + with pytest.raises( + Exception, + match="Unit suffix for -xyRes cannot be used with an unknown destination SRS", + ): + gdal.VectorTranslate(out_filename, src_ds, xyRes="1e-2 m") + + with pytest.raises( + Exception, + match="Invalid value for -zRes", + ): + gdal.VectorTranslate(out_filename, src_ds, zRes="invalid") + + with pytest.raises( + Exception, + match="Invalid value for -zRes", + ): + gdal.VectorTranslate(out_filename, src_ds, zRes="1 invalid") + + with pytest.raises( + Exception, + match="Unit suffix for -zRes cannot be used with an unknown destination SRS", + ): + gdal.VectorTranslate(out_filename, src_ds, zRes="1e-2 m") + + with pytest.raises( + Exception, + match="Invalid value for -mRes", + ): + gdal.VectorTranslate(out_filename, src_ds, mRes="invalid") + + with pytest.raises( + Exception, + match="Invalid value for -mRes", + ): + gdal.VectorTranslate(out_filename, src_ds, mRes="1 invalid") + + gdal.VectorTranslate(out_filename, src_ds, xyRes=1e-2, zRes=1e-3, mRes=1e-4) + ds = ogr.Open(out_filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-2 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-4 + ds.Close() + + out_filename2 = str( + tmp_vsimem / "test_ogr2ogr_lib_coordinate_precision2.gpkg", + ) + gdal.VectorTranslate(out_filename2, out_filename) + ds = ogr.Open(out_filename2) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-2 + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-4 + ds.Close() + + gdal.VectorTranslate(out_filename2, out_filename, setCoordPrecision=False) + ds = ogr.Open(out_filename2) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 0 + ds.Close() + + # Source layer with a geographic SRS + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + src_ds.CreateLayer("test", srs=srs) + + gdal.VectorTranslate(out_filename, src_ds, xyRes="1e-2 m", zRes="1e-3 m", mRes=1e-4) + ds = ogr.Open(out_filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(8.98315e-08) + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-4 + ds.Close() + + gdal.VectorTranslate(out_filename, src_ds, xyRes="10 mm", zRes="1 mm", mRes=1e-4) + ds = ogr.Open(out_filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(8.98315e-08) + assert prec.GetZResolution() == 1e-3 + assert prec.GetMResolution() == 1e-4 + ds.Close() + + gdal.VectorTranslate(out_filename, src_ds, xyRes="1e-7 deg") + ds = ogr.Open(out_filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-7 + assert prec.GetZResolution() == 0 + assert prec.GetMResolution() == 0 + ds.Close() + + # Source layer with a projected SRS + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + srs = osr.SpatialReference() + srs.ImportFromEPSG(32631) + src_ds.CreateLayer("test", srs=srs) + + gdal.VectorTranslate(out_filename, src_ds, xyRes="1e-7 deg") + ds = ogr.Open(out_filename) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(0.0111319) + assert prec.GetZResolution() == 0 + assert prec.GetMResolution() == 0 + ds.Close() + + # Test conversion of coordinate precision while reprojecting + gdal.VectorTranslate(out_filename2, out_filename, dstSRS="EPSG:4326") + ds = ogr.Open(out_filename2) + lyr = ds.GetLayer(0) + geom_fld = lyr.GetLayerDefn().GetGeomFieldDefn(0) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == pytest.approx(1e-7) + assert prec.GetZResolution() == 0 + assert prec.GetMResolution() == 0 + ds.Close() + + +############################################################################### + + +def test_ogr2ogr_lib_coordinate_precision_with_geom(): + + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + src_lyr = src_ds.CreateLayer("test") + f = ogr.Feature(src_lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("LINESTRING (1 1,9 9)")) + src_lyr.CreateFeature(f) + + out_ds = gdal.VectorTranslate("", src_ds, format="Memory", xyRes=10) + out_lyr = out_ds.GetLayer(0) + f = out_lyr.GetNextFeature() + if ogr.GetGEOSVersionMajor() > 0: + assert f.GetGeometryRef().ExportToWkt() == "LINESTRING (0 0,10 10)" + else: + assert f.GetGeometryRef().ExportToWkt() == "LINESTRING (1 1,9 9)" diff --git a/autotest/utilities/test_ogrinfo.py b/autotest/utilities/test_ogrinfo.py index 4b69eec5531a..30bc0fbfcf04 100755 --- a/autotest/utilities/test_ogrinfo.py +++ b/autotest/utilities/test_ogrinfo.py @@ -29,7 +29,9 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import os import pathlib +import stat import gdaltest import ogrtest @@ -749,3 +751,29 @@ def test_ogrinfo_if_ko(ogrinfo_path): ogrinfo_path + " -if GeoJSON ../ogr/data/gpkg/2d_envelope.gpkg" ) assert "not recognized as being in a supported file format" in err + + +############################################################################### +def test_ogrinfo_access_to_file_without_permission(ogrinfo_path, tmp_path): + + tmpfilename = str(tmp_path / "test.bin") + with open(tmpfilename, "wb") as f: + f.write(b"\x00" * 1024) + os.chmod(tmpfilename, 0) + + # Test that file is not accessible + try: + f = open(tmpfilename, "rb") + f.close() + pytest.skip("could not set non accessible permission") + except IOError: + pass + + _, err = gdaltest.runexternal_out_and_err( + ogrinfo_path + " " + tmpfilename, + encoding="UTF-8", + ) + lines = list(filter(lambda x: len(x) > 0, err.split("\n"))) + assert (len(lines)) == 3 + + os.chmod(tmpfilename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) diff --git a/autotest/utilities/test_ogrinfo_lib.py b/autotest/utilities/test_ogrinfo_lib.py index 0cdcbc214fe3..632cd76edbf1 100755 --- a/autotest/utilities/test_ogrinfo_lib.py +++ b/autotest/utilities/test_ogrinfo_lib.py @@ -27,6 +27,7 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import json import pathlib import gdaltest @@ -942,3 +943,38 @@ def test_ogrinfo_lib_extent3D(): 1.0, 1.0, ] + + +############################################################################### +# Test geometry coordinate precision support + + +@pytest.mark.require_driver("GeoJSON") +def test_ogrinfo_lib_json_features_resolution(): + + content = json.dumps( + { + "type": "FeatureCollection", + "xy_coordinate_resolution": 1e-1, + "z_coordinate_resolution": 1e-2, + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [1.23456789, 1.23456789, 1.23456789], + }, + "properties": None, + } + ], + } + ) + + j = gdal.VectorInfo(content, format="json", dumpFeatures=True) + assert j["layers"][0]["features"][0]["geometry"] == { + "type": "Point", + "coordinates": [1.2, 1.2, 1.23], + } + + s = gdal.VectorInfo(content, dumpFeatures=True) + assert "POINT Z (1.2 1.2 1.23)" in s diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 720488874e09..02e24104d205 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -734,14 +734,25 @@ gdal_check_package(MONGOCXX "Enable MongoDBV3 driver" CAN_DISABLE) define_find_package2(HEIF libheif/heif.h heif PKGCONFIG_NAME libheif) gdal_check_package(HEIF "HEIF >= 1.1" CAN_DISABLE) -# OpenJPEG's cmake-CONFIG is broken, so call module explicitly -find_package(OpenJPEG MODULE) +# OpenJPEG's cmake-CONFIG is broken with older OpenJPEG releases, so call module explicitly +set(GDAL_FIND_PACKAGE_OPENJPEG_METHOD "MODULE" CACHE STRING "Method to use for find_package(OpenJPEG): CONFIG, MODULE or empty string") +mark_as_advanced(GDAL_FIND_PACKAGE_OPENJPEG_METHOD) +if(NOT GDAL_FIND_PACKAGE_OPENJPEG_METHOD STREQUAL "") + find_package(OpenJPEG ${GDAL_FIND_PACKAGE_OPENJPEG_METHOD}) +else() + find_package(OpenJPEG) +endif() if (OPENJPEG_VERSION_STRING AND OPENJPEG_VERSION_STRING VERSION_LESS "2.3.1") message(WARNING "Ignoring OpenJPEG because it is at version ${OPENJPEG_VERSION_STRING}, whereas the minimum version required is 2.3.1") set(HAVE_OPENJPEG OFF) set(OPENJPEG_FOUND OFF) endif() if (GDAL_USE_OPENJPEG) + if (TARGET openjp2 AND NOT TARGET OPENJPEG::OpenJPEG) + # Triggered when using CONFIG dection + add_library(OPENJPEG::OpenJPEG INTERFACE IMPORTED) + set_target_properties(OPENJPEG::OpenJPEG PROPERTIES INTERFACE_LINK_LIBRARIES "openjp2") + endif() if (NOT OPENJPEG_FOUND) message(FATAL_ERROR "Configured to use GDAL_USE_OPENJPEG, but not found") endif () diff --git a/doc/source/development/rfc/index.rst b/doc/source/development/rfc/index.rst index 3d713b335ce2..f0763fe8d0ca 100644 --- a/doc/source/development/rfc/index.rst +++ b/doc/source/development/rfc/index.rst @@ -105,3 +105,4 @@ RFC list rfc96_deferred_plugin_loading rfc97_feature_and_fielddefn_sealing rfc98_build_requirements_gdal_3_9 + rfc99_geometry_coordinate_precision diff --git a/doc/source/development/rfc/rfc99_geometry_coordinate_precision.rst b/doc/source/development/rfc/rfc99_geometry_coordinate_precision.rst new file mode 100644 index 000000000000..d1fef256294c --- /dev/null +++ b/doc/source/development/rfc/rfc99_geometry_coordinate_precision.rst @@ -0,0 +1,740 @@ +.. _rfc-99: + +=================================================================== +RFC 99: Geometry coordinate precision +=================================================================== + +============== ============================================= +Author: Even Rouault +Contact: even.rouault @ spatialys.com +Started: 2024-Feb-24 +Status: Adopted, implemented in GDAL 3.9 +Target: GDAL 3.9 +============== ============================================= + +Summary +------- + +This RFC aims at introducing optional metadata to specify the coordinate +precision of geometries, to be able to round appropriately coordinates and limit +the number of decimals when exporting to text-based formats or nullify +least-significant bits for binary formats. That metadata will be stored into +and read from formats that can support it. + +Motivation +---------- + +The aim is multiple: + +- reducing file size. For text-based formats, rounding and truncating to the + specified precision directly reduce file size. For binary formats, using that + information to zero least-significant bits can increase the potential when + applying afterwards lossless compression methods (typically zipping a file). + +- presenting the user with hints on the precision of the data he/she accesses. + This can be used by user interfaces build on top of GDAL to display geometry + coordinates with an appropriate number of decimals. + +- a few drivers (GeoJSON, JSONFG, OpenFileGDB) have layer creation options to + specify coordinate precision, but there is currently no driver agnostic way + of specifying it. + +For example, currently, when exporting a file to GML, 15 significant decimal +digits (ie the total of digits for the integral and decimal parts) are used, +which corresponds to a 0.1 micrometer precision for geography coordinates. +The same holds for regular GeoJSON export, unless the RFC 7946 variant is +selected, in which case only 7 decimal digits after decimal separators are used. +However this is a layer creation option, which means that it is no longer +remembered when data is edited/appended to an existing layer +(see https://github.com/qgis/QGIS/issues/56335). + +For binary formats using IEEE-754 double-precision encoding of real numbers, +one can show that at least the last 16 least-significants bits (ie the last +2 bytes of 8) of a coordinate can be set to zero while keeping a 1 mm precision +(which corresponds to about 8.9e-9 degree). +On a test dataset, setting a 1 mm precision reduced the size of the .zip of the +.gpkg file from 766 MB to 667 MB (13% size decrease). +If only a 1 meter precision is wished, this increases to 26 useless least-significant bits. + +.. code-block:: python + + >>> import math + >>> earth_radius_in_meter = 6378137 + >>> one_degree_in_meter = earth_radius_in_meter * math.pi / 180.0 + >>> mm_prec_in_degree = 1e-3 / one_degree_in_meter + >>> print(mm_prec_in_degree) + 8.983152841195215e-09 + >>> max_integer_part = 180 # for coordinates in range [-180,180] + >>> significant_bits_needed = math.ceil(math.log2(max_integer_part)) + math.ceil(math.log2(1 / mm_prec_in_degree)) + 1 + >>> mantissa_bit_size = 52 + >>> unused_bits = mantissa_bit_size - significant_bits_needed + >>> print(unused_bits) + 16 + + +C and C++ API extensions and changes +------------------------------------ + +OGRGeomCoordinatePrecision class +++++++++++++++++++++++++++++++++ + +A new ``OGRGeomCoordinatePrecision`` class is introduced in the +``ogr_geomcoordinateprecision.h`` file: + +.. code-block:: c++ + + /** Geometry coordinate precision. + * + * This may affect how many decimal digits (for text-based output) or bits + * (for binary encodings) are used to encode geometries. + * + * It is important to note that the coordinate precision has no direct + * relationship with the "physical" accuracy. It is generally advised that + * the resolution (precision) be at least 10 times smaller than the accuracy. + */ + struct CPL_DLL OGRGeomCoordinatePrecision + { + static constexpr double UNKNOWN = 0; + + /** Resolution for the coordinate precision of the X and Y coordinates. + * Expressed in the units of the X and Y axis of the SRS. + * For example for a projected SRS with X,Y axis unit in meter, a value + * of 1e-3 corresponds to a 1 mm precision. + * For a geographic SRS (on Earth) with axis unit in degree, a value + * of 8.9e-9 (degree) also corresponds to a 1 mm precision. + * Set to 0 if unknown. + */ + double dfXYResolution = UNKNOWN; + + /** Resolution for the coordinate precision of the Z coordinate. + * Expressed in the units of the Z axis of the SRS. + * Set to 0 if unknown. + */ + double dfZResolution = UNKNOWN; + + /** Resolution for the coordinate precision of the M coordinate. + * Set to 0 if unknown. + */ + double dfMResolution = UNKNOWN; + + /** Map from a format name to a list of format specific options. + * + * This can be for example used to store FileGeodatabase + * xytolerance, xorigin, yorigin, etc. coordinate precision grids + * options, which can be help to maximize preservation of coordinates in + * FileGDB -> FileGDB conversion processes. + */ + std::map oFormatSpecificOptions{}; + + /** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z ordinates, the precision should be expressed in meter, + * e.g 1e-3 for millimetric precision. + * + * Resolution should be stricty positive, or set to + * OGRGeomCoordinatePrecision::UNKNOWN when unknown. + * + * @param poSRS Spatial reference system, used for metric to SRS unit conversion + * (must not be null) + * @param dfXYMeterResolution Resolution for for X and Y coordinates, in meter. + * @param dfZMeterResolution Resolution for for Z coordinates, in meter. + * @param dfMResolutionIn Resolution for for M coordinates. + */ + void SetFromMeter(const OGRSpatialReference *poSRS, + double dfXYMeterResolution, + double dfZMeterResolution, double dfMResolution); + + /** + * \brief Return equivalent coordinate precision setting taking into account + * a change of SRS. + * + * @param poSRSSrc Spatial reference system of the current instance + * (if null, meter unit is assumed) + * @param poSRSDst Spatial reference system of the returned instance + * (if null, meter unit is assumed) + * @return a new OGRGeomCoordinatePrecision instance, with a poSRSDst SRS. + */ + OGRGeomCoordinatePrecision + ConvertToOtherSRS(const OGRSpatialReference *poSRSSrc, + const OGRSpatialReference *poSRSDst) const; + + /** + * \brief Return the number of decimal digits after the decimal point to + * get the specified resolution. + */ + static int ResolutionToPrecision(double dfResolution); + } + + +Corresponding additions at the C API level: + +.. code-block:: c + + /** Value for a unknown coordinate precision. */ + #define OGR_GEOM_COORD_PRECISION_UNKNOWN 0 + + /** Opaque type for OGRGeomCoordinatePrecision */ + typedef struct OGRGeomCoordinatePrecision *OGRGeomCoordinatePrecisionH; + + OGRGeomCoordinatePrecisionH CPL_DLL OGRGeomCoordinatePrecisionCreate(void); + void CPL_DLL OGRGeomCoordinatePrecisionDestroy(OGRGeomCoordinatePrecisionH); + double CPL_DLL + OGRGeomCoordinatePrecisionGetXYResolution(OGRGeomCoordinatePrecisionH); + double CPL_DLL + OGRGeomCoordinatePrecisionGetZResolution(OGRGeomCoordinatePrecisionH); + double CPL_DLL + OGRGeomCoordinatePrecisionGetMResolution(OGRGeomCoordinatePrecisionH); + char CPL_DLL ** + OGRGeomCoordinatePrecisionGetFormats(OGRGeomCoordinatePrecisionH); + CSLConstList CPL_DLL OGRGeomCoordinatePrecisionGetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH, const char *pszFormatName); + void CPL_DLL OGRGeomCoordinatePrecisionSet(OGRGeomCoordinatePrecisionH, + double dfXYResolution, + double dfZResolution, + double dfMResolution); + void CPL_DLL OGRGeomCoordinatePrecisionSetFromMeter(OGRGeomCoordinatePrecisionH, + OGRSpatialReferenceH hSRS, + double dfXYMeterResolution, + double dfZMeterResolution, + double dfMResolution); + void CPL_DLL OGRGeomCoordinatePrecisionSetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH, const char *pszFormatName, + CSLConstList papszOptions); + +OGRGeomFieldDefn class +++++++++++++++++++++++ + +The existing :cpp:class:`OGRGeomFieldDefn` is extended with a new +OGRGeomCoordinatePrecision member, and associated getter and setter methods. + +.. code-block:: c++ + + class OGRGeomFieldDefn + { + public: + const OGRGeomCoordinatePrecision& GetCoordinatePrecision() const; + + void SetCoordinatePrecision(const OGRGeomCoordinatePrecision& prec); + + private: + OGRGeomCoordinatePrecision m_oCoordPrecision{}; + }; + +New corresponding C API: + +.. code-block:: c + + OGRGeomCoordinatePrecisionH + CPL_DLL OGR_GFld_GetCoordinatePrecision(OGRGeomFieldDefnH); + void CPL_DLL OGR_GFld_SetCoordinatePrecision(OGRGeomFieldDefnH, + OGRGeomCoordinatePrecisionH); + +OGRGeometry class ++++++++++++++++++ + +The existing :cpp:class:`OGRGeometry` is extended with the following new +``SetPrecision`` method which is a wrapper of the ``GEOSGeom_setPrecision_r`` function: + +.. code-block:: c++ + + /** Set the geometry's precision, rounding all its coordinates to the precision + * grid, and making sure the geometry is still valid. + * + * This is a stronger version of roundCoordinates(). + * + * Note that at time of writing GEOS does no supported curve geometries. So + * currently if this function is called on such a geometry, OGR will first call + * getLinearGeometry() on the input and getCurveGeometry() on the output, but + * that it is unlikely to yield to the expected result. + * + * @param dfGridSize size of the precision grid, or 0 for FLOATING + * precision. + * @param nFlags The bitwise OR of zero, one or several of OGR_GEOS_PREC_NO_TOPO + * and OGR_GEOS_PREC_KEEP_COLLAPSED + * + * @return a new geometry or NULL if an error occurs. + */ + + OGRGeometry *OGRGeometry::SetPrecision(double dfGridSize, int nFlags) const; + +New corresponding C API: + +.. code-block:: c + + /** This option causes OGR_G_SetPrecision() + * to not attempt at preserving the topology */ + #define OGR_GEOS_PREC_NO_TOPO (1 << 0) + + /** This option causes OGR_G_SetPrecision() + * to retain collapsed elements */ + #define OGR_GEOS_PREC_KEEP_COLLAPSED (1 << 1) + + OGRGeometryH OGR_G_SetPrecision(OGRGeometryH, double dfGridSize, int nFlags); + + +Note that this method is not automatically run by the writing side of drivers, +which assume that the passed geometries are valid once rounded with the specified +coordinate precision metadata. + +However it is invoked when the `-xyRes` switch of ogr2ogr is passed. + +It may also be triggered by setting the new ``OGR_APPLY_GEOM_SET_PRECISION`` +configuration option to ``YES``, for geometries passed to +:cpp:func:`OGRLayer::CreateFeature` and :cpp:func:`OGRLayer::SetFeature` before +they are passed to the driver. + + +A closely related ``roundCoordinates`` method is also introduced: + +.. code-block:: c++ + + /** Round coordinates of the geometry to the specified precision. + * + * Note that this is not the same as OGRGeometry::SetPrecision(). The later + * will return valid geometries, whereas roundCoordinates() does not make + * such guarantee and may return geometries with invalidities, if they are + * not compatible of the specified precision. roundCoordinates() supports + * curve geometries, whereas SetPrecision() does not currently. + * + * One use case for roundCoordinates() is to undo the effect of + * quantizeCoordinates(). + * + * @param sPrecision Contains the precision requirements. + * @since GDAL 3.9 + */ + void roundCoordinates(const OGRGeomCoordinatePrecision &sPrecision); + + +WKB export +++++++++++ + +WKB export methods will be modified in a similar way as in the prototype +https://github.com/OSGeo/gdal/pull/6974 to nullify least significant bits from +the precision specifications. + +More specifically the following 2 new classes are added: + +.. code-block:: c++ + + /** Geometry coordinate precision for a binary representation. + */ + struct CPL_DLL OGRGeomCoordinateBinaryPrecision + { + int nXYBitPrecision = + INT_MIN; /**< Number of bits needed to achieved XY precision. Typically + computed with SetFromResolution() */ + int nZBitPrecision = + INT_MIN; /**< Number of bits needed to achieved Z precision. Typically + computed with SetFromResolution() */ + int nMBitPrecision = + INT_MIN; /**< Number of bits needed to achieved M precision. Typically + computed with SetFromResolution() */ + + void SetFrom(const OGRGeomCoordinatePrecision &); + }; + + /** WKB export options. + */ + struct CPL_DLL OGRwkbExportOptions + { + OGRwkbByteOrder eByteOrder = wkbNDR; /**< Byte order */ + OGRwkbVariant eWkbVariant = wkbVariantOldOgc; /**< WKB variant. */ + OGRGeomCoordinateBinaryPrecision sPrecision{}; /**< Binary precision. */ + }; + +And the C++ OGRGeometry ``exportToWkb`` virtual method is modified to have the +following prototype: + +.. code-block:: c++ + + virtual OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const = 0; + + +The existing method with signature ``OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, OGRwkbVariant = wkbVariantOldOgc) const`` +is kept and call the new virtual method. + +New corresponding C API: + +.. code-block:: c + + + /** Opaque type for WKB export options */ + typedef struct OGRwkbExportOptions OGRwkbExportOptions; + + OGRwkbExportOptions CPL_DLL *OGRwkbExportOptionsCreate(void); + void CPL_DLL OGRwkbExportOptionsDestroy(OGRwkbExportOptions *); + void CPL_DLL OGRwkbExportOptionsSetByteOrder(OGRwkbExportOptions *, + OGRwkbByteOrder); + void CPL_DLL OGRwkbExportOptionsSetVariant(OGRwkbExportOptions *, + OGRwkbVariant); + void CPL_DLL OGRwkbExportOptionsSetPrecision(OGRwkbExportOptions *, + OGRGeomCoordinatePrecisionH); + OGRErr CPL_DLL OGR_G_ExportToWkbEx(OGRGeometryH, unsigned char *, + const OGRwkbExportOptions *); + +OGRLayer CreateLayer()/ICreateLayer() changes ++++++++++++++++++++++++++++++++++++++++++++++ + +The signature of the current :cpp:func:`OGRLayer::ICreateLayer()` protected +method (implemented by drivers) will be changed from + +.. code-block:: c++ + + virtual OGRLayer *ICreateLayer( + const char *pszName, const OGRSpatialReference *poSpatialRef = nullptr, + OGRwkbGeometryType eGType = wkbUnknown, char **papszOptions = nullptr); + +to + +.. code-block:: c++ + + virtual OGRLayer *ICreateLayer( + const char *pszName, + const OGRGeomFieldDefn* poGeomFieldDefn = nullptr, + CSLConstList papszOptions = nullptr); + +This will require changes to out-of-tree drivers that implement it. + +A corresponding non-virtual public method will also be added: + +.. code-block:: c++ + + OGRLayer *CreateLayer( + const char *pszName, + const OGRGeomFieldDefn* poGeomFieldDefn, + CSLConstList papszOptions = nullptr); + +And the current CreateLayer() signature will be adapted to call the modified +ICreateLayer(). + +And for the C API: + +.. code-block:: c + + OGRLayerH CPL_DLL GDALDatasetCreateLayerFromGeomFieldDefn( + GDALDatasetH, const char *, + OGRGeomFieldDefnH hGeomFieldDefn, + CSLConstList); + + +A new ``GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION`` driver capability will be added +to advertise that a driver honours OGRGeomFieldDefn::GetCoordinatePrecision() +when writing geometries. This may be useul for user interfaces that could offer +an option to the user to specify the coordinate precision. Note however that +the driver may not be able to store that precision in the dataset metadata. + +There will be *no* provision to modify the coordinate precision of an +existing layer geometry field with :cpp:func:`OGRLayer::AlterFieldDefn`. + +Driver changes +-------------- + +The following drivers will be modified to honour ``GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION`` + +GeoJSON ++++++++ + +The driver will compute the number of decimal digits after the decimal point +to write as ``ceil(1. / log10(resolution))`` + +The driver will be able to store and retrieve the coordinate precision metadata +in the files it generates, by adding ``xy_coordinate_resolution`` and +``z_coordinate_resolution`` members at the FeatureCollection level. + +The existing COORDINATE_PRECISION layer creation option, if specified, will +take precedence over the settings coming from OGRGeomFieldDefn::GetCoordinatePrecision(). + +GeoJSONSeq +++++++++++ + +The driver will compute the number of decimal digits after the decimal point +to write as ``ceil(1. / log10(resolution))`` + +It will *not* be able to store it in its metadata. + +JSONFG +++++++ + +Similar to GeoJSON. One subtelty is that this driver may write both the "place" +geometry (generally in a non-WGS84 CRS) and the GeoJSON RFC7946 WGS84 "geometry". + +The OGRGeomFieldDefn::GetCoordinatePrecision() will qualify the "place" geometry. +The coordinate precision of the WGS84 "geometry" will be derived from the one +of the "place" geometry with appropriate geographic/projected CRS and axis unit +changes. + +The coordinate precision metadata of the "place" member will be stored in +``xy_coordinate_resolution_place`` and ``z_coordinate_resolution_place`` +members at the FeatureCollection level. + +For the "geometry" member, the same ``xy_coordinate_resolution`` and +``z_coordinate_resolution`` members as the GeoJSON driver will be used. + +The existing COORDINATE_PRECISION_PLACE or COORDINATE_PRECISION_GEOMETRY layer +creation option, if specified, will take precedence over the settings coming +from OGRGeomFieldDefn::GetCoordinatePrecision(). + +GML ++++ + +The driver will compute the number of decimal digits after the decimal point +to write as ``ceil(1. / log10(resolution))`` + +The driver will be able to store the coordinate precision metadata in the XML +schema it generates by adding a ``xs:annotation/xs:appinfo`` element in the +declaration of the geometry property, and with ``ogr:xy_coordinate_resolution`` +and ``ogr:z_coordinate_resolution`` sub-elements. +This should hopefully be ignored by readers that don't recognize +that metadata (this will be the case of GDAL < 3.9) + +.. code-block:: xml + + + + + 8.9e-9 + 1e-3 + + + + +CSV ++++ + +The driver will compute the number of decimal digits after the decimal point +to write as ``ceil(1. / log10(resolution))`` + +It will *not* be able to store it in its metadata. The possibility of storing +the coordinate metadata in the .csvt side-car file has been considered, but it +would not be backwards-compatible. + +GeoPackage +++++++++++ + +The GeoPackage driver will support reading and writing the geometry coordinate +precision. By default, the geometry coordinate precision +will only noted in metadata, and does not cause geometries that are written to +be modified to comply with this precision. + +Several settings may be combined to apply further processing: + +* the ``OGR_APPLY_GEOM_SET_PRECISION`` configuration option as described + previously. + +* if the new ``DISCARD_COORD_LSB`` layer creation option is set to YES, the + less-significant bits of the WKB geometry encoding which are not relevant for + the requested precision are set to zero. This can improve further lossless + compression stages, for example when putting a GeoPackage in an archive. + Note however that when reading back such geometries and displaying them + to the maximum precision, they will not "exactly" match the original + OGRGeomCoordinatePrecision settings. However, they will round + back to it. + The value of the ``DISCARD_COORD_LSB`` layer creation option is written in + the dataset metadata, and will be re-used for later edition sessions. + +* if the new ``UNDO_DISCARD_COORD_LSB_ON_READING`` layer creation option is set to + YES (only makes sense if the ``DISCARD_COORD_LSB`` layer creation option is + also set to YES), when *reading* back geometries from a dataset, the + ``OGRGeometry::roundCoordinates`` method will be applied so that + the geometry coordinates exactly match the original specified coordinate + precision. That option will only be honored by GDAL 3.9 or later. + + +Implementation details: the coordinate precision is stored in a record in each +of the ``gpkg_metadata`` and ``gpkg_metadata_reference`` table, with the +following additional constraints on top of the ones imposed by the GeoPackage +specification: + +- gpkg_metadata.md_standard_uri = 'http://gdal.org' +- gpkg_metadata.mime_type = 'text/xml' +- gpkg_metadata.metadata = '' +- gpkg_metadata_reference.reference_scope = 'column' +- gpkg_metadata_reference.table_name = '{table_name}' +- gpkg_metadata_reference.column_name = '{geometry_column_name}' + +Note that the xy_resolution, z_resolution or m_resolution attributes of the +XML CoordinatePrecision element are optional. Their numeric value is expressed +in the units of the SRS for xy_resolution and z_resolution. + +.. code-block:: sql + + INSERT INTO gpkg_metadata VALUES(1,'dataset','http://gdal.org','text/xml', + ''); + INSERT INTO gpkg_metadata_reference VALUES('column','poly','geom',NULL,'2023-10-22T21:13:43.282Z',1,NULL); + +OpenFileGDB ++++++++++++ + +OGRGeomCoordinatePrecision::dfXYResolution (resp. dfZResolution, dfMResolution) +directly map to 1. / xyscale (resp. 1 / zscale, 1 / mscale) in the declaration +of the coordinate grid precision options of the FileGeodatabase format +(cf https://help.arcgis.com/en/sdk/10.0/java_ao_adf/conceptualhelp/engine/index.html#//00010000037m000000). + +Consequently the OpenFileGDB driver can be modified in reading and writing to +fully honour OGRGeomCoordinatePrecision. + +The driver will also get and set other coordinate grid precision options, such +as the origin and tolerance, values in the ``FileGeodatabase`` key of the +``OGRGeomCoordinatePrecision::oFormatSpecificOptions`` member. + +The existing ``XYSCALE``, ``ZSCALE`` and ``MSCALE`` layer creation options, +if specified, will take precedence over the settings coming from +OGRGeomFieldDefn::GetCoordinatePrecision(). + +FileGDB ++++++++ + +Modified to have exactly the same behavior as OpenFileGDB. + +OGR VRT ++++++++ + +The driver will read the geometry coordinate precision from the source geometry +field, or possibly overridden with the following elements in the XML VRT: + +.. code-block:: xml + + + {xy_resolution} + {z_resolution} + {m_resolution} + + +Utilities +--------- + +ogrinfo ++++++++ + +ogrinfo will be modified to honour OGRGeomCoordinatePrecision when outputting +WKT geometries (or GeoJSON geometries for the -json output) + +ogr2ogr ++++++++ + +ogr2ogr will forward by default the OGRGeomCoordinatePrecision of the input +layer to the output layer, but of course it will only have effects for drivers +honouring ``GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION``. + +When reprojection occurs, the coordinate precision will be adjusted to take into +account geographic vs projected CRS changes and unit changes. + +The following options will be added: + +- ``-xyRes ``: XY coordinate resolution. Nominally in the unit of the X and + Y SRS axis. + Appending a ``m``, ``mm`` or ``deg`` suffix will be also supported. + A warning will be emitted if the user specifies this option when creating a + new layer for a driver that does not advertise + ``GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION``. + +- ``-zRes ``: Z coordinate resolution. Nominally in the unit of the Z SRS + axis. Appending a ``m`` or ``mm`` suffix will be also supported. + +- ``-mRes ``: M coordinate resolution. + +- ``-unsetCoordPrecision``: to disable automatic propagation of the input + coordinate precision to the output. + +Out of scope +------------ + +While there is an obvious logical connection with GEOS' PrecisionModel +(https://libgeos.org/doxygen/classgeos_1_1geom_1_1PrecisionModel.html), +this RFC does not tie the introduced OGR coordinate precision metadata with it. +Tying both would require either adding a reference to a +OGRGeomCoordinatePrecision as a member of the OGRGeometry class (which would +have some extra RAM usage implications), or as a parameter in OGRGeometry GEOS +related methods. + +Quantization of raster pixel values (e.g. the ``DISCARD_LSB`` creation option +of the GeoTIFF driver) is also slightly connected. + +SWIG bindings +------------- + +The new C functions are bound to SWIG. + +.. code-block:: + + class ogr.GeomCoordinatePrecision: + + void Set(double xyResolution, double zResolution, double mResolution); + void SetFromMeter(osr.SpatialReference srs, double xyMeterResolution, double zMeterResolution, double mResolution); + double GetXYResolution(); + double GetZResolution(); + double GetMResolution(); + char **GetFormats(); // as a list + char ** GetFormatSpecificOptions(const char* formatName); // as a dictionary + void SetFormatSpecificOptions(const char* formatName, char **formatSpecificOptions) // formatSpecificOptions as a dictionary + + ogr.GeomCoordinatePrecision ogr.CreateGeomCoordinatePrecision(); + + class ogr.GeomFieldDefn: + ogr.GeomCoordinatePrecision GetCoordinatePrecision(); + void SetCoordinatePrecision(ogr.GeomCoordinatePrecision coordPrec); + + class gdal.Dataset: + Layer CreateLayerFromGeomFieldDefn(const char* name, ogr.GeomFieldDefn geom_field, char** options=0); + +Testing +------- + +Tests will be added for the new API and the modified drivers. + +Backward compatibility +---------------------- + +The C and C++ API are extended. + +The change of the ICreateLayer() virtual method is an ABI change, and will +require source code changes to out-of-tree drivers implementing it. + +MIGRATION_GUIDE.TXT will mention that and point to this RFC. + +Design discussion +----------------- + +This paragraph discusses a number of thoughts that arose during the writing of +this RFC but were not kept. + +While changing ICreateLayer() prototype, which requires the tedious process of +changing it in more than 50 drivers, I've also considered introducing +an additional OGRLayerCreationContext argument, but I've decided against if, +as it is unclear if it would be that useful. For example, in most ogr2ogr +scenarios, the final extent and feature count is unknown at the start of the +process. + +.. code-block:: c++ + + struct OGRLayerCreationContext + { + OGRExtent3D sExtent; + int64_t nFeatureCount; + } + + OGRLayer *ICreateLayer( + const char *pszName, const OGRGeomFieldDefn* poFieldDefn = nullptr, + const OGRLayerCreationContext& sContext = OGRLayerCreationContext(), + CSLConstList papszOptions = nullptr); + + +Related issues and PRs +---------------------- + +- Candidate implementation: https://github.com/OSGeo/gdal/pull/9378 + +- A prior implementation with a different and reduced scope was done last year + in https://github.com/OSGeo/gdal/pull/6974. + The GeoPackage driver specific creation options of this pull request will no + longer be needed in the implementation of this RFC. + +- Related QGIS issue about coordinate precision not being preserved when appending + to GeoJSON: https://github.com/qgis/QGIS/issues/56335 + +Voting history +-------------- + ++1 from PSC members EvenR and HowardB. +0 from KurtS diff --git a/doc/source/drivers/raster/ceos.rst b/doc/source/drivers/raster/ceos.rst index bdf5f7b32056..8a12386b15bd 100644 --- a/doc/source/drivers/raster/ceos.rst +++ b/doc/source/drivers/raster/ceos.rst @@ -24,6 +24,4 @@ NOTE: Implemented as :source_file:`frmts/ceos/ceosdataset.cpp`. Driver capabilities ------------------- -.. supports_createcopy:: - .. supports_virtualio:: diff --git a/doc/source/drivers/raster/grib.rst b/doc/source/drivers/raster/grib.rst index f214231c7a74..d9a6b2839500 100644 --- a/doc/source/drivers/raster/grib.rst +++ b/doc/source/drivers/raster/grib.rst @@ -86,6 +86,8 @@ Driver capabilities .. supports_georeferencing:: +.. supports_createcopy:: + .. supports_virtualio:: Configuration options 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/lan.rst b/doc/source/drivers/raster/lan.rst index 870c072d4aa5..736846e2516b 100644 --- a/doc/source/drivers/raster/lan.rst +++ b/doc/source/drivers/raster/lan.rst @@ -31,3 +31,5 @@ Driver capabilities .. supports_georeferencing:: .. supports_virtualio:: + +.. supports_create:: diff --git a/doc/source/drivers/raster/pnm.rst b/doc/source/drivers/raster/pnm.rst index 80187ec556ae..68d37cd169b7 100644 --- a/doc/source/drivers/raster/pnm.rst +++ b/doc/source/drivers/raster/pnm.rst @@ -28,6 +28,8 @@ NOTE: Implemented as :source_file:`frmts/raw/pnmdataset.cpp`. Driver capabilities ------------------- +.. supports_create:: + .. supports_createcopy:: .. supports_virtualio:: diff --git a/doc/source/drivers/raster/srtmhgt.rst b/doc/source/drivers/raster/srtmhgt.rst index 40b12a3bac94..54077188a3d3 100644 --- a/doc/source/drivers/raster/srtmhgt.rst +++ b/doc/source/drivers/raster/srtmhgt.rst @@ -35,4 +35,6 @@ Driver capabilities .. supports_georeferencing:: +.. supports_createcopy:: + .. supports_virtualio:: diff --git a/doc/source/drivers/raster/usgsdem.rst b/doc/source/drivers/raster/usgsdem.rst index 7157b2598588..c4be7d54fe06 100644 --- a/doc/source/drivers/raster/usgsdem.rst +++ b/doc/source/drivers/raster/usgsdem.rst @@ -33,7 +33,7 @@ VTP. Driver capabilities ------------------- -.. supports_create:: +.. supports_createcopy:: .. supports_georeferencing:: diff --git a/doc/source/drivers/vector/filegdb.rst b/doc/source/drivers/vector/filegdb.rst index f2273d27e143..d0a298197c3f 100644 --- a/doc/source/drivers/vector/filegdb.rst +++ b/doc/source/drivers/vector/filegdb.rst @@ -254,21 +254,29 @@ available: datasource is closed or when a read operation is done. Bulk load is enabled by default for newly created layers (unless otherwise specified). -Examples --------- - -- Read layer from FileGDB and load into PostGIS: -- Get detailed info for FileGDB: - -Building Notes --------------- - -Read the `GDAL Windows Building example for -Plugins `__. You will -find a similar section in nmake.opt for FileGDB. After you are done, go -to the *$gdal_source_root\ogr\ogrsf_frmts\filegdb* folder and execute: - -``nmake /f makefile.vc plugin nmake /f makefile.vc plugin-install`` +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The driver supports reading and writing the geometry coordinate +precision, using the XYResolution, ZResolution and MResolution members of +the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn`. ``XYScale`` is computed as 1.0 / ``XYResolution`` +(and similarly for the Z and M components). The tolerance setting is computed +as being one tenth of the resolution + +On reading, the coordinate precision grid parameters are returned as format +specific options of :cpp:class:`OGRGeomCoordinatePrecision` with the +``FileGeodatabase`` format key, with the following option key names: +``XYScale``, ``XYTolerance``, ``XYOrigin``, +``ZScale``, ``ZTolerance``, ``ZOrigin``, +``MScale``, ``MTolerance``, ``MOrigin``. On writing, they are also honored +(they will have precedence over XYResolution, ZResolution and MResolution). + +On layer creation, the XORIGIN, YORIGIN, ZORIGIN, MORIGIN, XYSCALE, ZSCALE, +ZORIGIN, XYTOLERANCE, ZTOLERANCE, MTOLERANCE layer creation options will be +used in priority over the settings of :cpp:class:`OGRGeomCoordinatePrecision`. Known Issues ------------ @@ -296,7 +304,7 @@ Known Issues Other limitations ----------------- -- The FileGeodatabase format (and thus the driver) does not support 64-bit integers. +- The driver does not support 64-bit integers. Links ----- diff --git a/doc/source/drivers/vector/geojson.rst b/doc/source/drivers/vector/geojson.rst index b44c62177acf..08fd7d5d9332 100644 --- a/doc/source/drivers/vector/geojson.rst +++ b/doc/source/drivers/vector/geojson.rst @@ -477,6 +477,37 @@ recalled here for what matters to the driver: - The default coordinate precision is 7 decimal digits after decimal separator. +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The GeoJSON driver supports reading and writing the geometry coordinate +precision, using the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn` Those settings are used to round the coordinates +of the geometry of the features to an appropriate decimal precision. + +.. note:: + + The :lco:`COORDINATE_PRECISION` layer creation option has precedence over + the values set on the :cpp:class:`OGRGeomFieldDefn`. + +Implementation details: the coordinate precision is stored as +``xy_coordinate_resolution`` and ``z_coordinate_resolution`` members at the +FeatureCollection level. Their numeric value is expressed in the units of the +SRS. + +Example: + +.. code-block:: JSON + + { + "type": "FeatureCollection", + "xy_coordinate_resolution": 8.9e-6, + "z_coordinate_resolution": 1e-1, + "features": [] + } + Examples -------- diff --git a/doc/source/drivers/vector/geojsonseq.rst b/doc/source/drivers/vector/geojsonseq.rst index 58ed233a8674..a03302e82212 100644 --- a/doc/source/drivers/vector/geojsonseq.rst +++ b/doc/source/drivers/vector/geojsonseq.rst @@ -113,6 +113,25 @@ Layer creation options if they start and end with brackets and braces, even if they do not have their subtype set to JSON. +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +On creation, the GeoJSONSeq driver supports using the geometry coordinate +precision, from th :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn`. Those settings are used to round the coordinates +of the geometry of the features to an appropriate decimal precision. + +.. note:: + + The :lco:`COORDINATE_PRECISION` layer creation option has precedence over + the values set on the :cpp:class:`OGRGeomFieldDefn`. + +The value of those geometry coordinate precision is *not* serialized in the +generated file, hence on reading, the driver will not advertise a geometry +coordinate precision. + See Also -------- @@ -124,4 +143,4 @@ See Also - `GeoJSONL `__: An optimized format for large geographic datasets - `JSON streaming on Wikipedia `__: An - overview over formats for concatenated JSON in a single file + overview over formats for concatenated JSON in a single file diff --git a/doc/source/drivers/vector/gml.rst b/doc/source/drivers/vector/gml.rst index 89eb17d83eb2..027465569922 100644 --- a/doc/source/drivers/vector/gml.rst +++ b/doc/source/drivers/vector/gml.rst @@ -1301,6 +1301,35 @@ will be read as the following: table1.geometry = POINT (3 50) table2.geometry = POINT (2 50) +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The GML driver supports reading and writing the geometry coordinate +precision, using the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn`. Those settings are used to round the coordinates +of the geometry of the features to an appropriate decimal precision. + +Implementation details: the coordinate precision is stored in the XML schema +as ``xs:annotation/xs:appinfo[source="http://ogr.maptools.org/"]/ogr:xy_coordinate_resolution`` +and ``xs:annotation/xs:appinfo[source="http://ogr.maptools.org/"]/ogr:z_coordinate_resolution`` +optional elements in the declaration of the geometry column. +Their numeric value is expressed in the units of the SRS. + +Example: + +.. code-block:: XML + + + + + 8.9e-8 + 1e-3 + + + + Examples -------- diff --git a/doc/source/drivers/vector/gmlas.rst b/doc/source/drivers/vector/gmlas.rst index af9af9c2c7a5..f360c14b140b 100644 --- a/doc/source/drivers/vector/gmlas.rst +++ b/doc/source/drivers/vector/gmlas.rst @@ -26,6 +26,8 @@ Driver capabilities .. supports_georeferencing:: +.. supports_createcopy:: + .. supports_virtualio:: Opening syntax diff --git a/doc/source/drivers/vector/gpkg.rst b/doc/source/drivers/vector/gpkg.rst index 798192145868..a6b67133b513 100644 --- a/doc/source/drivers/vector/gpkg.rst +++ b/doc/source/drivers/vector/gpkg.rst @@ -351,6 +351,20 @@ The following layer creation options are available: geometry column can be NULL. Can be set to NO so that geometry is required. +- .. lco:: DISCARD_COORD_LSB + :choices: YES, NO + :default: NO + :since: 3.9 + + Whether the geometry coordinate precision should be used to set to zero non-significant least-significant bits of geometries. Helps when further compression is used. See :ref:`ogr_gpkg_geometry_coordinate_precision` for more details. + +- .. lco:: UNDO_DISCARD_COORD_LSB_ON_READING + :choices: YES, NO + :default: NO + :since: 3.9 + + Whether to ask GDAL to take into coordinate precision to undo the effects of DISCARD_COORD_LSB. See :ref:`ogr_gpkg_geometry_coordinate_precision` for more details. + - .. lco:: FID :default: fid @@ -632,6 +646,62 @@ Update of an existing file is not supported. Creation involves the creation of a temporary file. Sufficiently large files will be automatically compressed using the SOZip optimization. +.. _ogr_gpkg_geometry_coordinate_precision: + +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The GeoPackage driver supports reading and writing the geometry coordinate +precision, using the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn`. By default, the geometry coordinate precision +is only noted in metadata, and does not cause geometries that are written to +be modified to comply with this precision. + +Several settings may be combined to apply further processing: + +* if the :config:`OGR_APPLY_GEOM_SET_PRECISION` configuration option is set to + ``YES``, the :cpp:func:`OGRGeometry::SetPrecision` method will be applied + when calling the CreateFeature() and SetFeature() method of the driver, to + round X and Y coordinates to the specified precision, and fix potential + geometry invalidities resulting from the rounding. + +* if the ``DISCARD_COORD_LSB`` layer creation option is set to YES, the + less-significant bits of the WKB geometry encoding which are not relevant for + the requested precision are set to zero. This can improve further lossless + compression stages, for example when putting a GeoPackage in an archive. + Note however that when reading back such geometries and displaying them + to the maximum precision, they will not "exactly" match the original + :cpp:class:`OGRGeomCoordinatePrecision` settings. However, they will round + back to it. + The value of the ``DISCARD_COORD_LSB`` layer creation option is written in + the dataset metadata, and will be re-used for later edition sessions. + +* if the ``UNDO_DISCARD_COORD_LSB_ON_READING`` layer creation option is set to + YES (only makes sense if the ``DISCARD_COORD_LSB`` layer creation option is + also set to YES), when *reading* back geometries from a dataset, the + :cpp:func:`OGRGeometry::roundCoordinates` method will be applied so that + the geometry coordinates exactly match the original specified coordinate + precision. That option will only be honored by GDAL 3.9 or later. + + +Implementation details: the coordinate precision is stored in a record in each +of the ``gpkg_metadata`` and ``gpkg_metadata_reference`` table, with the +following additional constraints on top of the ones imposed by the GeoPackage +specification: + +- gpkg_metadata.md_standard_uri = 'http://gdal.org' +- gpkg_metadata.mime_type = 'text/xml' +- gpkg_metadata.metadata = '' +- gpkg_metadata_reference.reference_scope = 'column' +- gpkg_metadata_reference.table_name = '{table_name}' +- gpkg_metadata_reference.column_name = '{geometry_column_name}' + +Note that the xy_resolution, z_resolution or m_resolution attributes of the +XML CoordinatePrecision element are optional. Their numeric value is expressed +in the units of the SRS for xy_resolution and z_resolution. + .. _target_drivers_vector_gpkg_performance_hints: Performance hints diff --git a/doc/source/drivers/vector/jsonfg.rst b/doc/source/drivers/vector/jsonfg.rst index 3f93dfa490ff..1d33b08c1422 100644 --- a/doc/source/drivers/vector/jsonfg.rst +++ b/doc/source/drivers/vector/jsonfg.rst @@ -108,7 +108,7 @@ Open options Dataset creation options ------------------------ -- .. lco:: SINGLE_LAYER +- .. dsco:: SINGLE_LAYER :choices: YES, NO :default: NO @@ -179,6 +179,44 @@ domains. Writing to /dev/stdout or /vsistdout/ is also supported. +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The GeoJSON driver supports reading and writing the geometry coordinate +precision, using the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn` Those settings are used to round the coordinates +of the geometry of the features to an appropriate decimal precision. + +.. note:: + + The :lco:`COORDINATE_PRECISION_GEOMETRY` or :lco:`COORDINATE_PRECISION_PLACE` layer + creation option has precedence over the values set on the :cpp:class:`OGRGeomFieldDefn`. + +Implementation details: the coordinate precision is stored as +``xy_coordinate_resolution_place`` and ``z_coordinate_resolution_place`` members at the +FeatureCollection level, for the geometries written in the ``place`` element. +Their numeric value is expressed in the units of the SRS. + +For the ``geometry`` standard GeoJSON element, the coordinate precision is stored as +``xy_coordinate_resolution`` and ``z_coordinate_resolution`` members, and their +numeric value is expressed in the units of the OGC:CRS84 SRS (hence decimal degrees +for ``xy_coordinate_resolution``) + +Example: + +.. code-block:: JSON + + { + "type": "FeatureCollection", + "xy_coordinate_resolution_place": 1.0, + "z_coordinate_resolution_place": 1.0, + "xy_coordinate_resolution": 8.9e-6, + "z_coordinate_resolution": 1e-1, + "features": [] + } + See Also -------- diff --git a/doc/source/drivers/vector/mongodbv3.rst b/doc/source/drivers/vector/mongodbv3.rst index 1f0ead1314bf..4fd297d15c09 100644 --- a/doc/source/drivers/vector/mongodbv3.rst +++ b/doc/source/drivers/vector/mongodbv3.rst @@ -20,8 +20,6 @@ This driver requires the MongoDB C++ v3.4.0 client library. Driver capabilities ------------------- -.. supports_create:: - .. supports_georeferencing:: .. supports_virtualio:: diff --git a/doc/source/drivers/vector/openfilegdb.rst b/doc/source/drivers/vector/openfilegdb.rst index 93e7f4505c7a..1d4440a34ed8 100644 --- a/doc/source/drivers/vector/openfilegdb.rst +++ b/doc/source/drivers/vector/openfilegdb.rst @@ -281,6 +281,30 @@ Note that this emulation has an unspecified behavior in case of concurrent updates (with different connections in the same or another process). +Geometry coordinate precision +----------------------------- + +.. versionadded:: GDAL 3.9 + +The driver supports reading and writing the geometry coordinate +precision, using the XYResolution, ZResolution and MResolution members of +the :cpp:class:`OGRGeomCoordinatePrecision` settings of the +:cpp:class:`OGRGeomFieldDefn`. ``XYScale`` is computed as 1.0 / ``XYResolution`` +(and similarly for the Z and M components). The tolerance setting is computed +as being one tenth of the resolution + +On reading, the coordinate precision grid parameters are returned as format +specific options of :cpp:class:`OGRGeomCoordinatePrecision` with the +``FileGeodatabase`` format key, with the following option key names: +``XYScale``, ``XYTolerance``, ``XYOrigin``, +``ZScale``, ``ZTolerance``, ``ZOrigin``, +``MScale``, ``MTolerance``, ``MOrigin``. On writing, they are also honored +(they will have precedence over XYResolution, ZResolution and MResolution). + +On layer creation, the XORIGIN, YORIGIN, ZORIGIN, MORIGIN, XYSCALE, ZSCALE, +ZORIGIN, XYTOLERANCE, ZTOLERANCE, MTOLERANCE layer creation options will be +used in priority over the settings of :cpp:class:`OGRGeomCoordinatePrecision`. + Comparison with the FileGDB driver ---------------------------------- diff --git a/doc/source/drivers/vector/pg.rst b/doc/source/drivers/vector/pg.rst index 490e1caa9708..0a9e1c2749c0 100644 --- a/doc/source/drivers/vector/pg.rst +++ b/doc/source/drivers/vector/pg.rst @@ -137,6 +137,19 @@ SQL command with ExecuteSQL() : "SET client_encoding TO encoding_name" where encoding_name is LATIN1, etc. Errors can be caught by enclosing this command with a CPLPushErrorHandler()/CPLPopErrorHandler() pair. +Updating existing tables +------------------------ +When data is appended to an existing table (for example, using the +``-append`` option in ``ogr2ogr``) the driver will, by default, +emit an INSERT statement for each row of data to be added. This may +be significantly slower than the COPY-based approach taken when creating +a new table, but ensures consistency of unique identifiers if multiple +connections are accessing the table simultaneously. + +If only one connection is accessing the table when data is appended, the +COPY-based approach can be chosen by setting the config option +``PG_USE_COPY`` to ``YES``, which may significantly speed up the operation. + Dataset open options ~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/drivers/vector/s57.rst b/doc/source/drivers/vector/s57.rst index fd2251b9bef5..5693d39a9342 100644 --- a/doc/source/drivers/vector/s57.rst +++ b/doc/source/drivers/vector/s57.rst @@ -33,6 +33,8 @@ the application therefore includes all the updates. Driver capabilities ------------------- +.. supports_create:: + .. supports_georeferencing:: .. supports_virtualio:: diff --git a/doc/source/drivers/vector/sqlite.rst b/doc/source/drivers/vector/sqlite.rst index 8c90eacd2352..257ed243d00d 100644 --- a/doc/source/drivers/vector/sqlite.rst +++ b/doc/source/drivers/vector/sqlite.rst @@ -504,10 +504,8 @@ OGR_L_StartTransaction() / OGR_L_CommitTransaction()) in order to get optimal performance. By default, if no transaction is explicitly started, SQLite will autocommit on every statement, which will be slow. If using ogr2ogr, its default behavior is to COMMIT a transaction every -20000 inserted rows. The **-gt** argument allows explicitly setting the -number of rows for each transaction. Increasing to **-gt 65536** or more -ensures optimal performance while populating some table containing many -hundredth thousand or million rows. +100000 inserted rows. The **-gt** argument allows explicitly setting the +number of rows for each transaction. SQLite usually has a very minimal memory foot-print; just about 20MB of RAM are reserved to store the internal Page Cache [merely 2000 pages]. diff --git a/doc/source/drivers/vector/vrt.rst b/doc/source/drivers/vector/vrt.rst index b1de74ac1050..05a9102f40f8 100644 --- a/doc/source/drivers/vector/vrt.rst +++ b/doc/source/drivers/vector/vrt.rst @@ -20,8 +20,6 @@ The virtual files are currently normally prepared by hand. Driver capabilities ------------------- -.. supports_create:: - .. supports_georeferencing:: .. supports_virtualio:: @@ -210,6 +208,14 @@ layer name, and may have the following subelements: **SrcRegion** * **ExtentXMin**, **ExtentYMin**, **ExtentXMax** and **ExtentXMax** (optional) : same syntax as OGRVRTLayer-level elements of same name + * **XYResolution** (optional, GDAL >= 3.9): + Resolution for the coordinate precision of the X and Y coordinates. + Expressed in the units of the X and Y axis of the SRS + * **ZResolution** (optional, GDAL >= 3.9): + Resolution for the coordinate precision of the Z coordinates. + Expressed in the units of the Z axis of the SRS + * **MResolution** (optional, GDAL >= 3.9): + Resolution for the coordinate precision of the M coordinates. If no **GeometryField** element is specified, all the geometry fields of the source layer will be exposed by the VRT layer. In order not to 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/gdallocationinfo.rst b/doc/source/programs/gdallocationinfo.rst index 5f8d9df82970..023711b24165 100644 --- a/doc/source/programs/gdallocationinfo.rst +++ b/doc/source/programs/gdallocationinfo.rst @@ -17,6 +17,7 @@ Synopsis Usage: gdallocationinfo [--help] [--help-general] [-xml] [-lifonly] [-valonly] + [-E] [-field_sep ] [-ignore_extra_input] [-b ]... [-overview ] [-l_srs ] [-geoloc] [-wgs84] [-oo =]... [ ] @@ -45,7 +46,8 @@ reporting options are provided. .. option:: -valonly The only output is the pixel values of the selected pixel on each of - the selected bands. + the selected bands. By default, the value of each band is output on a + separate line, unless :option:`-field_sep` is specified. .. option:: -b @@ -74,6 +76,32 @@ reporting options are provided. Dataset open option (format specific) +.. option:: -ignore_extra_input + + .. versionadded:: 3.9 + + Set this flag to avoid extra non-numeric content at end of input lines to be + appended to the output lines in -valonly mode (requires :option:`-field_sep` + to be also defined), or as a dedicated field in default or :option:`-xml` modes. + +.. option:: -E + + .. versionadded:: 3.9 + + Enable Echo mode, where input coordinates are prepended to the output lines + in :option:`-valonly` mode. + +.. option:: -field_sep + + .. versionadded:: 3.9 + + Defines the field separator, used in :option:`-valonly` mode, to separate different values. + It defaults to the new-line character, which means that when querying + a raster with several bands, the output will contain one value per line, which + may make it hard to recognize which value belongs to which set of input x,y + points when several ones are provided. Defining the field separator is also + needed + .. option:: The source GDAL raster datasource name. @@ -165,3 +193,8 @@ Reading location from stdin. Location: (52P,59L) Band 1: Value: 148 + + $ cat coordinates.txt | gdallocationinfo -geoloc -valonly -E -field_sep , utmsmall.tif + 443020,3748359,214 + 441197,3749005,107 + 443852,3747743,148 diff --git a/doc/source/programs/gdaltransform.rst b/doc/source/programs/gdaltransform.rst index b91ef59fddbc..3c76070a138b 100644 --- a/doc/source/programs/gdaltransform.rst +++ b/doc/source/programs/gdaltransform.rst @@ -19,7 +19,8 @@ Synopsis [-i] [-s_srs ] [-t_srs ] [-to =]... [-s_coord_epoch ] [-t_coord_epoch ] [-ct ] [-order ] [-tps] [-rpc] [-geoloc] - [-gcp [elevation]]... [-output_xy] + [-gcp [elevation]]... + [-output_xy] [-E] [-field_sep ] [-ignore_extra_input] [ []] @@ -114,6 +115,26 @@ projection,including GCP-based transformations. Restrict output to "x y" instead of "x y z" +.. option:: -ignore_extra_input + + .. versionadded:: 3.9 + + Set this flag to avoid extra non-numeric content at end of input lines to be + appended to the output lines. + +.. option:: -E + + .. versionadded:: 3.9 + + Enable Echo mode, where input coordinates are prepended to the output lines. + +.. option:: -field_sep + + .. versionadded:: 3.9 + + Defines the field separator, to separate different values. + It defaults to the space character. + .. option:: Raster dataset with source projection definition or GCPs. If @@ -131,6 +152,10 @@ Coordinates are read as pairs, triples (for 3D,) or (since GDAL 3.0.0,) quadrupl input, transformed, and written out to standard output in the same way. All transformations offered by gdalwarp are handled, including gcp-based ones. +Starting with GDAL 3.9, additional non-numeric content (typically point name) +at the end of an input line will also be appended to the output line, unless +the :option:`-ignore_extra_input` is added. + Note that input and output must always be in decimal form. There is currently no support for DMS input or output. @@ -194,7 +219,7 @@ 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 metres: +Produces this output measured in longitude degrees, latitude degrees and ellipsoid height in meters: .. code-block:: bash @@ -208,9 +233,9 @@ some nearby corners' coordinates? .. code-block:: bash - echo 300 -370 | gdaltransform \ + echo 300 -370 my address | 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 + -111.8825697384 40.761338402 0 my address diff --git a/doc/source/programs/ogr2ogr.rst b/doc/source/programs/ogr2ogr.rst index 0cc31653c721..f4a21b290477 100644 --- a/doc/source/programs/ogr2ogr.rst +++ b/doc/source/programs/ogr2ogr.rst @@ -51,6 +51,8 @@ Synopsis [-resolveDomains] [-explodecollections] [-zfield ] [-gcp []]... [-order | -tps] + [-xyRes "[ m|mm|deg]"] [-zRes "[ m|mm]"] [-mRes ] + [-unsetCoordPrecision] [-s_coord_epoch ] [-t_coord_epoch ] [-a_coord_epoch ] [-nomd] [-mo =]... [-noNativeData] @@ -255,6 +257,49 @@ output coordinate system or even reprojecting the features during translation. .. include:: options/srs_def.rst +.. option:: -xyRes "[ m|mm|deg]" + + .. versionadded:: 3.9 + + Set/override the geometry X/Y coordinate resolution. If only a numeric value + is specified, it is assumed to be expressed in the units of the target SRS. + The m, mm or deg suffixes can be specified to indicate that the value must be + interpreted as being in meter, millimeter or degree. + + When specifying this option, the :cpp:func:`OGRGeometry::SetPrecision` + method is run on geometries (that are not curves) before passing them to the + output driver, to avoid generating invalid geometries due to the potentially + reduced precision (unless the :config:`OGR_APPLY_GEOM_SET_PRECISION` + configuration option is set to ``NO``) + + If neither this option nor :option:`-unsetCoordPrecision` are specified, the + coordinate resolution of the source layer, if available, is used. + +.. option:: -zRes "[ m|mm]" + + .. versionadded:: 3.9 + + Set/override the geometry Z coordinate resolution. If only a numeric value + is specified, it is assumed to be expressed in the units of the target SRS. + The m or mm suffixes can be specified to indicate that the value must be + interpreted as being in meter or millimeter. + If neither this option nor :option:`-unsetCoordPrecision` are specified, the + coordinate resolution of the source layer, if available, is used. + +.. option:: -mRes + + .. versionadded:: 3.9 + + Set/override the geometry M coordinate resolution. + If neither this option nor :option:`-unsetCoordPrecision` are specified, the + coordinate resolution of the source layer, if available, is used. + +.. option:: -unsetCoordPrecision + + .. versionadded:: 3.9 + + Prevent the geometry coordinate resolution from being set on target layer(s). + .. option:: -s_coord_epoch .. versionadded:: 3.4 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/user/configoptions.rst b/doc/source/user/configoptions.rst index d64b0371c5e0..338b17d11fe9 100644 --- a/doc/source/user/configoptions.rst +++ b/doc/source/user/configoptions.rst @@ -406,6 +406,88 @@ General options Sets the resampling algorithm to be used when reading from a raster into a buffer with different dimensions from the source region. +- .. config:: CPL_VSIL_ZIP_ALLOWED_EXTENSIONS + :choices: + + Add to zip FS handler default extensions array (zip, kmz, dwf, ods, xlsx) + additional extensions listed in :config:`CPL_VSIL_ZIP_ALLOWED_EXTENSIONS` config + option. + +- .. config:: CPL_VSIL_DEFLATE_CHUNK_SIZE + :default: 1 M + +- .. config:: GDAL_DISABLE_CPLLOCALEC + :choices: YES, NO + :default: NO + + If set to YES (default is NO) this option will disable the normal behavior of + the CPLLocaleC class which forces the numeric locale to "C" for selected chunks + of code using the setlocale() call. Behavior of setlocale() in multi-threaded + applications may be undependable but use of this option may result in problem + formatting and interpreting numbers properly. + +- .. config:: GDAL_FILENAME_IS_UTF8 + :choices: YES, NO + :default: YES + + This option only has an effect on Windows systems (using + cpl_vsil_win32.cpp). If set to "NO" then filenames passed + to functions like :cpp:func:`VSIFOpenL` will be passed on directly to CreateFile() + instead of being converted from UTF-8 to wchar_t and passed to + CreateFileW(). This effectively restores the pre-GDAL1.8 behavior for + handling filenames on Windows and might be appropriate for applications that + treat filenames as being in the local encoding. + +- .. config:: GDAL_MAX_BAND_COUNT + :choices: + :default: 65536 + + Defines the maximum number of bands to read from a single dataset. + +- .. config:: GDAL_XML_VALIDATION + :choices: YES, NO + :default: YES + + Determines whether XML content should be validated against an XSD, with + non-conformities reported as warnings. + +- .. config:: GDAL_GEOREF_SOURCES + :since: 2.2 + + Determines the order in which potential georeferencing sources are + scanned. Value should be a comma-separated list of sources in order of + decreasing priority. The set of sources recognized by this option is + driver-specific. + +- .. config:: GDAL_OVR_PROPAGATE_NODATA + :choices: YES, NO + :default: NO + :since: 2.2 + + When computing the value of an overview pixel, determines whether a + single NODATA value should cause the overview pixel to be set to NODATA + (``YES``), or whether the NODATA values should be simply ignored + (``NO``). This configuration option is not supported for all resampling + algorithms/data types. + + +- .. config:: USE_RRD + :choices: YES, NO + :default: NO + + Used by :source_file:`gcore/gdaldefaultoverviews.cpp` + + Can be set to YES to use Erdas Imagine format (.aux) as overview format. See + :program:`gdaladdo` documentation. + +- .. config:: PYTHONSO + + Location of Python shared library file, e.g. ``pythonX.Y[...].so/.dll``. + + +Vector related options +^^^^^^^^^^^^^^^^^^^^^^ + - .. config:: OGR_ARC_STEPSIZE :choices: :default: 4 @@ -461,7 +543,6 @@ General options are present, a GeometryCollection will be returned. - - .. config:: OGR_SQL_LIKE_AS_ILIKE :choices: YES, NO :default: NO @@ -483,83 +564,19 @@ General options Set to NO to preserve the content, but beware that the resulting XML file will not be valid and will require manual edition of the encoding in the XML header. -- .. config:: CPL_VSIL_ZIP_ALLOWED_EXTENSIONS - :choices: - - Add to zip FS handler default extensions array (zip, kmz, dwf, ods, xlsx) - additional extensions listed in :config:`CPL_VSIL_ZIP_ALLOWED_EXTENSIONS` config - option. - -- .. config:: CPL_VSIL_DEFLATE_CHUNK_SIZE - :default: 1 M - -- .. config:: GDAL_DISABLE_CPLLOCALEC - :choices: YES, NO - :default: NO - - If set to YES (default is NO) this option will disable the normal behavior of - the CPLLocaleC class which forces the numeric locale to "C" for selected chunks - of code using the setlocale() call. Behavior of setlocale() in multi-threaded - applications may be undependable but use of this option may result in problem - formatting and interpreting numbers properly. - -- .. config:: GDAL_FILENAME_IS_UTF8 - :choices: YES, NO - :default: YES - - This option only has an effect on Windows systems (using - cpl_vsil_win32.cpp). If set to "NO" then filenames passed - to functions like :cpp:func:`VSIFOpenL` will be passed on directly to CreateFile() - instead of being converted from UTF-8 to wchar_t and passed to - CreateFileW(). This effectively restores the pre-GDAL1.8 behavior for - handling filenames on Windows and might be appropriate for applications that - treat filenames as being in the local encoding. - -- .. config:: GDAL_MAX_BAND_COUNT - :choices: - :default: 65536 - - Defines the maximum number of bands to read from a single dataset. - -- .. config:: GDAL_XML_VALIDATION - :choices: YES, NO - :default: YES - - Determines whether XML content should be validated against an XSD, with - non-conformities reported as warnings. - -- .. config:: GDAL_GEOREF_SOURCES - :since: 2.2 - - Determines the order in which potential georeferencing sources are - scanned. Value should be a comma-separated list of sources in order of - decreasing priority. The set of sources recognized by this option is - driver-specific. - -- .. config:: GDAL_OVR_PROPAGATE_NODATA +- .. config:: OGR_APPLY_GEOM_SET_PRECISION :choices: YES, NO :default: NO - :since: 2.2 - - When computing the value of an overview pixel, determines whether a - single NODATA value should cause the overview pixel to be set to NODATA - (``YES``), or whether the NODATA values should be simply ignored - (``NO``). This configuration option is not supported for all resampling - algorithms/data types. - - -- .. config:: USE_RRD - :choices: YES, NO - :default: NO - - Used by :source_file:`gcore/gdaldefaultoverviews.cpp` - - Can be set to YES to use Erdas Imagine format (.aux) as overview format. See - :program:`gdaladdo` documentation. - -- .. config:: PYTHONSO + :since: 3.9 - Location of Python shared library file, e.g. ``pythonX.Y[...].so/.dll``. + By default, when a geometry coordinate precision is set on a geometry field + definition and a driver honors the GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION + capability, geometries passed to :cpp:func:`OGRLayer::CreateFeature` and + :cpp:func:`OGRLayer::SetFeature` are assumed to be compatible of the + coordinate precision. That is they are assumed to be valid once their + coordinates are rounded to it. If it might not be the case, set this + configuration option to YES before calling CreateFeature() or SetFeature() + to force :cpp:func:`OGRGeometry::SetPrecision` to be called on the passed geometries. Networking options diff --git a/docker/ubuntu-full/Dockerfile b/docker/ubuntu-full/Dockerfile index 21ce77bc742f..f2d22e879c6e 100644 --- a/docker/ubuntu-full/Dockerfile +++ b/docker/ubuntu-full/Dockerfile @@ -227,7 +227,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ && rm -rf /var/lib/apt/lists/* # Install Arrow C++ -ARG ARROW_VERSION=15.0.0-1 +ARG ARROW_VERSION=15.0.1-1 # ARROW_SOVERSION to be updated in the "Build final image" section too ARG ARROW_SOVERSION=1500 RUN . /buildscripts/bh-set-envvars.sh \ diff --git a/frmts/fits/fitsdataset.cpp b/frmts/fits/fitsdataset.cpp index 6c717f76bf02..563d755f3e0c 100644 --- a/frmts/fits/fitsdataset.cpp +++ b/frmts/fits/fitsdataset.cpp @@ -118,9 +118,9 @@ class FITSDataset final : public GDALPamDataset OGRLayer *GetLayer(int) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, - char **papszOptions) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + int TestCapability(const char *pszCap) override; bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override; @@ -2301,12 +2301,13 @@ OGRLayer *FITSDataset::GetLayer(int idx) /************************************************************************/ OGRLayer *FITSDataset::ICreateLayer(const char *pszName, - const OGRSpatialReference * /* poSRS */, - OGRwkbGeometryType eGType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!TestCapability(ODsCCreateLayer)) return nullptr; + + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; if (eGType != wkbNone) { CPLError(CE_Failure, CPLE_NotSupported, "Spatial tables not supported"); diff --git a/frmts/gtiff/gtiffdataset.cpp b/frmts/gtiff/gtiffdataset.cpp index 375f396b3fbb..1e3e56cc47a9 100644 --- a/frmts/gtiff/gtiffdataset.cpp +++ b/frmts/gtiff/gtiffdataset.cpp @@ -367,13 +367,7 @@ std::tuple GTiffDataset::Finalize() m_fpToWrite = nullptr; } - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - m_pasGCPList = nullptr; - m_nGCPCount = 0; - } + m_aoGCPs.clear(); CSLDestroy(m_papszCreationOptions); m_papszCreationOptions = nullptr; diff --git a/frmts/gtiff/gtiffdataset.h b/frmts/gtiff/gtiffdataset.h index 9ebf5043702f..3198b5251902 100644 --- a/frmts/gtiff/gtiffdataset.h +++ b/frmts/gtiff/gtiffdataset.h @@ -153,7 +153,7 @@ class GTiffDataset final : public GDALPamDataset std::unique_ptr m_poMaskExtOvrDS{}; // Used with MASK_OVERVIEW_DATASET open option GTiffJPEGOverviewDS **m_papoJPEGOverviewDS = nullptr; - GDAL_GCP *m_pasGCPList = nullptr; + std::vector m_aoGCPs{}; GDALColorTable *m_poColorTable = nullptr; char **m_papszMetadataFiles = nullptr; GByte *m_pabyBlockBuf = nullptr; @@ -202,7 +202,6 @@ class GTiffDataset final : public GDALPamDataset int m_nLastBandRead = -1; // Used for the all-in-on-strip case. int m_nLastWrittenBlockId = -1; // used for m_bStreamingOut int m_nRefBaseMapping = 0; - int m_nGCPCount = 0; int m_nDisableMultiThreadedRead = 0; GTIFFKeysFlavorEnum m_eGeoTIFFKeysFlavor = GEOTIFF_KEYS_STANDARD; diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index 651903a73f84..4e2365e040de 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -4299,23 +4299,14 @@ void GTiffDataset::ApplyPamInfo() int nPamGCPCount; if (m_nPAMGeorefSrcIndex >= 0 && !oMDMD.GetMetadata("xml:ESRI") && (nPamGCPCount = GDALPamDataset::GetGCPCount()) > 0 && - ((m_nGCPCount > 0 && + ((!m_aoGCPs.empty() && m_nPAMGeorefSrcIndex < m_nGeoTransformGeorefSrcIndex) || - m_nGeoTransformGeorefSrcIndex < 0 || m_nGCPCount == 0)) + m_nGeoTransformGeorefSrcIndex < 0 || m_aoGCPs.empty())) { - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - m_pasGCPList = nullptr; - } - - m_nGCPCount = nPamGCPCount; - m_pasGCPList = - GDALDuplicateGCPs(m_nGCPCount, GDALPamDataset::GetGCPs()); + m_aoGCPs = gdal::GCP::fromC(GDALPamDataset::GetGCPs(), nPamGCPCount); // Invalidate Geotransorm got from less prioritary sources - if (m_nGCPCount > 0 && m_bGeoTransformValid && !bGotGTFromPAM && + if (!m_aoGCPs.empty() && m_bGeoTransformValid && !bGotGTFromPAM && m_nPAMGeorefSrcIndex == 0) { m_bGeoTransformValid = false; @@ -4394,35 +4385,26 @@ void GTiffDataset::ApplyPamInfo() } } - if (m_nGCPCount > 0) + m_aoGCPs.clear(); + const size_t nNewGCPCount = adfSourceGCPs.size() / 2; + for (size_t i = 0; i < nNewGCPCount; ++i) { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - m_pasGCPList = nullptr; - m_nGCPCount = 0; - } - - m_nGCPCount = static_cast(adfSourceGCPs.size() / 2); - m_pasGCPList = static_cast( - CPLCalloc(sizeof(GDAL_GCP), m_nGCPCount)); - for (int i = 0; i < m_nGCPCount; ++i) - { - m_pasGCPList[i].pszId = CPLStrdup(""); - m_pasGCPList[i].pszInfo = CPLStrdup(""); - // The origin used is the bottom left corner, - // and raw values to be multiplied by the - // TIFFTAG_XRESOLUTION/TIFFTAG_YRESOLUTION - m_pasGCPList[i].dfGCPPixel = - adfSourceGCPs[2 * i] * CPLAtof(pszTIFFTagXRes); - m_pasGCPList[i].dfGCPLine = - nRasterYSize - - adfSourceGCPs[2 * i + 1] * CPLAtof(pszTIFFTagYRes); - m_pasGCPList[i].dfGCPX = adfTargetGCPs[2 * i]; - m_pasGCPList[i].dfGCPY = adfTargetGCPs[2 * i + 1]; + m_aoGCPs.emplace_back( + "", "", + // The origin used is the bottom left corner, + // and raw values to be multiplied by the + // TIFFTAG_XRESOLUTION/TIFFTAG_YRESOLUTION + /* pixel = */ + adfSourceGCPs[2 * i] * CPLAtof(pszTIFFTagXRes), + /* line = */ + nRasterYSize - adfSourceGCPs[2 * i + 1] * + CPLAtof(pszTIFFTagYRes), + /* X = */ adfTargetGCPs[2 * i], + /* Y = */ adfTargetGCPs[2 * i + 1]); } // Invalidate Geotransform got from less prioritary sources - if (m_nGCPCount > 0 && m_bGeoTransformValid && + if (!m_aoGCPs.empty() && m_bGeoTransformValid && !bGotGTFromPAM && m_nPAMGeorefSrcIndex == 0) { m_bGeoTransformValid = false; @@ -5970,9 +5952,11 @@ void GTiffDataset::LoadGeoreferencingAndPamIfNeeded() char **papszSiblingFiles = GetSiblingFiles(); // Begin with .tab since it can also have projection info. + int nGCPCount = 0; + GDAL_GCP *pasGCPList = nullptr; const int bTabFileOK = GDALReadTabFile2( - m_pszFilename, m_adfGeoTransform, &pszTabWKT, &m_nGCPCount, - &m_pasGCPList, papszSiblingFiles, &pszGeorefFilename); + m_pszFilename, m_adfGeoTransform, &pszTabWKT, &nGCPCount, + &pasGCPList, papszSiblingFiles, &pszGeorefFilename); if (bTabFileOK) { @@ -5981,12 +5965,19 @@ void GTiffDataset::LoadGeoreferencingAndPamIfNeeded() // { // m_nProjectionGeorefSrcIndex = nIndex; // } - if (m_nGCPCount == 0) + m_aoGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); + if (m_aoGCPs.empty()) { m_bGeoTransformValid = true; } } + if (nGCPCount) + { + GDALDeinitGCPs(nGCPCount, pasGCPList); + CPLFree(pasGCPList); + } + if (pszGeorefFilename) { CPLFree(m_pszGeorefFilename); @@ -6037,32 +6028,21 @@ void GTiffDataset::LoadGeoreferencingAndPamIfNeeded() &padfTiePoints) && !m_bGeoTransformValid) { - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - } - m_nGCPCount = nCount / 6; - m_pasGCPList = static_cast( - CPLCalloc(sizeof(GDAL_GCP), m_nGCPCount)); - - for (int iGCP = 0; iGCP < m_nGCPCount; ++iGCP) + m_aoGCPs.clear(); + const int nNewGCPCount = nCount / 6; + for (int iGCP = 0; iGCP < nNewGCPCount; ++iGCP) { - char szID[32] = {}; - - snprintf(szID, sizeof(szID), "%d", iGCP + 1); - m_pasGCPList[iGCP].pszId = CPLStrdup(szID); - m_pasGCPList[iGCP].pszInfo = CPLStrdup(""); - m_pasGCPList[iGCP].dfGCPPixel = padfTiePoints[iGCP * 6 + 0]; - m_pasGCPList[iGCP].dfGCPLine = padfTiePoints[iGCP * 6 + 1]; - m_pasGCPList[iGCP].dfGCPX = padfTiePoints[iGCP * 6 + 3]; - m_pasGCPList[iGCP].dfGCPY = padfTiePoints[iGCP * 6 + 4]; - m_pasGCPList[iGCP].dfGCPZ = padfTiePoints[iGCP * 6 + 5]; + m_aoGCPs.emplace_back(CPLSPrintf("%d", iGCP + 1), "", + /* pixel = */ padfTiePoints[iGCP * 6 + 0], + /* line = */ padfTiePoints[iGCP * 6 + 1], + /* X = */ padfTiePoints[iGCP * 6 + 3], + /* Y = */ padfTiePoints[iGCP * 6 + 4], + /* Z = */ padfTiePoints[iGCP * 6 + 5]); if (bPixelIsPoint && !bPointGeoIgnore) { - m_pasGCPList[iGCP].dfGCPPixel += 0.5; - m_pasGCPList[iGCP].dfGCPLine += 0.5; + m_aoGCPs.back().Pixel() += 0.5; + m_aoGCPs.back().Line() += 0.5; } } m_nGeoTransformGeorefSrcIndex = m_nINTERNALGeorefSrcIndex; @@ -6119,12 +6099,12 @@ const OGRSpatialReference *GTiffDataset::GetSpatialRef() const { const_cast(this)->LoadGeoreferencingAndPamIfNeeded(); - if (m_nGCPCount == 0) + if (m_aoGCPs.empty()) { const_cast(this)->LookForProjection(); } - return m_nGCPCount == 0 && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; + return m_aoGCPs.empty() && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; } /************************************************************************/ @@ -6164,7 +6144,7 @@ int GTiffDataset::GetGCPCount() { LoadGeoreferencingAndPamIfNeeded(); - return m_nGCPCount; + return static_cast(m_aoGCPs.size()); } /************************************************************************/ @@ -6176,11 +6156,11 @@ const OGRSpatialReference *GTiffDataset::GetGCPSpatialRef() const { const_cast(this)->LoadGeoreferencingAndPamIfNeeded(); - if (m_nGCPCount > 0) + if (!m_aoGCPs.empty()) { const_cast(this)->LookForProjection(); } - return m_nGCPCount > 0 && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; + return !m_aoGCPs.empty() && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; } /************************************************************************/ @@ -6192,7 +6172,7 @@ const GDAL_GCP *GTiffDataset::GetGCPs() { LoadGeoreferencingAndPamIfNeeded(); - return m_pasGCPList; + return gdal::GCP::c_ptr(m_aoGCPs); } /************************************************************************/ diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index c05bb12213fb..5e0e177ec7ff 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -3577,15 +3577,15 @@ void GTiffDataset::WriteGeoTIFFInfo() double *padfTiePoints = static_cast( CPLMalloc(6 * sizeof(double) * GetGCPCount())); - for (int iGCP = 0; iGCP < GetGCPCount(); ++iGCP) + for (size_t iGCP = 0; iGCP < m_aoGCPs.size(); ++iGCP) { - padfTiePoints[iGCP * 6 + 0] = m_pasGCPList[iGCP].dfGCPPixel; - padfTiePoints[iGCP * 6 + 1] = m_pasGCPList[iGCP].dfGCPLine; + padfTiePoints[iGCP * 6 + 0] = m_aoGCPs[iGCP].Pixel(); + padfTiePoints[iGCP * 6 + 1] = m_aoGCPs[iGCP].Line(); padfTiePoints[iGCP * 6 + 2] = 0; - padfTiePoints[iGCP * 6 + 3] = m_pasGCPList[iGCP].dfGCPX; - padfTiePoints[iGCP * 6 + 4] = m_pasGCPList[iGCP].dfGCPY; - padfTiePoints[iGCP * 6 + 5] = m_pasGCPList[iGCP].dfGCPZ; + padfTiePoints[iGCP * 6 + 3] = m_aoGCPs[iGCP].X(); + padfTiePoints[iGCP * 6 + 4] = m_aoGCPs[iGCP].Y(); + padfTiePoints[iGCP * 6 + 5] = m_aoGCPs[iGCP].Z(); if (bPixelIsPoint && !bPointGeoIgnore) { @@ -7314,8 +7314,11 @@ GDALDataset *GTiffDataset::CreateCopy(const char *pszFilename, GTiffFillStreamableOffsetAndCount(l_hTIFF, nSize); TIFFWriteDirectory(l_hTIFF); } - TIFFSetDirectory(l_hTIFF, - static_cast(TIFFNumberOfDirectories(l_hTIFF) - 1)); + const auto nDirCount = TIFFNumberOfDirectories(l_hTIFF); + if (nDirCount >= 1) + { + TIFFSetDirectory(l_hTIFF, static_cast(nDirCount - 1)); + } const toff_t l_nDirOffset = TIFFCurrentDirOffset(l_hTIFF); TIFFFlush(l_hTIFF); XTIFFClose(l_hTIFF); @@ -8252,16 +8255,13 @@ CPLErr GTiffDataset::SetGeoTransform(double *padfTransform) CPLErr eErr = CE_None; if (eAccess == GA_Update) { - if (m_nGCPCount > 0) + if (!m_aoGCPs.empty()) { ReportError(CE_Warning, CPLE_AppDefined, "GCPs previously set are going to be cleared " "due to the setting of a geotransform."); m_bForceUnsetGTOrGCPs = true; - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - m_nGCPCount = 0; - m_pasGCPList = nullptr; + m_aoGCPs.clear(); } else if (padfTransform[0] == 0.0 && padfTransform[1] == 0.0 && padfTransform[2] == 0.0 && padfTransform[3] == 0.0 && @@ -8319,7 +8319,7 @@ CPLErr GTiffDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, if (eAccess == GA_Update) { - if (m_nGCPCount > 0 && nGCPCountIn == 0) + if (!m_aoGCPs.empty() && nGCPCountIn == 0) { m_bForceUnsetGTOrGCPs = true; } @@ -8377,14 +8377,7 @@ CPLErr GTiffDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); } - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - } - - m_nGCPCount = nGCPCountIn; - m_pasGCPList = GDALDuplicateGCPs(m_nGCPCount, pasGCPListIn); + m_aoGCPs = gdal::GCP::fromC(pasGCPListIn, nGCPCountIn); } return eErr; diff --git a/frmts/hdf4/hdf4imagedataset.cpp b/frmts/hdf4/hdf4imagedataset.cpp index bc3cf060d411..a1c57b70c30d 100644 --- a/frmts/hdf4/hdf4imagedataset.cpp +++ b/frmts/hdf4/hdf4imagedataset.cpp @@ -130,8 +130,7 @@ class HDF4ImageDataset final : public HDF4Dataset OGRSpatialReference m_oGCPSRS{}; bool bHasGeoTransform; double adfGeoTransform[6]; - GDAL_GCP *pasGCPList; - int nGCPCount; + std::vector m_aoGCPs{}; HDF4DatasetType iDatasetType; @@ -765,8 +764,8 @@ HDF4ImageDataset::HDF4ImageDataset() iPalDataType(0), nComps(0), nPalEntries(0), iXDim(0), iYDim(0), iBandDim(-1), i4Dim(0), nBandCount(0), pszSubdatasetName(nullptr), pszFieldName(nullptr), poColorTable(nullptr), bHasGeoTransform(false), - pasGCPList(nullptr), nGCPCount(0), iDatasetType(HDF4_UNKNOWN), iSDS(FAIL), - nBlockPreferredXSize(-1), nBlockPreferredYSize(-1), bReadTile(false) + iDatasetType(HDF4_UNKNOWN), iSDS(FAIL), nBlockPreferredXSize(-1), + nBlockPreferredYSize(-1), bReadTile(false) { m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); @@ -810,16 +809,6 @@ HDF4ImageDataset::~HDF4ImageDataset() if (poColorTable != nullptr) delete poColorTable; - if (nGCPCount > 0) - { - for (int i = 0; i < nGCPCount; i++) - { - CPLFree(pasGCPList[i].pszId); - CPLFree(pasGCPList[i].pszInfo); - } - - CPLFree(pasGCPList); - } if (hHDF4 > 0) { switch (iDatasetType) @@ -905,7 +894,7 @@ CPLErr HDF4ImageDataset::SetSpatialRef(const OGRSpatialReference *poSRS) int HDF4ImageDataset::GetGCPCount() { - return nGCPCount; + return static_cast(m_aoGCPs.size()); } /************************************************************************/ @@ -915,7 +904,7 @@ int HDF4ImageDataset::GetGCPCount() const OGRSpatialReference *HDF4ImageDataset::GetGCPSpatialRef() const { - return m_oSRS.IsEmpty() || nGCPCount == 0 ? nullptr : &m_oGCPSRS; + return m_oSRS.IsEmpty() || m_aoGCPs.empty() ? nullptr : &m_oGCPSRS; } /************************************************************************/ @@ -924,7 +913,7 @@ const OGRSpatialReference *HDF4ImageDataset::GetGCPSpatialRef() const const GDAL_GCP *HDF4ImageDataset::GetGCPs() { - return pasGCPList; + return gdal::GCP::c_ptr(m_aoGCPs); } /************************************************************************/ @@ -1252,29 +1241,13 @@ void HDF4ImageDataset::CaptureL1GMTLInfo() "AUTHORITY[\"EPSG\",\"9108\"]],AXIS[\"Lat\",NORTH],AXIS[\"Long\",EAST]" ",AUTHORITY[\"EPSG\",\"4326\"]]"); - nGCPCount = 4; - pasGCPList = (GDAL_GCP *)CPLCalloc(nGCPCount, sizeof(GDAL_GCP)); - GDALInitGCPs(nGCPCount, pasGCPList); - - pasGCPList[0].dfGCPX = dfULX; - pasGCPList[0].dfGCPY = dfULY; - pasGCPList[0].dfGCPPixel = 0.0; - pasGCPList[0].dfGCPLine = 0.0; - - pasGCPList[1].dfGCPX = dfURX; - pasGCPList[1].dfGCPY = dfURY; - pasGCPList[1].dfGCPPixel = GetRasterXSize(); - pasGCPList[1].dfGCPLine = 0.0; - - pasGCPList[2].dfGCPX = dfLLX; - pasGCPList[2].dfGCPY = dfLLY; - pasGCPList[2].dfGCPPixel = 0.0; - pasGCPList[2].dfGCPLine = GetRasterYSize(); - - pasGCPList[3].dfGCPX = dfLRX; - pasGCPList[3].dfGCPY = dfLRY; - pasGCPList[3].dfGCPPixel = GetRasterXSize(); - pasGCPList[3].dfGCPLine = GetRasterYSize(); + m_aoGCPs.emplace_back(nullptr, nullptr, 0.0, 0.0, dfULX, dfULY); + m_aoGCPs.emplace_back(nullptr, nullptr, GetRasterXSize(), 0.0, dfURX, + dfURY); + m_aoGCPs.emplace_back(nullptr, nullptr, 0.0, GetRasterYSize(), dfLLX, + dfLLY); + m_aoGCPs.emplace_back(nullptr, nullptr, GetRasterXSize(), GetRasterYSize(), + dfLRX, dfLRY); } /************************************************************************/ @@ -2520,25 +2493,17 @@ int HDF4ImageDataset::ProcessSwathGeolocation(int32 hSW, char **papszDimList) */ if (iGCPStepX > 0) { - nGCPCount = (((nXPoints - 1) / iGCPStepX) + 1) * - (((nYPoints - 1) / iGCPStepY) + 1); - - pasGCPList = reinterpret_cast( - CPLCalloc(nGCPCount, sizeof(GDAL_GCP))); - GDALInitGCPs(nGCPCount, pasGCPList); - - int iGCP = 0; for (int i = 0; i < nYPoints; i += iGCPStepY) { for (int j = 0; j < nXPoints; j += iGCPStepX) { const int iGeoOff = i * nXPoints + j; - pasGCPList[iGCP].dfGCPX = AnyTypeToDouble( + double dfGCPX = AnyTypeToDouble( iWrkNumType, reinterpret_cast( reinterpret_cast(pLong) + iGeoOff * iDataSize)); - pasGCPList[iGCP].dfGCPY = AnyTypeToDouble( + double dfGCPY = AnyTypeToDouble( iWrkNumType, reinterpret_cast( reinterpret_cast(pLat) + iGeoOff * iDataSize)); @@ -2552,27 +2517,24 @@ int HDF4ImageDataset::ProcessSwathGeolocation(int32 hSW, char **papszDimList) if (eProduct == PROD_ASTER_L1A || eProduct == PROD_ASTER_L1B) { - pasGCPList[iGCP].dfGCPY = - atan(tan(pasGCPList[iGCP].dfGCPY * PI / 180) / - 0.99330562) * - 180 / PI; + dfGCPY = atan(tan(dfGCPY * PI / 180) / 0.99330562) * + 180 / PI; } - ToGeoref(&pasGCPList[iGCP].dfGCPX, - &pasGCPList[iGCP].dfGCPY); - - pasGCPList[iGCP].dfGCPZ = 0.0; + ToGeoref(&dfGCPX, &dfGCPY); + double dfGCPPixel = 0.0; + double dfGCPLine = 0.0; if (pLatticeX && pLatticeY) { - pasGCPList[iGCP].dfGCPPixel = + dfGCPPixel = AnyTypeToDouble( iLatticeType, reinterpret_cast( reinterpret_cast(pLatticeX) + iGeoOff * iLatticeDataSize)) + 0.5; - pasGCPList[iGCP].dfGCPLine = + dfGCPLine = AnyTypeToDouble( iLatticeType, reinterpret_cast( @@ -2582,15 +2544,14 @@ int HDF4ImageDataset::ProcessSwathGeolocation(int32 hSW, char **papszDimList) } else if (paiOffset && paiIncrement) { - pasGCPList[iGCP].dfGCPPixel = - paiOffset[iPixelDim] + j * paiIncrement[iPixelDim] + - 0.5; - pasGCPList[iGCP].dfGCPLine = - paiOffset[iLineDim] + i * paiIncrement[iLineDim] + - 0.5; + dfGCPPixel = paiOffset[iPixelDim] + + j * paiIncrement[iPixelDim] + 0.5; + dfGCPLine = paiOffset[iLineDim] + + i * paiIncrement[iLineDim] + 0.5; } - iGCP++; + m_aoGCPs.emplace_back("", "", dfGCPPixel, dfGCPLine, dfGCPX, + dfGCPY); } } } diff --git a/frmts/hdf5/hdf5imagedataset.cpp b/frmts/hdf5/hdf5imagedataset.cpp index 39b827b45762..96d135db450b 100644 --- a/frmts/hdf5/hdf5imagedataset.cpp +++ b/frmts/hdf5/hdf5imagedataset.cpp @@ -63,8 +63,7 @@ class HDF5ImageDataset final : public HDF5Dataset OGRSpatialReference m_oSRS{}; OGRSpatialReference m_oGCPSRS{}; - GDAL_GCP *pasGCPList; - int nGCPCount; + std::vector m_aoGCPs{}; hsize_t *dims; hsize_t *maxdims; @@ -172,11 +171,10 @@ class HDF5ImageDataset final : public HDF5Dataset /* HDF5ImageDataset() */ /************************************************************************/ HDF5ImageDataset::HDF5ImageDataset() - : pasGCPList(nullptr), nGCPCount(0), dims(nullptr), maxdims(nullptr), - poH5Objects(nullptr), ndims(0), dimensions(0), dataset_id(-1), - dataspace_id(-1), size(0), datatype(-1), native(-1), - iSubdatasetType(UNKNOWN_PRODUCT), iCSKProductType(PROD_UNKNOWN), - bHasGeoTransform(false) + : dims(nullptr), maxdims(nullptr), poH5Objects(nullptr), ndims(0), + dimensions(0), dataset_id(-1), dataspace_id(-1), size(0), datatype(-1), + native(-1), iSubdatasetType(UNKNOWN_PRODUCT), + iCSKProductType(PROD_UNKNOWN), bHasGeoTransform(false) { m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); @@ -208,16 +206,6 @@ HDF5ImageDataset::~HDF5ImageDataset() CPLFree(dims); CPLFree(maxdims); - - if (nGCPCount > 0) - { - for (int i = 0; i < nGCPCount; i++) - { - CPLFree(pasGCPList[i].pszId); - CPLFree(pasGCPList[i].pszInfo); - } - CPLFree(pasGCPList); - } } /************************************************************************/ @@ -1205,7 +1193,6 @@ CPLErr HDF5ImageDataset::CreateProjections() bool bHasLonNearMinus180 = false; bool bHasLonNearPlus180 = false; bool bHasLonNearZero = false; - nGCPCount = 0; for (int j = 0; j < nYLimit; j += nDeltaLat) { for (int i = 0; i < nXLimit; i += nDeltaLon) @@ -1223,17 +1210,10 @@ CPLErr HDF5ImageDataset::CreateProjections() bHasLonNearMinus180 = true; if (fabs(Longitude[iGCP]) < 90) bHasLonNearZero = true; - nGCPCount++; } } // Fill the GCPs list. - - pasGCPList = static_cast( - CPLCalloc(nGCPCount, sizeof(GDAL_GCP))); - - GDALInitGCPs(nGCPCount, pasGCPList); - const char *pszShiftGCP = CPLGetConfigOption("HDF5_SHIFT_GCPX_BY_180", nullptr); const bool bAdd180 = @@ -1241,7 +1221,6 @@ CPLErr HDF5ImageDataset::CreateProjections() !bHasLonNearZero && pszShiftGCP == nullptr) || (pszShiftGCP != nullptr && CPLTestBool(pszShiftGCP)); - int k = 0; for (int j = 0; j < nYLimit; j += nDeltaLat) { for (int i = 0; i < nXLimit; i += nDeltaLon) @@ -1253,15 +1232,14 @@ CPLErr HDF5ImageDataset::CreateProjections() static_cast(dfLongNoData) == Longitude[iGCP])) continue; - pasGCPList[k].dfGCPX = - static_cast(Longitude[iGCP]); + double dfGCPX = static_cast(Longitude[iGCP]); if (bAdd180) - pasGCPList[k].dfGCPX += 180.0; - pasGCPList[k].dfGCPY = + dfGCPX += 180.0; + const double dfGCPY = static_cast(Latitude[iGCP]); - pasGCPList[k].dfGCPPixel = i + 0.5; - pasGCPList[k++].dfGCPLine = j + 0.5; + m_aoGCPs.emplace_back("", "", i + 0.5, j + 0.5, dfGCPX, + dfGCPY); } } @@ -1299,8 +1277,8 @@ const OGRSpatialReference *HDF5ImageDataset::GetSpatialRef() const int HDF5ImageDataset::GetGCPCount() { - if (nGCPCount > 0) - return nGCPCount; + if (!m_aoGCPs.empty()) + return static_cast(m_aoGCPs.size()); return GDALPamDataset::GetGCPCount(); } @@ -1312,7 +1290,7 @@ int HDF5ImageDataset::GetGCPCount() const OGRSpatialReference *HDF5ImageDataset::GetGCPSpatialRef() const { - if (nGCPCount > 0 && !m_oGCPSRS.IsEmpty()) + if (!m_aoGCPs.empty() && !m_oGCPSRS.IsEmpty()) return &m_oGCPSRS; return GDALPamDataset::GetGCPSpatialRef(); @@ -1324,8 +1302,8 @@ const OGRSpatialReference *HDF5ImageDataset::GetGCPSpatialRef() const const GDAL_GCP *HDF5ImageDataset::GetGCPs() { - if (nGCPCount > 0) - return pasGCPList; + if (!m_aoGCPs.empty()) + return gdal::GCP::c_ptr(m_aoGCPs); return GDALPamDataset::GetGCPs(); } @@ -1563,8 +1541,6 @@ void HDF5ImageDataset::CaptureCSKGCPs(int iProductType) if (iProductType == PROD_CSK_L0 || iProductType == PROD_CSK_L1A || iProductType == PROD_CSK_L1B) { - nGCPCount = 4; - pasGCPList = static_cast(CPLCalloc(sizeof(GDAL_GCP), 4)); CPLString osCornerName[4]; double pdCornerPixel[4] = {0.0, 0.0, 0.0, 0.0}; double pdCornerLine[4] = {0.0, 0.0, 0.0, 0.0}; @@ -1596,11 +1572,6 @@ void HDF5ImageDataset::CaptureCSKGCPs(int iProductType) // For all the image's corners. for (int i = 0; i < 4; i++) { - GDALInitGCPs(1, pasGCPList + i); - - CPLFree(pasGCPList[i].pszId); - pasGCPList[i].pszId = nullptr; - double *pdCornerCoordinates = nullptr; // Retrieve the attributes. @@ -1609,29 +1580,15 @@ void HDF5ImageDataset::CaptureCSKGCPs(int iProductType) { CPLError(CE_Failure, CPLE_OpenFailed, "Error retrieving CSK GCPs"); - // Free on failure, e.g. in case of QLK subdataset. - for (i = 0; i < 4; i++) - { - if (pasGCPList[i].pszId) - CPLFree(pasGCPList[i].pszId); - if (pasGCPList[i].pszInfo) - CPLFree(pasGCPList[i].pszInfo); - } - CPLFree(pasGCPList); - pasGCPList = nullptr; - nGCPCount = 0; + m_aoGCPs.clear(); break; } - // Fill the GCPs name. - pasGCPList[i].pszId = CPLStrdup(osCornerName[i].c_str()); - - // Fill the coordinates. - pasGCPList[i].dfGCPX = pdCornerCoordinates[1]; - pasGCPList[i].dfGCPY = pdCornerCoordinates[0]; - pasGCPList[i].dfGCPZ = pdCornerCoordinates[2]; - pasGCPList[i].dfGCPPixel = pdCornerPixel[i]; - pasGCPList[i].dfGCPLine = pdCornerLine[i]; + m_aoGCPs.emplace_back(osCornerName[i].c_str(), "", pdCornerPixel[i], + pdCornerLine[i], + /* X = */ pdCornerCoordinates[1], + /* Y = */ pdCornerCoordinates[0], + /* Z = */ pdCornerCoordinates[2]); // Free the returned coordinates. CPLFree(pdCornerCoordinates); diff --git a/frmts/hfa/hfadataset.cpp b/frmts/hfa/hfadataset.cpp index 6b573ee2f26c..af338e59c10b 100644 --- a/frmts/hfa/hfadataset.cpp +++ b/frmts/hfa/hfadataset.cpp @@ -3064,7 +3064,6 @@ HFADataset::HFADataset() { m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - memset(asGCPList, 0, sizeof(asGCPList)); memset(adfGeoTransform, 0, sizeof(adfGeoTransform)); } @@ -3098,9 +3097,6 @@ HFADataset::~HFADataset() } hHFA = nullptr; } - - if (nGCPCount > 0) - GDALDeinitGCPs(36, asGCPList); } /************************************************************************/ @@ -4618,28 +4614,21 @@ void HFADataset::UseXFormStack(int nStepCount, Efga_Polynomial *pasPLForward, { // Generate GCPs using the transform. - nGCPCount = 0; - GDALInitGCPs(36, asGCPList); - for (double dfYRatio = 0.0; dfYRatio < 1.001; dfYRatio += 0.2) { for (double dfXRatio = 0.0; dfXRatio < 1.001; dfXRatio += 0.2) { const double dfLine = 0.5 + (GetRasterYSize() - 1) * dfYRatio; const double dfPixel = 0.5 + (GetRasterXSize() - 1) * dfXRatio; - const int iGCP = nGCPCount; - - asGCPList[iGCP].dfGCPPixel = dfPixel; - asGCPList[iGCP].dfGCPLine = dfLine; - - asGCPList[iGCP].dfGCPX = dfPixel; - asGCPList[iGCP].dfGCPY = dfLine; - asGCPList[iGCP].dfGCPZ = 0.0; + gdal::GCP gcp("", "", dfPixel, dfLine, + /* X = */ dfPixel, + /* Y = */ dfLine); if (HFAEvaluateXFormStack(nStepCount, FALSE, pasPLReverse, - &(asGCPList[iGCP].dfGCPX), - &(asGCPList[iGCP].dfGCPY))) - nGCPCount++; + &(gcp.X()), &(gcp.Y()))) + { + m_aoGCPs.emplace_back(std::move(gcp)); + } } } @@ -4715,7 +4704,7 @@ void HFADataset::UseXFormStack(int nStepCount, Efga_Polynomial *pasPLForward, int HFADataset::GetGCPCount() { const int nPAMCount = GDALPamDataset::GetGCPCount(); - return nPAMCount > 0 ? nPAMCount : nGCPCount; + return nPAMCount > 0 ? nPAMCount : static_cast(m_aoGCPs.size()); } /************************************************************************/ @@ -4728,7 +4717,7 @@ const OGRSpatialReference *HFADataset::GetGCPSpatialRef() const const OGRSpatialReference *poSRS = GDALPamDataset::GetGCPSpatialRef(); if (poSRS) return poSRS; - return nGCPCount > 0 && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; + return !m_aoGCPs.empty() && !m_oSRS.IsEmpty() ? &m_oSRS : nullptr; } /************************************************************************/ @@ -4740,7 +4729,7 @@ const GDAL_GCP *HFADataset::GetGCPs() const GDAL_GCP *psPAMGCPs = GDALPamDataset::GetGCPs(); if (psPAMGCPs) return psPAMGCPs; - return asGCPList; + return gdal::GCP::c_ptr(m_aoGCPs); } /************************************************************************/ diff --git a/frmts/hfa/hfadataset.h b/frmts/hfa/hfadataset.h index 0c49a98d3660..f1b613333a0b 100644 --- a/frmts/hfa/hfadataset.h +++ b/frmts/hfa/hfadataset.h @@ -68,8 +68,7 @@ class HFADataset final : public GDALPamDataset bool bForceToPEString = false; bool bDisablePEString = false; - int nGCPCount = 0; - GDAL_GCP asGCPList[36]; + std::vector m_aoGCPs{}; void UseXFormStack(int nStepCount, Efga_Polynomial *pasPolyListForward, Efga_Polynomial *pasPolyListReverse); diff --git a/frmts/jp2kak/jp2kak_headers.h b/frmts/jp2kak/jp2kak_headers.h index 330a34abf81b..572fb9c98153 100644 --- a/frmts/jp2kak/jp2kak_headers.h +++ b/frmts/jp2kak/jp2kak_headers.h @@ -85,7 +85,7 @@ #endif #if KDU_MAJOR_VERSION > 7 || (KDU_MAJOR_VERSION == 7 && KDU_MINOR_VERSION >= 8) -// Before Kakdu 7.8, kdu_roi_rect was missing from libkdu_aXY +// Before Kakadu 7.8, kdu_roi_rect was missing from libkdu_aXY #define KDU_HAS_ROI_RECT #endif diff --git a/frmts/jpeg/jpgdataset.cpp b/frmts/jpeg/jpgdataset.cpp index d50d3cc0332f..3256371402d1 100644 --- a/frmts/jpeg/jpgdataset.cpp +++ b/frmts/jpeg/jpgdataset.cpp @@ -1643,9 +1643,9 @@ int JPGRasterBand::GetOverviewCount() JPGDatasetCommon::JPGDatasetCommon() : nScaleFactor(1), bHasInitInternalOverviews(false), nInternalOverviewsCurrent(0), nInternalOverviewsToFree(0), - papoInternalOverviews(nullptr), bGeoTransformValid(false), nGCPCount(0), - pasGCPList(nullptr), m_fpImage(nullptr), nSubfileOffset(0), - nLoadedScanline(-1), m_pabyScanline(nullptr), bHasReadEXIFMetadata(false), + papoInternalOverviews(nullptr), bGeoTransformValid(false), + m_fpImage(nullptr), nSubfileOffset(0), nLoadedScanline(-1), + m_pabyScanline(nullptr), bHasReadEXIFMetadata(false), bHasReadXMPMetadata(false), bHasReadICCMetadata(false), papszMetadata(nullptr), nExifOffset(-1), nInterOffset(-1), nGPSOffset(-1), bSwabflag(false), nTiffDirStart(-1), nTIFFHEADER(-1), @@ -1678,12 +1678,6 @@ JPGDatasetCommon::~JPGDatasetCommon() if (papszMetadata != nullptr) CSLDestroy(papszMetadata); - if (nGCPCount > 0) - { - GDALDeinitGCPs(nGCPCount, pasGCPList); - CPLFree(pasGCPList); - } - CPLFree(pabyBitMask); CPLFree(pabyCMask); delete poMaskBand; @@ -2499,7 +2493,7 @@ int JPGDatasetCommon::GetGCPCount() LoadWorldFileOrTab(); - return nGCPCount; + return static_cast(m_aoGCPs.size()); } /************************************************************************/ @@ -2516,7 +2510,7 @@ const OGRSpatialReference *JPGDatasetCommon::GetGCPSpatialRef() const const_cast(this)->LoadWorldFileOrTab(); - if (!m_oSRS.IsEmpty() && nGCPCount > 0) + if (!m_oSRS.IsEmpty() && !m_aoGCPs.empty()) return &m_oSRS; return nullptr; @@ -2535,7 +2529,7 @@ const GDAL_GCP *JPGDatasetCommon::GetGCPs() LoadWorldFileOrTab(); - return pasGCPList; + return gdal::GCP::c_ptr(m_aoGCPs); } /************************************************************************/ @@ -3151,12 +3145,17 @@ void JPGDatasetCommon::LoadWorldFileOrTab() if (!bGeoTransformValid) { char *pszProjection = nullptr; + int nGCPCount = 0; + GDAL_GCP *pasGCPList = nullptr; const bool bTabFileOK = CPL_TO_BOOL(GDALReadTabFile2( GetDescription(), adfGeoTransform, &pszProjection, &nGCPCount, &pasGCPList, oOvManager.GetSiblingFiles(), &pszWldFilename)); if (pszProjection) m_oSRS.importFromWkt(pszProjection); CPLFree(pszProjection); + m_aoGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); + GDALDeinitGCPs(nGCPCount, pasGCPList); + CPLFree(pasGCPList); if (bTabFileOK && nGCPCount == 0) bGeoTransformValid = true; diff --git a/frmts/jpeg/jpgdataset.h b/frmts/jpeg/jpgdataset.h index 2c4b690056de..ed8ff651f999 100644 --- a/frmts/jpeg/jpgdataset.h +++ b/frmts/jpeg/jpgdataset.h @@ -173,8 +173,7 @@ class JPGDatasetCommon CPL_NON_FINAL : public GDALPamDataset OGRSpatialReference m_oSRS{}; bool bGeoTransformValid; double adfGeoTransform[6]; - int nGCPCount; - GDAL_GCP *pasGCPList; + std::vector m_aoGCPs{}; VSILFILE *m_fpImage; GUIntBig nSubfileOffset; diff --git a/frmts/mem/memdataset.cpp b/frmts/mem/memdataset.cpp index 7403d1fc8dc9..f4b502ede067 100644 --- a/frmts/mem/memdataset.cpp +++ b/frmts/mem/memdataset.cpp @@ -542,9 +542,8 @@ bool MEMRasterBand::IsMaskBand() const /************************************************************************/ MEMDataset::MEMDataset() - : GDALDataset(FALSE), bGeoTransformSet(FALSE), m_nGCPCount(0), - m_pasGCPs(nullptr), m_nOverviewDSCount(0), m_papoOverviewDS(nullptr), - m_poPrivate(new Private()) + : GDALDataset(FALSE), bGeoTransformSet(FALSE), m_nOverviewDSCount(0), + m_papoOverviewDS(nullptr), m_poPrivate(new Private()) { adfGeoTransform[0] = 0.0; adfGeoTransform[1] = 1.0; @@ -567,9 +566,6 @@ MEMDataset::~MEMDataset() FlushCache(true); bSuppressOnClose = bSuppressOnCloseBackup; - GDALDeinitGCPs(m_nGCPCount, m_pasGCPs); - CPLFree(m_pasGCPs); - for (int i = 0; i < m_nOverviewDSCount; ++i) delete m_papoOverviewDS[i]; CPLFree(m_papoOverviewDS); @@ -681,7 +677,7 @@ void *MEMDataset::GetInternalHandle(const char *pszRequest) int MEMDataset::GetGCPCount() { - return m_nGCPCount; + return static_cast(m_aoGCPs.size()); } /************************************************************************/ @@ -701,7 +697,7 @@ const OGRSpatialReference *MEMDataset::GetGCPSpatialRef() const const GDAL_GCP *MEMDataset::GetGCPs() { - return m_pasGCPs; + return gdal::GCP::c_ptr(m_aoGCPs); } /************************************************************************/ @@ -712,15 +708,11 @@ CPLErr MEMDataset::SetGCPs(int nNewCount, const GDAL_GCP *pasNewGCPList, const OGRSpatialReference *poSRS) { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPs); - CPLFree(m_pasGCPs); - m_oGCPSRS.Clear(); if (poSRS) m_oGCPSRS = *poSRS; - m_nGCPCount = nNewCount; - m_pasGCPs = GDALDuplicateGCPs(m_nGCPCount, pasNewGCPList); + m_aoGCPs = gdal::GCP::fromC(pasNewGCPList, nNewCount); return CE_None; } diff --git a/frmts/mem/memdataset.h b/frmts/mem/memdataset.h index 46404b18b244..213a46d3fffc 100644 --- a/frmts/mem/memdataset.h +++ b/frmts/mem/memdataset.h @@ -63,8 +63,7 @@ class CPL_DLL MEMDataset CPL_NON_FINAL : public GDALDataset OGRSpatialReference m_oSRS{}; - int m_nGCPCount; - GDAL_GCP *m_pasGCPs; + std::vector m_aoGCPs{}; OGRSpatialReference m_oGCPSRS{}; int m_nOverviewDSCount; diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index f087efa662c3..d707d3f1f699 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -6576,14 +6576,17 @@ OGRLayer *netCDFDataset::GetLayer(int nIdx) /************************************************************************/ OGRLayer *netCDFDataset::ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { int nLayerCDFId = cdfid; if (!TestCapability(ODsCCreateLayer)) return nullptr; + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + CPLString osNetCDFLayerName(pszName); const netCDFWriterConfigLayer *poLayerConfig = nullptr; if (oWriterConfig.m_bIsValid) diff --git a/frmts/netcdf/netcdfdataset.h b/frmts/netcdf/netcdfdataset.h index 2ff21a54dc36..8fab085cc6e4 100644 --- a/frmts/netcdf/netcdfdataset.h +++ b/frmts/netcdf/netcdfdataset.h @@ -543,10 +543,9 @@ class netCDFDataset final : public GDALPamDataset protected: CPLXMLNode *SerializeToXML(const char *pszVRTPath) override; - virtual OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; CPLErr Close() override; diff --git a/frmts/netcdf/netcdfdrivercore.cpp b/frmts/netcdf/netcdfdrivercore.cpp index 17202b1ccea7..eb034cb4dbfb 100644 --- a/frmts/netcdf/netcdfdrivercore.cpp +++ b/frmts/netcdf/netcdfdrivercore.cpp @@ -297,11 +297,11 @@ struct NCDFDriverSubdatasetInfo : public GDALSubdatasetInfo { m_subdatasetComponent = m_subdatasetComponent.substr(1); } - if (m_subdatasetComponent.rfind('"') == - m_subdatasetComponent.length() - 1) + if (!m_subdatasetComponent.empty() && + m_subdatasetComponent.rfind('"') == + m_subdatasetComponent.length() - 1) { - m_subdatasetComponent = m_subdatasetComponent.substr( - 0, m_subdatasetComponent.length() - 1); + m_subdatasetComponent.pop_back(); } } } diff --git a/frmts/netcdf/netcdfsgwriterutil.cpp b/frmts/netcdf/netcdfsgwriterutil.cpp index 390608f0c6ca..2863d4214d57 100644 --- a/frmts/netcdf/netcdfsgwriterutil.cpp +++ b/frmts/netcdf/netcdfsgwriterutil.cpp @@ -37,7 +37,7 @@ SGeometry_Feature::SGeometry_Feature(OGRFeature &ft) if (geom == nullptr) { - throw SGWriter_Exception_EmptyGeometry(); + throw SGWriter_Exception_NullGeometry(); } OGRwkbGeometryType ogwkt = geom->getGeometryType(); @@ -114,12 +114,8 @@ SGeometry_Feature::SGeometry_Feature(OGRFeature &ft) // Get node count // First count exterior ring const auto exterior_ring = poly->getExteriorRing(); - if (exterior_ring == nullptr) - { - throw SGWriter_Exception_EmptyGeometry(); - } - - size_t outer_ring_ct = exterior_ring->getNumPoints(); + const size_t outer_ring_ct = + exterior_ring ? exterior_ring->getNumPoints() : 0; this->total_point_count += outer_ring_ct; this->ppart_node_count.push_back(outer_ring_ct); @@ -134,14 +130,12 @@ SGeometry_Feature::SGeometry_Feature(OGRFeature &ft) { this->hasInteriorRing = true; const auto iring = poly->getInteriorRing(iRingCt); - if (iring == nullptr) + if (iring) { - throw SGWriter_Exception_RingOOB(); + this->total_point_count += iring->getNumPoints(); + this->ppart_node_count.push_back(iring->getNumPoints()); + this->total_part_count++; } - - this->total_point_count += iring->getNumPoints(); - this->ppart_node_count.push_back(iring->getNumPoints()); - this->total_part_count++; } } @@ -155,12 +149,8 @@ SGeometry_Feature::SGeometry_Feature(OGRFeature &ft) for (const auto poly : *poMP) { const auto exterior_ring = poly->getExteriorRing(); - if (exterior_ring == nullptr) - { - throw SGWriter_Exception_EmptyGeometry(); - } - - size_t outer_ring_ct = exterior_ring->getNumPoints(); + const size_t outer_ring_ct = + exterior_ring ? exterior_ring->getNumPoints() : 0; this->total_point_count += outer_ring_ct; this->ppart_node_count.push_back(outer_ring_ct); @@ -176,16 +166,14 @@ SGeometry_Feature::SGeometry_Feature(OGRFeature &ft) iRingCt++) { const auto iring = poly->getInteriorRing(iRingCt); - if (iring == nullptr) + if (iring) { - throw SGWriter_Exception_RingOOB(); + this->hasInteriorRing = true; + this->total_point_count += iring->getNumPoints(); + this->ppart_node_count.push_back(iring->getNumPoints()); + this->total_part_count++; + this->part_at_ind_interior.push_back(true); } - - this->hasInteriorRing = true; - this->total_point_count += iring->getNumPoints(); - this->ppart_node_count.push_back(iring->getNumPoints()); - this->total_part_count++; - this->part_at_ind_interior.push_back(true); } } } diff --git a/frmts/netcdf/netcdfsgwriterutil.h b/frmts/netcdf/netcdfsgwriterutil.h index 1f3bac76c462..175c7b9bf6b2 100644 --- a/frmts/netcdf/netcdfsgwriterutil.h +++ b/frmts/netcdf/netcdfsgwriterutil.h @@ -639,7 +639,7 @@ class SGWriter_Exception_NCDefFailure : public SGWriter_Exception const char *failure_type); }; -class SGWriter_Exception_EmptyGeometry : public SGWriter_Exception +class SGWriter_Exception_NullGeometry : public SGWriter_Exception { std::string msg; @@ -648,28 +648,13 @@ class SGWriter_Exception_EmptyGeometry : public SGWriter_Exception { return this->msg.c_str(); } - SGWriter_Exception_EmptyGeometry() - : msg("An empty geometry was detected when writing a netCDF file. " + SGWriter_Exception_NullGeometry() + : msg("A null geometry was detected when writing a netCDF file. " "Empty geometries are not allowed.") { } }; -class SGWriter_Exception_RingOOB : public SGWriter_Exception -{ - std::string msg; - - public: - const char *get_err_msg() override - { - return this->msg.c_str(); - } - SGWriter_Exception_RingOOB() - : msg("An attempt was made to read a polygon ring that does not exist.") - { - } -}; - class SGWriter_Exception_NCDelFailure : public SGWriter_Exception { std::string msg; diff --git a/frmts/null/nulldataset.cpp b/frmts/null/nulldataset.cpp index 4f13bb99bdf1..9b209dbae2cd 100644 --- a/frmts/null/nulldataset.cpp +++ b/frmts/null/nulldataset.cpp @@ -51,10 +51,9 @@ class GDALNullDataset final : public GDALDataset } virtual OGRLayer *GetLayer(int) override; - virtual OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual int TestCapability(const char *) override; @@ -222,9 +221,13 @@ GDALNullDataset::~GDALNullDataset() /************************************************************************/ OGRLayer *GDALNullDataset::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions */) { + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + m_papoLayers = static_cast( CPLRealloc(m_papoLayers, sizeof(OGRLayer *) * (m_nLayers + 1))); m_papoLayers[m_nLayers] = new GDALNullLayer(pszLayerName, poSRS, eType); diff --git a/frmts/ogcapi/gdalogcapidataset.cpp b/frmts/ogcapi/gdalogcapidataset.cpp index 8e52d159f6e3..74adfdf21a7a 100644 --- a/frmts/ogcapi/gdalogcapidataset.cpp +++ b/frmts/ogcapi/gdalogcapidataset.cpp @@ -2427,6 +2427,7 @@ OGCAPITiledLayer::OGCAPITiledLayer( OGCAPITiledLayer::~OGCAPITiledLayer() { + m_poFeatureDefn->InvalidateLayer(); m_poFeatureDefn->Release(); } diff --git a/frmts/pcidsk/pcidskdataset2.cpp b/frmts/pcidsk/pcidskdataset2.cpp index 5844710f472e..69824f9495ba 100644 --- a/frmts/pcidsk/pcidskdataset2.cpp +++ b/frmts/pcidsk/pcidskdataset2.cpp @@ -2094,9 +2094,8 @@ OGRLayer *PCIDSK2Dataset::GetLayer(int iLayer) /************************************************************************/ OGRLayer *PCIDSK2Dataset::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - CPL_UNUSED char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { /* -------------------------------------------------------------------- */ /* Verify we are in update mode. */ @@ -2110,6 +2109,10 @@ OGRLayer *PCIDSK2Dataset::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Figure out what type of layer we need. */ /* -------------------------------------------------------------------- */ diff --git a/frmts/pcidsk/pcidskdataset2.h b/frmts/pcidsk/pcidskdataset2.h index f8332c882a54..77bb8a489b85 100644 --- a/frmts/pcidsk/pcidskdataset2.h +++ b/frmts/pcidsk/pcidskdataset2.h @@ -107,8 +107,9 @@ class PCIDSK2Dataset final : public GDALPamDataset virtual int TestCapability(const char *) override; - virtual OGRLayer *ICreateLayer(const char *, const OGRSpatialReference *, - OGRwkbGeometryType, char **) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; }; /************************************************************************/ diff --git a/frmts/pdf/gdal_pdf.h b/frmts/pdf/gdal_pdf.h index a567ecb3d124..93716e95a0c4 100644 --- a/frmts/pdf/gdal_pdf.h +++ b/frmts/pdf/gdal_pdf.h @@ -520,10 +520,9 @@ class PDFWritableVectorDataset final : public GDALDataset PDFWritableVectorDataset(); virtual ~PDFWritableVectorDataset(); - virtual OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) override; + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr SyncToDisk(); diff --git a/frmts/pdf/pdfcreatecopy.cpp b/frmts/pdf/pdfcreatecopy.cpp index 6b882cd7137b..462c2b158bc6 100644 --- a/frmts/pdf/pdfcreatecopy.cpp +++ b/frmts/pdf/pdfcreatecopy.cpp @@ -702,7 +702,7 @@ GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS, double dfLRPixel = nWidth; double dfLRLine = nHeight; - GDAL_GCP asNeatLineGCPs[4]; + std::vector asNeatLineGCPs(4); if (pszNEATLINE == nullptr) pszNEATLINE = poSrcDS->GetMetadataItem("NEATLINE"); if (bHasGT && pszNEATLINE != nullptr && pszNEATLINE[0] != '\0') @@ -721,32 +721,33 @@ GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS, { const double X = poLS->getX(i); const double Y = poLS->getY(i); - asNeatLineGCPs[i].dfGCPX = X; - asNeatLineGCPs[i].dfGCPY = Y; + asNeatLineGCPs[i].X() = X; + asNeatLineGCPs[i].Y() = Y; const double x = adfGeoTransformInv[0] + X * adfGeoTransformInv[1] + Y * adfGeoTransformInv[2]; const double y = adfGeoTransformInv[3] + X * adfGeoTransformInv[4] + Y * adfGeoTransformInv[5]; - asNeatLineGCPs[i].dfGCPPixel = x; - asNeatLineGCPs[i].dfGCPLine = y; + asNeatLineGCPs[i].Pixel() = x; + asNeatLineGCPs[i].Line() = y; } int iUL = 0; int iUR = 0; int iLR = 0; int iLL = 0; - GDALPDFFind4Corners(asNeatLineGCPs, iUL, iUR, iLR, iLL); - - if (fabs(asNeatLineGCPs[iUL].dfGCPPixel - - asNeatLineGCPs[iLL].dfGCPPixel) > .5 || - fabs(asNeatLineGCPs[iUR].dfGCPPixel - - asNeatLineGCPs[iLR].dfGCPPixel) > .5 || - fabs(asNeatLineGCPs[iUL].dfGCPLine - - asNeatLineGCPs[iUR].dfGCPLine) > .5 || - fabs(asNeatLineGCPs[iLL].dfGCPLine - - asNeatLineGCPs[iLR].dfGCPLine) > .5) + GDALPDFFind4Corners(gdal::GCP::c_ptr(asNeatLineGCPs), iUL, iUR, + iLR, iLL); + + if (fabs(asNeatLineGCPs[iUL].Pixel() - + asNeatLineGCPs[iLL].Pixel()) > .5 || + fabs(asNeatLineGCPs[iUR].Pixel() - + asNeatLineGCPs[iLR].Pixel()) > .5 || + fabs(asNeatLineGCPs[iUL].Line() - + asNeatLineGCPs[iUR].Line()) > .5 || + fabs(asNeatLineGCPs[iLL].Line() - + asNeatLineGCPs[iLR].Line()) > .5) { CPLError(CE_Warning, CPLE_NotSupported, "Neatline coordinates should form a rectangle in " @@ -754,13 +755,13 @@ GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS, for (int i = 0; i < 4; i++) { CPLDebug("PDF", "pixel[%d] = %.1f, line[%d] = %.1f", i, - asNeatLineGCPs[i].dfGCPPixel, i, - asNeatLineGCPs[i].dfGCPLine); + asNeatLineGCPs[i].Pixel(), i, + asNeatLineGCPs[i].Line()); } } else { - pasGCPList = asNeatLineGCPs; + pasGCPList = gdal::GCP::c_ptr(asNeatLineGCPs); } } } diff --git a/frmts/pdf/pdfcreatefromcomposition.cpp b/frmts/pdf/pdfcreatefromcomposition.cpp index e0a8eaac47a1..1e31cca481a7 100644 --- a/frmts/pdf/pdfcreatefromcomposition.cpp +++ b/frmts/pdf/pdfcreatefromcomposition.cpp @@ -662,7 +662,7 @@ bool GDALPDFComposerWriter::GenerateGeoreferencing( } } - std::vector aGCPs; + std::vector aGCPs; for (const auto *psIter = psGeoreferencing->psChild; psIter; psIter = psIter->psNext) { @@ -680,15 +680,8 @@ bool GDALPDFComposerWriter::GenerateGeoreferencing( "missing on ControlPoint"); return false; } - GDAL_GCP gcp; - gcp.pszId = nullptr; - gcp.pszInfo = nullptr; - gcp.dfGCPPixel = CPLAtof(pszx); - gcp.dfGCPLine = CPLAtof(pszy); - gcp.dfGCPX = CPLAtof(pszX); - gcp.dfGCPY = CPLAtof(pszY); - gcp.dfGCPZ = 0; - aGCPs.emplace_back(std::move(gcp)); + aGCPs.emplace_back(nullptr, nullptr, CPLAtof(pszx), CPLAtof(pszy), + CPLAtof(pszX), CPLAtof(pszY)); } } if (aGCPs.size() < 4) @@ -771,7 +764,8 @@ bool GDALPDFComposerWriter::GenerateGeoreferencing( if (pszId) { if (!GDALGCPsToGeoTransform(static_cast(aGCPs.size()), - aGCPs.data(), georeferencing.m_adfGT, TRUE)) + gdal::GCP::c_ptr(aGCPs), + georeferencing.m_adfGT, TRUE)) { CPLError(CE_Failure, CPLE_AppDefined, "Could not compute geotransform with approximate match."); @@ -809,7 +803,7 @@ bool GDALPDFComposerWriter::GenerateGeoreferencing( GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing( OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2, - double bboxY2, const std::vector &aGCPs, + double bboxY2, const std::vector &aGCPs, const std::vector &aBoundingPolygon) { OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS); @@ -826,22 +820,15 @@ GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing( return GDALPDFObjectNum(); } - std::vector aGCPReprojected; + std::vector aGCPReprojected; bool bSuccess = true; for (const auto &gcp : aGCPs) { - double X = gcp.dfGCPX; - double Y = gcp.dfGCPY; + double X = gcp.X(); + double Y = gcp.Y(); bSuccess &= OCTTransform(hCT, 1, &X, &Y, nullptr) == 1; - GDAL_GCP newGCP; - newGCP.pszId = nullptr; - newGCP.pszInfo = nullptr; - newGCP.dfGCPPixel = gcp.dfGCPPixel; - newGCP.dfGCPLine = gcp.dfGCPLine; - newGCP.dfGCPX = X; - newGCP.dfGCPY = Y; - newGCP.dfGCPZ = 0; - aGCPReprojected.emplace_back(std::move(newGCP)); + aGCPReprojected.emplace_back(nullptr, nullptr, gcp.Pixel(), gcp.Line(), + X, Y); } if (!bSuccess) { @@ -891,12 +878,12 @@ GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing( atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16")); for (const auto &gcp : aGCPReprojected) { - poGPTS->AddWithPrecision(gcp.dfGCPY, nPrecision) - .AddWithPrecision(gcp.dfGCPX, nPrecision); // Lat, long order + poGPTS->AddWithPrecision(gcp.Y(), nPrecision) + .AddWithPrecision(gcp.X(), nPrecision); // Lat, long order poLPTS - ->AddWithPrecision((gcp.dfGCPPixel - bboxX1) / (bboxX2 - bboxX1), + ->AddWithPrecision((gcp.Pixel() - bboxX1) / (bboxX2 - bboxX1), nPrecision) - .AddWithPrecision((gcp.dfGCPLine - bboxY1) / (bboxY2 - bboxY1), + .AddWithPrecision((gcp.Line() - bboxY1) / (bboxY2 - bboxY1), nPrecision); } @@ -942,7 +929,7 @@ GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing( GDALPDFObjectNum GDALPDFComposerWriter::GenerateOGC_BP_Georeferencing( OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2, - double bboxY2, const std::vector &aGCPs, + double bboxY2, const std::vector &aGCPs, const std::vector &aBoundingPolygon) { const OGRSpatialReference *poSRS = OGRSpatialReference::FromHandle(hSRS); @@ -972,10 +959,10 @@ GDALPDFObjectNum GDALPDFComposerWriter::GenerateOGC_BP_Georeferencing( for (const auto &gcp : aGCPs) { GDALPDFArrayRW *poGCP = new GDALPDFArrayRW(); - poGCP->Add(gcp.dfGCPPixel, TRUE) - .Add(gcp.dfGCPLine, TRUE) - .Add(gcp.dfGCPX, TRUE) - .Add(gcp.dfGCPY, TRUE); + poGCP->Add(gcp.Pixel(), TRUE) + .Add(gcp.Line(), TRUE) + .Add(gcp.X(), TRUE) + .Add(gcp.Y(), TRUE); poRegistration->Add(poGCP); } diff --git a/frmts/pdf/pdfcreatefromcomposition.h b/frmts/pdf/pdfcreatefromcomposition.h index 93b9855d0fbe..96d1d181a25e 100644 --- a/frmts/pdf/pdfcreatefromcomposition.h +++ b/frmts/pdf/pdfcreatefromcomposition.h @@ -160,13 +160,13 @@ class GDALPDFComposerWriter final : public GDALPDFBaseWriter GDALPDFObjectNum GenerateISO32000_Georeferencing( OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2, - double bboxY2, const std::vector &aGCPs, + double bboxY2, const std::vector &aGCPs, const std::vector &aBoundingPolygon); GDALPDFObjectNum GenerateOGC_BP_Georeferencing(OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2, double bboxY2, - const std::vector &aGCPs, + const std::vector &aGCPs, const std::vector &aBoundingPolygon); bool ExploreContent(const CPLXMLNode *psNode, PageContext &oPageContext); diff --git a/frmts/pdf/pdfwritabledataset.cpp b/frmts/pdf/pdfwritabledataset.cpp index 74eda8fe79b0..6c9ec9cff973 100644 --- a/frmts/pdf/pdfwritabledataset.cpp +++ b/frmts/pdf/pdfwritabledataset.cpp @@ -100,9 +100,13 @@ GDALDataset *PDFWritableVectorDataset::Create(const char *pszName, int nXSize, OGRLayer * PDFWritableVectorDataset::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Create the layer object. */ /* -------------------------------------------------------------------- */ diff --git a/frmts/pds/pds4dataset.cpp b/frmts/pds/pds4dataset.cpp index 15d235f388ca..dd532d3676f4 100644 --- a/frmts/pds/pds4dataset.cpp +++ b/frmts/pds/pds4dataset.cpp @@ -4116,9 +4116,8 @@ void PDS4Dataset::WriteHeader() /************************************************************************/ OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { const char *pszTableType = CSLFetchNameValueDef(papszOptions, "TABLE_TYPE", "DELIMITED"); @@ -4128,6 +4127,10 @@ OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + const char *pszExt = EQUAL(pszTableType, "CHARACTER") ? "dat" : EQUAL(pszTableType, "BINARY") ? "bin" : "csv"; diff --git a/frmts/pds/pds4dataset.h b/frmts/pds/pds4dataset.h index 3ffc4e5600fd..86133740c5b3 100644 --- a/frmts/pds/pds4dataset.h +++ b/frmts/pds/pds4dataset.h @@ -395,9 +395,9 @@ class PDS4Dataset final : public RawDataset OGRLayer *GetLayer(int) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + int TestCapability(const char *pszCap) override; bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override; diff --git a/frmts/tiledb/tiledbheaders.h b/frmts/tiledb/tiledbheaders.h index eeb89ea7127e..0f60d54820a4 100644 --- a/frmts/tiledb/tiledbheaders.h +++ b/frmts/tiledb/tiledbheaders.h @@ -494,10 +494,11 @@ class OGRTileDBDataset final : public TileDBDataset : nullptr; } int TestCapability(const char *) override; + OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + static GDALDataset *Open(GDALOpenInfo *, tiledb::Object::Type objectType); static GDALDataset *Create(const char *pszFilename, CSLConstList papszOptions); diff --git a/frmts/tiledb/tiledbsparse.cpp b/frmts/tiledb/tiledbsparse.cpp index 2a6ae5232211..e13afe8456bc 100644 --- a/frmts/tiledb/tiledbsparse.cpp +++ b/frmts/tiledb/tiledbsparse.cpp @@ -337,8 +337,8 @@ OGRLayer *OGRTileDBDataset::ExecuteSQL(const char *pszSQLCommand, /***********************************************************************/ OGRLayer * OGRTileDBDataset::ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (eAccess != GA_Update) { @@ -347,6 +347,10 @@ OGRTileDBDataset::ICreateLayer(const char *pszName, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + if (m_osGroupName.empty() && !m_apoLayers.empty()) { #ifdef HAS_TILEDB_GROUP diff --git a/frmts/vrt/vrtdataset.cpp b/frmts/vrt/vrtdataset.cpp index 9331480d9bea..540d3e755159 100644 --- a/frmts/vrt/vrtdataset.cpp +++ b/frmts/vrt/vrtdataset.cpp @@ -101,11 +101,6 @@ VRTDataset::~VRTDataset() m_poSRS->Release(); if (m_poGCP_SRS) m_poGCP_SRS->Release(); - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - } CPLFree(m_pszVRTPath); delete m_poMaskBand; @@ -315,10 +310,9 @@ CPLXMLNode *VRTDataset::SerializeToXML(const char *pszVRTPathIn) /* -------------------------------------------------------------------- */ /* GCPs */ /* -------------------------------------------------------------------- */ - if (m_nGCPCount > 0) + if (!m_asGCPs.empty()) { - GDALSerializeGCPListToXML(psDSTree, m_pasGCPList, m_nGCPCount, - m_poGCP_SRS); + GDALSerializeGCPListToXML(psDSTree, m_asGCPs, m_poGCP_SRS); } /* -------------------------------------------------------------------- */ @@ -505,8 +499,7 @@ CPLErr VRTDataset::XMLInit(const CPLXMLNode *psTree, const char *pszVRTPathIn) /* -------------------------------------------------------------------- */ if (const CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList")) { - GDALDeserializeGCPListFromXML(psGCPList, &m_pasGCPList, &m_nGCPCount, - &m_poGCP_SRS); + GDALDeserializeGCPListFromXML(psGCPList, m_asGCPs, &m_poGCP_SRS); } /* -------------------------------------------------------------------- */ @@ -621,7 +614,7 @@ CPLErr VRTDataset::XMLInit(const CPLXMLNode *psTree, const char *pszVRTPathIn) int VRTDataset::GetGCPCount() { - return m_nGCPCount; + return static_cast(m_asGCPs.size()); } /************************************************************************/ @@ -631,7 +624,7 @@ int VRTDataset::GetGCPCount() const GDAL_GCP *VRTDataset::GetGCPs() { - return m_pasGCPList; + return gdal::GCP::c_ptr(m_asGCPs); } /************************************************************************/ @@ -644,17 +637,9 @@ CPLErr VRTDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, { if (m_poGCP_SRS) m_poGCP_SRS->Release(); - if (m_nGCPCount > 0) - { - GDALDeinitGCPs(m_nGCPCount, m_pasGCPList); - CPLFree(m_pasGCPList); - } m_poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr; - - m_nGCPCount = nGCPCountIn; - - m_pasGCPList = GDALDuplicateGCPs(nGCPCountIn, pasGCPListIn); + m_asGCPs = gdal::GCP::fromC(pasGCPListIn, nGCPCountIn); SetNeedsFlush(); diff --git a/frmts/vrt/vrtdataset.h b/frmts/vrt/vrtdataset.h index 6c123027779a..06d6b8e52ca7 100644 --- a/frmts/vrt/vrtdataset.h +++ b/frmts/vrt/vrtdataset.h @@ -214,8 +214,7 @@ class CPL_DLL VRTDataset CPL_NON_FINAL : public GDALDataset int m_bGeoTransformSet = false; double m_adfGeoTransform[6]; - int m_nGCPCount = 0; - GDAL_GCP *m_pasGCPList = nullptr; + std::vector m_asGCPs{}; OGRSpatialReference *m_poGCP_SRS = nullptr; bool m_bNeedsFlush = false; @@ -426,6 +425,14 @@ class CPL_DLL VRTWarpedDataset final : public VRTDataset virtual char **GetFileList() override; + virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, + int nXSize, int nYSize, void *pData, int nBufXSize, + int nBufYSize, GDALDataType eBufType, + int nBandCount, int *panBandMap, + GSpacing nPixelSpace, GSpacing nLineSpace, + GSpacing nBandSpace, + GDALRasterIOExtraArg *psExtraArg) override; + CPLErr ProcessBlock(int iBlockX, int iBlockY); void GetBlockSize(int *, int *) const; @@ -819,8 +826,18 @@ class CPL_DLL VRTWarpedRasterBand final : public VRTRasterBand virtual CPLErr IReadBlock(int, int, void *) override; virtual CPLErr IWriteBlock(int, int, void *) override; + virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, + int nXSize, int nYSize, void *pData, int nBufXSize, + int nBufYSize, GDALDataType eBufType, + GSpacing nPixelSpace, GSpacing nLineSpace, + GDALRasterIOExtraArg *psExtraArg) override; + virtual int GetOverviewCount() override; virtual GDALRasterBand *GetOverview(int) override; + + private: + int m_nIRasterIOCounter = + 0; //! Protects against infinite recursion inside IRasterIO() }; /************************************************************************/ /* VRTPansharpenedRasterBand */ diff --git a/frmts/vrt/vrtwarped.cpp b/frmts/vrt/vrtwarped.cpp index 642241b7392b..7e56d4fbb50d 100644 --- a/frmts/vrt/vrtwarped.cpp +++ b/frmts/vrt/vrtwarped.cpp @@ -35,6 +35,7 @@ #include #include #include +#include // Suppress deprecation warning for GDALOpenVerticalShiftGrid and // GDALApplyVerticalShiftGrid @@ -1809,6 +1810,208 @@ CPLErr VRTWarpedDataset::ProcessBlock(int iBlockX, int iBlockY) return CE_None; } +/************************************************************************/ +/* IRasterIO() */ +/************************************************************************/ + +// Specialized implementation of IRasterIO() that will be faster than +// using the VRTWarpedRasterBand::IReadBlock() method in situations where +// - a large enough chunk of data is requested at once +// - and multi-threaded warping is enabled (it only kicks in if the warped +// chunk is large enough) and/or when reading the source dataset is +// multi-threaded (e.g JP2KAK or JP2OpenJPEG driver). +CPLErr VRTWarpedDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, + int nXSize, int nYSize, void *pData, + int nBufXSize, int nBufYSize, + GDALDataType eBufType, int nBandCount, + int *panBandMap, GSpacing nPixelSpace, + GSpacing nLineSpace, GSpacing nBandSpace, + GDALRasterIOExtraArg *psExtraArg) +{ + if (eRWFlag == GF_Write || + // For too small request fall back to the block-based approach to + // benefit from caching + nBufXSize <= m_nBlockXSize || nBufYSize <= m_nBlockYSize || + // Or if we don't request all bands at once + nBandCount < nBands || + !CPLTestBool( + CPLGetConfigOption("GDAL_VRT_WARP_USE_DATASET_RASTERIO", "YES"))) + { + return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nBandCount, panBandMap, nPixelSpace, + nLineSpace, nBandSpace, psExtraArg); + } + + // Try overviews for sub-sampled requests + if (nBufXSize < nXSize || nBufYSize < nYSize) + { + int bTried = FALSE; + const CPLErr eErr = TryOverviewRasterIO( + eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, + eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, + nBandSpace, psExtraArg, &bTried); + + if (bTried) + { + return eErr; + } + } + + // Fallback to default block-based implementation when not requesting at + // the nominal resolution (in theory we could construct a warper taking + // into account that, like we do for virtual warped overviews, but that + // would be a complication). + if (nBufXSize != nXSize || nBufYSize != nYSize) + { + return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nBandCount, panBandMap, nPixelSpace, + nLineSpace, nBandSpace, psExtraArg); + } + + if (m_poWarper == nullptr) + return CE_Failure; + + const GDALWarpOptions *psWO = m_poWarper->GetOptions(); + + // Build a map from warped output bands to their index + std::map oMapBandToWarpingBandIndex; + for (int i = 0; i < psWO->nBandCount; ++i) + oMapBandToWarpingBandIndex[psWO->panDstBands[i]] = i; + + // Check that all requested bands are actually warped output bands. + for (int i = 0; i < nBandCount; ++i) + { + const int nRasterIOBand = panBandMap[i]; + if (oMapBandToWarpingBandIndex.find(nRasterIOBand) == + oMapBandToWarpingBandIndex.end()) + { + // Not sure if that can happen... + // but if that does, that will likely later fail in ProcessBlock() + return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nBandCount, panBandMap, nPixelSpace, + nLineSpace, nBandSpace, psExtraArg); + } + } + + int nSrcXOff = 0; + int nSrcYOff = 0; + int nSrcXSize = 0; + int nSrcYSize = 0; + double dfSrcXExtraSize = 0; + double dfSrcYExtraSize = 0; + double dfSrcFillRatio = 0; + // Find the source window that corresponds to our target window + if (m_poWarper->ComputeSourceWindow(nXOff, nYOff, nXSize, nYSize, &nSrcXOff, + &nSrcYOff, &nSrcXSize, &nSrcYSize, + &dfSrcXExtraSize, &dfSrcYExtraSize, + &dfSrcFillRatio) != CE_None) + { + return CE_Failure; + } + + GByte *const pabyDst = static_cast(pData); + const int nWarpDTSize = GDALGetDataTypeSizeBytes(psWO->eWorkingDataType); + + const double dfMemRequired = m_poWarper->GetWorkingMemoryForWindow( + nSrcXSize, nSrcYSize, nXSize, nYSize); + // If we need more warp working memory than allowed, we have to use a + // splitting strategy until we get below the limit. + if (dfMemRequired > psWO->dfWarpMemoryLimit && nXSize >= 2 && nYSize >= 2) + { + CPLDebugOnly("VRT", "VRTWarpedDataset::IRasterIO(): exceeding warp " + "memory. Splitting region"); + + GDALRasterIOExtraArg sExtraArg; + INIT_RASTERIO_EXTRA_ARG(sExtraArg); + + bool bOK; + // Split along the longest dimension + if (nXSize >= nYSize) + { + const int nHalfXSize = nXSize / 2; + bOK = IRasterIO(GF_Read, nXOff, nYOff, nHalfXSize, nYSize, pabyDst, + nHalfXSize, nYSize, eBufType, nBandCount, + panBandMap, nPixelSpace, nLineSpace, nBandSpace, + &sExtraArg) == CE_None && + IRasterIO(GF_Read, nXOff + nHalfXSize, nYOff, + nXSize - nHalfXSize, nYSize, + pabyDst + nHalfXSize * nPixelSpace, + nXSize - nHalfXSize, nYSize, eBufType, nBandCount, + panBandMap, nPixelSpace, nLineSpace, nBandSpace, + &sExtraArg) == CE_None; + } + else + { + const int nHalfYSize = nYSize / 2; + bOK = IRasterIO(GF_Read, nXOff, nYOff, nXSize, nHalfYSize, pabyDst, + nXSize, nHalfYSize, eBufType, nBandCount, + panBandMap, nPixelSpace, nLineSpace, nBandSpace, + &sExtraArg) == CE_None && + IRasterIO(GF_Read, nXOff, nYOff + nHalfYSize, nXSize, + nYSize - nHalfYSize, + pabyDst + nHalfYSize * nLineSpace, nXSize, + nYSize - nHalfYSize, eBufType, nBandCount, + panBandMap, nPixelSpace, nLineSpace, nBandSpace, + &sExtraArg) == CE_None; + } + return bOK ? CE_None : CE_Failure; + } + + CPLDebugOnly("VRT", + "Using optimized VRTWarpedDataset::IRasterIO() code path"); + + // Allocate a warping destination buffer + // Note: we could potentially use the pData target buffer argument of this + // function in some circumstances... but probably not worth the complication + GByte *pabyWarpBuffer = static_cast( + m_poWarper->CreateDestinationBuffer(nXSize, nYSize)); + + if (pabyWarpBuffer == nullptr) + { + return CE_Failure; + } + + const CPLErr eErr = m_poWarper->WarpRegionToBuffer( + nXOff, nYOff, nXSize, nYSize, pabyWarpBuffer, psWO->eWorkingDataType, + nSrcXOff, nSrcYOff, nSrcXSize, nSrcYSize, dfSrcXExtraSize, + dfSrcYExtraSize); + if (eErr == CE_None) + { + // Copy warping buffer into user destination buffer + for (int i = 0; i < nBandCount; i++) + { + const int nRasterIOBand = panBandMap[i]; + const auto oIterToWarpingBandIndex = + oMapBandToWarpingBandIndex.find(nRasterIOBand); + // cannot happen due to earlier check + CPLAssert(oIterToWarpingBandIndex != + oMapBandToWarpingBandIndex.end()); + + const GByte *const pabyWarpBandBuffer = + pabyWarpBuffer + + static_cast(oIterToWarpingBandIndex->second) * + nXSize * nYSize * nWarpDTSize; + GByte *const pabyDstBand = pabyDst + i * nBandSpace; + + for (int iY = 0; iY < nYSize; iY++) + { + GDALCopyWords(pabyWarpBandBuffer + static_cast(iY) * + nXSize * nWarpDTSize, + psWO->eWorkingDataType, nWarpDTSize, + pabyDstBand + iY * nLineSpace, eBufType, + static_cast(nPixelSpace), nXSize); + } + } + } + + m_poWarper->DestroyDestinationBuffer(pabyWarpBuffer); + + return eErr; +} + /************************************************************************/ /* AddBand() */ /************************************************************************/ @@ -1929,6 +2132,34 @@ CPLErr VRTWarpedRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, return CE_None; } +/************************************************************************/ +/* IRasterIO() */ +/************************************************************************/ + +CPLErr VRTWarpedRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, + int nXSize, int nYSize, void *pData, + int nBufXSize, int nBufYSize, + GDALDataType eBufType, + GSpacing nPixelSpace, GSpacing nLineSpace, + GDALRasterIOExtraArg *psExtraArg) +{ + VRTWarpedDataset *poWDS = static_cast(poDS); + if (m_nIRasterIOCounter == 0 && poWDS->GetRasterCount() == 1) + { + int anBandMap[] = {nBand}; + ++m_nIRasterIOCounter; + const CPLErr eErr = poWDS->IRasterIO( + eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, + eBufType, 1, anBandMap, nPixelSpace, nLineSpace, 0, psExtraArg); + --m_nIRasterIOCounter; + return eErr; + } + + return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nPixelSpace, nLineSpace, psExtraArg); +} + /************************************************************************/ /* SerializeToXML() */ /************************************************************************/ diff --git a/gcore/gdal.h b/gcore/gdal.h index e98afdc0dd52..0c6f05f25c63 100644 --- a/gcore/gdal.h +++ b/gcore/gdal.h @@ -744,6 +744,17 @@ typedef struct GDALDimensionHS *GDALDimensionH; */ #define GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE "DCAP_FLUSHCACHE_CONSISTENT_STATE" +/** Capability set by drivers which honor the OGRCoordinatePrecision settings + * of geometry fields at layer creation and/or for OGRLayer::CreateGeomField(). + * Note that while those drivers honor the settings at feature writing time, + * they might not be able to store the precision settings in layer metadata, + * hence on reading it might not be possible to recover the precision with + * which coordinates have been written. + * @since GDAL 3.9 + */ +#define GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION \ + "DCAP_HONOR_GEOM_COORDINATE_PRECISION" + /** List of (space separated) flags indicating the features of relationships are * supported by the driver. * @@ -1238,6 +1249,10 @@ OGRErr CPL_DLL GDALDatasetDeleteLayer(GDALDatasetH, int); OGRLayerH CPL_DLL GDALDatasetCreateLayer(GDALDatasetH, const char *, OGRSpatialReferenceH, OGRwkbGeometryType, CSLConstList); +OGRLayerH CPL_DLL GDALDatasetCreateLayerFromGeomFieldDefn(GDALDatasetH, + const char *, + OGRGeomFieldDefnH, + CSLConstList); OGRLayerH CPL_DLL GDALDatasetCopyLayer(GDALDatasetH, OGRLayerH, const char *, CSLConstList); void CPL_DLL GDALDatasetResetReading(GDALDatasetH); @@ -1705,6 +1720,8 @@ void CPL_DLL GDALDeinterleave(const void *pSourceBuffer, GDALDataType eSourceDT, int nComponents, void **ppDestBuffer, GDALDataType eDestDT, size_t nIters); +double CPL_DLL GDALGetNoDataReplacementValue(GDALDataType, double); + int CPL_DLL CPL_STDCALL GDALLoadWorldFile(const char *, double *); int CPL_DLL CPL_STDCALL GDALReadWorldFile(const char *, const char *, double *); int CPL_DLL CPL_STDCALL GDALWriteWorldFile(const char *, const char *, diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index b219a9437154..01a22cb7e403 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -54,6 +54,7 @@ #include "gdal.h" #include "gdal_mdreader.h" #include "gdal_priv.h" +#include "gdal_priv_templates.hpp" #include "ogr_core.h" #include "ogr_spatialref.h" #include "ogr_geos.h" @@ -1345,6 +1346,118 @@ int CPL_STDCALL GDALGetRandomRasterSample(GDALRasterBandH hBand, int nSamples, return nActualSamples; } +/************************************************************************/ +/* gdal::GCP */ +/************************************************************************/ + +namespace gdal +{ +/** Constructor. */ +GCP::GCP(const char *pszId, const char *pszInfo, double dfPixel, double dfLine, + double dfX, double dfY, double dfZ) + : gcp{CPLStrdup(pszId ? pszId : ""), + CPLStrdup(pszInfo ? pszInfo : ""), + dfPixel, + dfLine, + dfX, + dfY, + dfZ} +{ + static_assert(sizeof(GCP) == sizeof(GDAL_GCP)); +} + +/** Destructor. */ +GCP::~GCP() +{ + CPLFree(gcp.pszId); + CPLFree(gcp.pszInfo); +} + +/** Constructor from a C GDAL_GCP instance. */ +GCP::GCP(const GDAL_GCP &other) + : gcp{CPLStrdup(other.pszId), + CPLStrdup(other.pszInfo), + other.dfGCPPixel, + other.dfGCPLine, + other.dfGCPX, + other.dfGCPY, + other.dfGCPZ} +{ +} + +/** Copy constructor. */ +GCP::GCP(const GCP &other) : GCP(other.gcp) +{ +} + +/** Move constructor. */ +GCP::GCP(GCP &&other) + : gcp{other.gcp.pszId, other.gcp.pszInfo, other.gcp.dfGCPPixel, + other.gcp.dfGCPLine, other.gcp.dfGCPX, other.gcp.dfGCPY, + other.gcp.dfGCPZ} +{ + other.gcp.pszId = nullptr; + other.gcp.pszInfo = nullptr; +} + +/** Copy assignment operator. */ +GCP &GCP::operator=(const GCP &other) +{ + if (this != &other) + { + CPLFree(gcp.pszId); + CPLFree(gcp.pszInfo); + gcp = other.gcp; + gcp.pszId = CPLStrdup(other.gcp.pszId); + gcp.pszInfo = CPLStrdup(other.gcp.pszInfo); + } + return *this; +} + +/** Move assignment operator. */ +GCP &GCP::operator=(GCP &&other) +{ + if (this != &other) + { + CPLFree(gcp.pszId); + CPLFree(gcp.pszInfo); + gcp = other.gcp; + other.gcp.pszId = nullptr; + other.gcp.pszInfo = nullptr; + } + return *this; +} + +/** Set the 'id' member of the GCP. */ +void GCP::SetId(const char *pszId) +{ + CPLFree(gcp.pszId); + gcp.pszId = CPLStrdup(pszId ? pszId : ""); +} + +/** Set the 'info' member of the GCP. */ +void GCP::SetInfo(const char *pszInfo) +{ + CPLFree(gcp.pszInfo); + gcp.pszInfo = CPLStrdup(pszInfo ? pszInfo : ""); +} + +/** Cast a vector of gdal::GCP as a C array of GDAL_GCP. */ +/*static */ +const GDAL_GCP *GCP::c_ptr(const std::vector &asGCPs) +{ + return asGCPs.empty() ? nullptr : asGCPs.front().c_ptr(); +} + +/** Creates a vector of GDAL::GCP from a C array of GDAL_GCP. */ +/*static*/ +std::vector GCP::fromC(const GDAL_GCP *pasGCPList, int nGCPCount) +{ + return std::vector(pasGCPList, pasGCPList + nGCPCount); +} + +} /* namespace gdal */ + /************************************************************************/ /* GDALInitGCPs() */ /************************************************************************/ @@ -3490,8 +3603,10 @@ int CPL_STDCALL GDALGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv, /*ok*/ printf( " Supports: Creating geometry fields with NOT NULL " "constraint.\n"); - if (CPLFetchBool(papszMD, GDAL_DCAP_NONSPATIAL, false)) - printf(" No support for geometries.\n"); /*ok*/ + if (CPLFetchBool(papszMD, GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, + false)) + /*ok*/ printf(" Supports: Writing geometries with given " + "coordinate precision\n"); if (CPLFetchBool(papszMD, GDAL_DCAP_FEATURE_STYLES_READ, false)) printf(" Supports: Reading feature styles.\n"); /*ok*/ if (CPLFetchBool(papszMD, GDAL_DCAP_FEATURE_STYLES_WRITE, false)) @@ -4056,8 +4171,8 @@ CPL_C_END /* GDALSerializeGCPListToXML() */ /************************************************************************/ -void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, GDAL_GCP *pasGCPList, - int nGCPCount, +void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, + const std::vector &asGCPs, const OGRSpatialReference *poGCP_SRS) { CPLString oFmt; @@ -4087,10 +4202,8 @@ void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, GDAL_GCP *pasGCPList, psLastChild = psPamGCPList->psChild->psNext; } - for (int iGCP = 0; iGCP < nGCPCount; iGCP++) + for (const gdal::GCP &gcp : asGCPs) { - GDAL_GCP *psGCP = pasGCPList + iGCP; - CPLXMLNode *psXMLGCP = CPLCreateXMLNode(nullptr, CXT_Element, "GCP"); if (psLastChild == nullptr) @@ -4099,25 +4212,23 @@ void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, GDAL_GCP *pasGCPList, psLastChild->psNext = psXMLGCP; psLastChild = psXMLGCP; - CPLSetXMLValue(psXMLGCP, "#Id", psGCP->pszId); + CPLSetXMLValue(psXMLGCP, "#Id", gcp.Id()); - if (psGCP->pszInfo != nullptr && strlen(psGCP->pszInfo) > 0) - CPLSetXMLValue(psXMLGCP, "Info", psGCP->pszInfo); + if (gcp.Info() != nullptr && strlen(gcp.Info()) > 0) + CPLSetXMLValue(psXMLGCP, "Info", gcp.Info()); - CPLSetXMLValue(psXMLGCP, "#Pixel", - oFmt.Printf("%.4f", psGCP->dfGCPPixel)); + CPLSetXMLValue(psXMLGCP, "#Pixel", oFmt.Printf("%.4f", gcp.Pixel())); - CPLSetXMLValue(psXMLGCP, "#Line", - oFmt.Printf("%.4f", psGCP->dfGCPLine)); + CPLSetXMLValue(psXMLGCP, "#Line", oFmt.Printf("%.4f", gcp.Line())); - CPLSetXMLValue(psXMLGCP, "#X", oFmt.Printf("%.12E", psGCP->dfGCPX)); + CPLSetXMLValue(psXMLGCP, "#X", oFmt.Printf("%.12E", gcp.X())); - CPLSetXMLValue(psXMLGCP, "#Y", oFmt.Printf("%.12E", psGCP->dfGCPY)); + CPLSetXMLValue(psXMLGCP, "#Y", oFmt.Printf("%.12E", gcp.Y())); /* Note: GDAL 1.10.1 and older generated #GCPZ, but could not read it * back */ - if (psGCP->dfGCPZ != 0.0) - CPLSetXMLValue(psXMLGCP, "#Z", oFmt.Printf("%.12E", psGCP->dfGCPZ)); + if (gcp.Z() != 0.0) + CPLSetXMLValue(psXMLGCP, "#Z", oFmt.Printf("%.12E", gcp.Z())); } } @@ -4126,7 +4237,7 @@ void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, GDAL_GCP *pasGCPList, /************************************************************************/ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, - GDAL_GCP **ppasGCPList, int *pnGCPCount, + std::vector &asGCPs, OGRSpatialReference **ppoGCP_SRS) { if (ppoGCP_SRS) @@ -4165,42 +4276,16 @@ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, } } - // Count GCPs. - int nGCPMax = 0; - - for (const CPLXMLNode *psXMLGCP = psGCPList->psChild; psXMLGCP != nullptr; + asGCPs.clear(); + for (const CPLXMLNode *psXMLGCP = psGCPList->psChild; psXMLGCP; psXMLGCP = psXMLGCP->psNext) { - if (!EQUAL(psXMLGCP->pszValue, "GCP") || psXMLGCP->eType != CXT_Element) continue; - nGCPMax++; - } - - *ppasGCPList = static_cast( - nGCPMax ? CPLCalloc(sizeof(GDAL_GCP), nGCPMax) : nullptr); - *pnGCPCount = 0; - - if (nGCPMax == 0) - return; - - for (const CPLXMLNode *psXMLGCP = psGCPList->psChild; - *ppasGCPList != nullptr && psXMLGCP != nullptr; - psXMLGCP = psXMLGCP->psNext) - { - GDAL_GCP *psGCP = *ppasGCPList + *pnGCPCount; - - if (!EQUAL(psXMLGCP->pszValue, "GCP") || psXMLGCP->eType != CXT_Element) - continue; - - GDALInitGCPs(1, psGCP); - - CPLFree(psGCP->pszId); - psGCP->pszId = CPLStrdup(CPLGetXMLValue(psXMLGCP, "Id", "")); - - CPLFree(psGCP->pszInfo); - psGCP->pszInfo = CPLStrdup(CPLGetXMLValue(psXMLGCP, "Info", "")); + gdal::GCP gcp; + gcp.SetId(CPLGetXMLValue(psXMLGCP, "Id", "")); + gcp.SetInfo(CPLGetXMLValue(psXMLGCP, "Info", "")); const auto ParseDoubleValue = [psXMLGCP](const char *pszParameter, double &dfVal) @@ -4225,13 +4310,13 @@ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, }; bool bOK = true; - if (!ParseDoubleValue("Pixel", psGCP->dfGCPPixel)) + if (!ParseDoubleValue("Pixel", gcp.Pixel())) bOK = false; - if (!ParseDoubleValue("Line", psGCP->dfGCPLine)) + if (!ParseDoubleValue("Line", gcp.Line())) bOK = false; - if (!ParseDoubleValue("X", psGCP->dfGCPX)) + if (!ParseDoubleValue("X", gcp.X())) bOK = false; - if (!ParseDoubleValue("Y", psGCP->dfGCPY)) + if (!ParseDoubleValue("Y", gcp.Y())) bOK = false; const char *pszZ = CPLGetXMLValue(psXMLGCP, "Z", nullptr); if (pszZ == nullptr) @@ -4241,7 +4326,7 @@ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, pszZ = CPLGetXMLValue(psXMLGCP, "GCPZ", "0.0"); } char *endptr = nullptr; - psGCP->dfGCPZ = CPLStrtod(pszZ, &endptr); + gcp.Z() = CPLStrtod(pszZ, &endptr); if (endptr == pszZ) { CPLError(CE_Failure, CPLE_AppDefined, @@ -4249,13 +4334,9 @@ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, bOK = false; } - if (!bOK) + if (bOK) { - GDALDeinitGCPs(1, psGCP); - } - else - { - (*pnGCPCount)++; + asGCPs.emplace_back(std::move(gcp)); } } } @@ -4828,3 +4909,202 @@ std::string GDALGetCompressionFormatForJPEG(const void *pBuffer, } //! @endcond + +/************************************************************************/ +/* GDALGetNoDataReplacementValue() */ +/************************************************************************/ + +/** + * \brief Returns a replacement value for a nodata value or 0 if dfNoDataValue + * is out of range for the specified data type (dt). + * For UInt64 and Int64 data type this function cannot reliably trusted + * because their nodata values might not always be representable exactly + * as a double, in particular the maximum absolute value for those types + * is 2^53. + * + * The replacement value is a value that can be used in a computation + * whose result would match by accident the nodata value, whereas it is + * meant to be valid. For example, for a dataset with a nodata value of 0, + * when averaging -1 and 1, one would get normally a value of 0. The + * replacement nodata value can then be substituted to that 0 value to still + * get a valid value, as close as practical to the true value, while being + * different from the nodata value. + * + * @param dt Data type + * @param dfNoDataValue The no data value + + * @since GDAL 3.9 + */ +double GDALGetNoDataReplacementValue(GDALDataType dt, double dfNoDataValue) +{ + + // The logic here is to check if the value is out of range for the + // specified data type and return a replacement value if it is, return + // 0 otherwise. + double dfReplacementVal = dfNoDataValue; + if (dt == GDT_Byte) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_Int8) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_UInt16) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_Int16) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_UInt32) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_Int32) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + if (dfNoDataValue == std::numeric_limits::max()) + dfReplacementVal = std::numeric_limits::max() - 1; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_UInt64) + { + // Implicit conversion from 'unsigned long' to 'double' changes value from 18446744073709551615 to 18446744073709551616 + // so we take the next lower value representable as a double 18446744073709549567 + static const double dfMaxUInt64Value{ + std::nextafter( + static_cast(std::numeric_limits::max()), 0) - + 1}; + + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + + if (dfNoDataValue >= + static_cast(std::numeric_limits::max())) + dfReplacementVal = dfMaxUInt64Value; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_Int64) + { + // Implicit conversion from 'long' to 'double' changes value from 9223372036854775807 to 9223372036854775808 + // so we take the next lower value representable as a double 9223372036854774784 + static const double dfMaxInt64Value{ + std::nextafter( + static_cast(std::numeric_limits::max()), 0) - + 1}; + + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + + if (dfNoDataValue >= + static_cast(std::numeric_limits::max())) + dfReplacementVal = dfMaxInt64Value; + else + dfReplacementVal = dfNoDataValue + 1; + } + else if (dt == GDT_Float32) + { + + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + + if (dfNoDataValue == std::numeric_limits::max()) + { + dfReplacementVal = + std::nextafter(static_cast(dfNoDataValue), 0.0f); + } + else + { + dfReplacementVal = + std::nextafter(static_cast(dfNoDataValue), + std::numeric_limits::max()); + } + } + else if (dt == GDT_Float64) + { + if (GDALClampDoubleValue(dfNoDataValue, + std::numeric_limits::lowest(), + std::numeric_limits::max())) + { + return 0; + } + + if (dfNoDataValue == std::numeric_limits::max()) + { + dfReplacementVal = std::nextafter(dfNoDataValue, 0.0f); + } + else + { + dfReplacementVal = std::nextafter( + dfNoDataValue, std::numeric_limits::max()); + } + } + + return dfReplacementVal; +} diff --git a/gcore/gdal_pam.h b/gcore/gdal_pam.h index 73ae1e25a939..c75fd8019563 100644 --- a/gcore/gdal_pam.h +++ b/gcore/gdal_pam.h @@ -97,8 +97,7 @@ class GDALDatasetPamInfo int bHaveGeoTransform = false; double adfGeoTransform[6]{0, 0, 0, 0, 0, 0}; - int nGCPCount = 0; - GDAL_GCP *pasGCPList = nullptr; + std::vector asGCPs{}; OGRSpatialReference *poGCP_SRS = nullptr; CPLString osPhysicalFilename{}; diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index 0368de11c838..809f3cd88c63 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -321,6 +321,122 @@ class CPL_DLL GDALOpenInfo CPL_DISALLOW_COPY_ASSIGN(GDALOpenInfo) }; +/* ******************************************************************** */ +/* gdal::GCP */ +/* ******************************************************************** */ + +namespace gdal +{ +/** C++ wrapper over the C GDAL_GCP structure. + * + * It has the same binary layout, and thus a gdal::GCP pointer can be cast as a + * GDAL_GCP pointer. + * + * @since 3.9 + */ +class CPL_DLL GCP +{ + public: + explicit GCP(const char *pszId = "", const char *pszInfo = "", + double dfPixel = 0, double dfLine = 0, double dfX = 0, + double dfY = 0, double dfZ = 0); + ~GCP(); + GCP(const GCP &); + explicit GCP(const GDAL_GCP &other); + GCP &operator=(const GCP &); + GCP(GCP &&); + GCP &operator=(GCP &&); + + /** Returns the "id" member. */ + inline const char *Id() const + { + return gcp.pszId; + } + void SetId(const char *pszId); + + /** Returns the "info" member. */ + inline const char *Info() const + { + return gcp.pszInfo; + } + void SetInfo(const char *pszInfo); + + /** Returns the "pixel" member. */ + inline double Pixel() const + { + return gcp.dfGCPPixel; + } + + /** Returns a reference to the "pixel" member. */ + inline double &Pixel() + { + return gcp.dfGCPPixel; + } + + /** Returns the "line" member. */ + inline double Line() const + { + return gcp.dfGCPLine; + } + + /** Returns a reference to the "line" member. */ + inline double &Line() + { + return gcp.dfGCPLine; + } + + /** Returns the "X" member. */ + inline double X() const + { + return gcp.dfGCPX; + } + + /** Returns a reference to the "X" member. */ + inline double &X() + { + return gcp.dfGCPX; + } + + /** Returns the "Y" member. */ + inline double Y() const + { + return gcp.dfGCPY; + } + + /** Returns a reference to the "Y" member. */ + inline double &Y() + { + return gcp.dfGCPY; + } + + /** Returns the "Z" member. */ + inline double Z() const + { + return gcp.dfGCPZ; + } + + /** Returns a reference to the "Z" member. */ + inline double &Z() + { + return gcp.dfGCPZ; + } + + /** Casts as a C GDAL_GCP pointer */ + inline const GDAL_GCP *c_ptr() const + { + return &gcp; + } + + static const GDAL_GCP *c_ptr(const std::vector &asGCPs); + + static std::vector fromC(const GDAL_GCP *pasGCPList, int nGCPCount); + + private: + GDAL_GCP gcp; +}; + +} /* namespace gdal */ + /* ******************************************************************** */ /* GDALDataset */ /* ******************************************************************** */ @@ -891,9 +1007,21 @@ class CPL_DLL GDALDataset : public GDALMajorObject UpdateRelationship(std::unique_ptr &&relationship, std::string &failureReason); - virtual OGRLayer *CreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, char **papszOptions = nullptr); + //! @cond Doxygen_Suppress + OGRLayer *CreateLayer(const char *pszName); + + OGRLayer *CreateLayer(const char *pszName, std::nullptr_t); + //! @endcond + + OGRLayer *CreateLayer(const char *pszName, + const OGRSpatialReference *poSpatialRef, + OGRwkbGeometryType eGType = wkbUnknown, + CSLConstList papszOptions = nullptr); + + OGRLayer *CreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions = nullptr); + virtual OGRLayer *CopyLayer(OGRLayer *poSrcLayer, const char *pszNewName, char **papszOptions = nullptr); @@ -932,9 +1060,9 @@ class CPL_DLL GDALDataset : public GDALMajorObject //! @endcond protected: - virtual OGRLayer *ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, char **papszOptions = nullptr); + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions); //! @cond Doxygen_Suppress OGRErr ProcessSQLCreateIndex(const char *); @@ -4053,11 +4181,11 @@ double GDALAdjustNoDataCloseToFloatMax(double dfVal); // (minimum value, maximum value, etc.) #define GDALSTAT_APPROX_NUMSAMPLES 2500 -void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, GDAL_GCP *pasGCPList, - int nGCPCount, +void GDALSerializeGCPListToXML(CPLXMLNode *psParentNode, + const std::vector &asGCPs, const OGRSpatialReference *poGCP_SRS); void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, - GDAL_GCP **ppasGCPList, int *pnGCPCount, + std::vector &asGCPs, OGRSpatialReference **ppoGCP_SRS); void GDALSerializeOpenOptionsToXML(CPLXMLNode *psParentNode, diff --git a/gcore/gdal_priv_templates.hpp b/gcore/gdal_priv_templates.hpp index 337f6e8317a1..80a988dcdf3f 100644 --- a/gcore/gdal_priv_templates.hpp +++ b/gcore/gdal_priv_templates.hpp @@ -102,6 +102,34 @@ inline T GDALClampValue(const T tValue, const T tMax, const T tMin) return tValue > tMax ? tMax : tValue < tMin ? tMin : tValue; } +/************************************************************************/ +/* GDALClampDoubleValue() */ +/************************************************************************/ +/** + * Clamp double values to a specified range, this uses the same + * argument ordering as std::clamp, returns TRUE if the value was clamped. + * + * @param tValue the value + * @param tMin the min value + * @param tMax the max value + * + */ +template +inline bool GDALClampDoubleValue(double &tValue, const T2 tMin, const T3 tMax) +{ + const double tMin2{static_cast(tMin)}; + const double tMax2{static_cast(tMax)}; + if (tValue > tMax2 || tValue < tMin2) + { + tValue = tValue > tMax2 ? tMax2 : tValue < tMin2 ? tMin2 : tValue; + return true; + } + else + { + return false; + } +} + /************************************************************************/ /* GDALIsValueInRange() */ /************************************************************************/ @@ -114,7 +142,7 @@ inline T GDALClampValue(const T tValue, const T tMax, const T tMin) */ template inline bool GDALIsValueInRange(double dfValue) { - return dfValue >= static_cast(std::numeric_limits::min()) && + return dfValue >= static_cast(std::numeric_limits::lowest()) && dfValue <= static_cast(std::numeric_limits::max()); } diff --git a/gcore/gdaldataset.cpp b/gcore/gdaldataset.cpp index 38503ec8d697..8ffe1a75ef6d 100644 --- a/gcore/gdaldataset.cpp +++ b/gcore/gdaldataset.cpp @@ -4944,7 +4944,70 @@ specific. OGRLayer *GDALDataset::CreateLayer(const char *pszName, const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType, - char **papszOptions) + CSLConstList papszOptions) + +{ + if (eGType == wkbNone) + { + return CreateLayer(pszName, nullptr, papszOptions); + } + else + { + OGRGeomFieldDefn oGeomFieldDefn("", eGType); + oGeomFieldDefn.SetSpatialRef(poSpatialRef); + return CreateLayer(pszName, &oGeomFieldDefn, papszOptions); + } +} + +/** +\brief This method attempts to create a new layer on the dataset with the +indicated name and geometry field definition. + +When poGeomFieldDefn is not null, most drivers should honor +poGeomFieldDefn->GetType() and poGeomFieldDefn->GetSpatialRef(). +Drivers that honor poGeomFieldDefn->GetCoordinatePrecision() will declare the +GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability. Drivers may honor +poGeomFieldDefn->GetNameRef() and poGeomFieldDefn->IsNullable(), but there are +very few currently. + +Note that even if a geometry coordinate precision is set and a driver honors the +GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, geometries passed to +OGRLayer::CreateFeature() and OGRLayer::SetFeature() are assumed to be compatible +with the coordinate precision. That is they are assumed to be valid once their +coordinates are rounded to it. If it might not be the case, the user may set +the OGR_APPLY_GEOM_SET_PRECISION configuration option before calling CreateFeature() +or SetFeature() to force the OGRGeometry::SetPrecision() method to be called on +the passed geometries. + +The papszOptions argument +can be used to control driver specific creation options. These options are +normally documented in the format specific documentation. +This function will try to validate the creation option list passed to the +driver with the GDALValidateCreationOptions() method. This check can be +disabled by defining the configuration option GDAL_VALIDATE_CREATION_OPTIONS set +to NO. + +Drivers should extend the ICreateLayer() method and not +CreateLayer(). CreateLayer() adds validation of layer creation options, before +delegating the actual work to ICreateLayer(). + +This method is the same as the C function GDALDatasetCreateLayerFromGeomFieldDefn(). + +@param pszName the name for the new layer. This should ideally not +match any existing layer on the datasource. +@param poGeomFieldDefn the geometry field definition to use for the new layer, +or NULL if there is no geometry field. +@param papszOptions a StringList of name=value options. Options are driver +specific. + +@return NULL is returned on failure, or a new OGRLayer handle on success. +@since 3.9 + +*/ + +OGRLayer *GDALDataset::CreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (CPLTestBool( @@ -4953,13 +5016,23 @@ OGRLayer *GDALDataset::CreateLayer(const char *pszName, ValidateLayerCreationOptions(papszOptions); } - if (OGR_GT_IsNonLinear(eGType) && !TestCapability(ODsCCurveGeometries)) + OGRLayer *poLayer; + if (poGeomFieldDefn) { - eGType = OGR_GT_GetLinear(eGType); - } + OGRGeomFieldDefn oGeomFieldDefn(poGeomFieldDefn); + if (OGR_GT_IsNonLinear(poGeomFieldDefn->GetType()) && + !TestCapability(ODsCCurveGeometries)) + { + oGeomFieldDefn.SetType( + OGR_GT_GetLinear(poGeomFieldDefn->GetType())); + } - OGRLayer *poLayer = - ICreateLayer(pszName, poSpatialRef, eGType, papszOptions); + poLayer = ICreateLayer(pszName, &oGeomFieldDefn, papszOptions); + } + else + { + poLayer = ICreateLayer(pszName, nullptr, papszOptions); + } #ifdef DEBUG if (poLayer != nullptr && OGR_GT_IsNonLinear(poLayer->GetGeomType()) && !poLayer->TestCapability(OLCCurveGeometries)) @@ -4973,6 +5046,25 @@ OGRLayer *GDALDataset::CreateLayer(const char *pszName, return poLayer; } +//! @cond Doxygen_Suppress + +// Technical override to avoid ambiguous choice between the old and new +// new CreateLayer() signatures. +OGRLayer *GDALDataset::CreateLayer(const char *pszName) +{ + OGRGeomFieldDefn oGeomFieldDefn("", wkbUnknown); + return CreateLayer(pszName, &oGeomFieldDefn, nullptr); +} + +// Technical override to avoid ambiguous choice between the old and new +// new CreateLayer() signatures. +OGRLayer *GDALDataset::CreateLayer(const char *pszName, std::nullptr_t) +{ + OGRGeomFieldDefn oGeomFieldDefn("", wkbUnknown); + return CreateLayer(pszName, &oGeomFieldDefn, nullptr); +} +//!@endcond + /************************************************************************/ /* GDALDatasetCreateLayer() */ /************************************************************************/ @@ -5059,6 +5151,73 @@ OGRLayerH GDALDatasetCreateLayer(GDALDatasetH hDS, const char *pszName, return hLayer; } +/************************************************************************/ +/* GDALDatasetCreateLayerFromGeomFieldDefn() */ +/************************************************************************/ + +/** +\brief This function attempts to create a new layer on the dataset with the +indicated name and geometry field. + +When poGeomFieldDefn is not null, most drivers should honor +poGeomFieldDefn->GetType() and poGeomFieldDefn->GetSpatialRef(). +Drivers that honor poGeomFieldDefn->GetCoordinatePrecision() will declare the +GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability. Drivers may honor +poGeomFieldDefn->GetNameRef() and poGeomFieldDefn->IsNullable(), but there are +very few currently. + +Note that even if a geometry coordinate precision is set and a driver honors the +GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, geometries passed to +OGRLayer::CreateFeature() and OGRLayer::SetFeature() are assumed to be compatible +with the coordinate precision. That is they are assumed to be valid once their +coordinates are rounded to it. If it might not be the case, the user may set +the OGR_APPLY_GEOM_SET_PRECISION configuration option before calling CreateFeature() +or SetFeature() to force the OGRGeometry::SetPrecision() method to be called on +the passed geometries. + +The papszOptions argument can be used to control driver specific creation +options. These options are normally documented in the format specific +documentation. + +This method is the same as the C++ method GDALDataset::CreateLayer(). + +@param hDS the dataset handle +@param pszName the name for the new layer. This should ideally not +match any existing layer on the datasource. +@param hGeomFieldDefn the geometry field definition. May be NULL to indicate +a non-spatial file (or if adding geometry fields later with OGR_L_CreateGeomField() +for drivers supporting that interface). +@param papszOptions a StringList of name=value options. Options are driver +specific. + +@return NULL is returned on failure, or a new OGRLayer handle on success. + +@since GDAL 3.9 + +*/ + +OGRLayerH +GDALDatasetCreateLayerFromGeomFieldDefn(GDALDatasetH hDS, const char *pszName, + OGRGeomFieldDefnH hGeomFieldDefn, + CSLConstList papszOptions) + +{ + VALIDATE_POINTER1(hDS, "GDALDatasetCreateLayerFromGeomFieldDefn", nullptr); + + if (!pszName) + { + CPLError(CE_Failure, CPLE_ObjectNull, + "Name was NULL in GDALDatasetCreateLayerFromGeomFieldDefn"); + return nullptr; + } + + OGRLayerH hLayer = + OGRLayer::ToHandle(GDALDataset::FromHandle(hDS)->CreateLayer( + pszName, OGRGeomFieldDefn::FromHandle(hGeomFieldDefn), + papszOptions)); + return hLayer; +} + /************************************************************************/ /* GDALDatasetCopyLayer() */ /************************************************************************/ @@ -5383,23 +5542,20 @@ int GDALDataset::GetSummaryRefCount() const @param pszName the name for the new layer. This should ideally not match any existing layer on the datasource. - @param poSpatialRef the coordinate system to use for the new layer, or NULL if - no coordinate system is available. - @param eGType the geometry type for the layer. Use wkbUnknown if there - are no constraints on the types geometry to be written. + @param poGeomFieldDefn the geometry field definition to use for the new layer, + or NULL if there is no geometry field. @param papszOptions a StringList of name=value options. Options are driver specific. @return NULL is returned on failure, or a new OGRLayer handle on success. - @since GDAL 2.0 + @since GDAL 2.0 (prototype modified in 3.9) */ OGRLayer * GDALDataset::ICreateLayer(CPL_UNUSED const char *pszName, - CPL_UNUSED const OGRSpatialReference *poSpatialRef, - CPL_UNUSED OGRwkbGeometryType eGType, - CPL_UNUSED char **papszOptions) + CPL_UNUSED const OGRGeomFieldDefn *poGeomFieldDefn, + CPL_UNUSED CSLConstList papszOptions) { CPLError(CE_Failure, CPLE_NotSupported, @@ -5464,17 +5620,19 @@ OGRLayer *GDALDataset::CopyLayer(OGRLayer *poSrcLayer, const char *pszNewName, aosCleanedUpOptions.SetNameValue("COPY_MD", nullptr); CPLErrorReset(); - if (poSrcDefn->GetGeomFieldCount() > 1 && - TestCapability(ODsCCreateGeomFieldAfterCreateLayer)) + const int nSrcGeomFieldCount = poSrcDefn->GetGeomFieldCount(); + if (nSrcGeomFieldCount == 1) { - poDstLayer = ICreateLayer(pszNewName, nullptr, wkbNone, + OGRGeomFieldDefn oGeomFieldDefn(poSrcDefn->GetGeomFieldDefn(0)); + if (pszSRSWKT) + oGeomFieldDefn.SetSpatialRef(&oDstSpaRef); + poDstLayer = ICreateLayer(pszNewName, &oGeomFieldDefn, aosCleanedUpOptions.List()); } else { - poDstLayer = ICreateLayer( - pszNewName, pszSRSWKT ? &oDstSpaRef : poSrcLayer->GetSpatialRef(), - poSrcDefn->GetGeomType(), aosCleanedUpOptions.List()); + poDstLayer = + ICreateLayer(pszNewName, nullptr, aosCleanedUpOptions.List()); } if (poDstLayer == nullptr) @@ -5555,7 +5713,6 @@ OGRLayer *GDALDataset::CopyLayer(OGRLayer *poSrcLayer, const char *pszNewName, /* -------------------------------------------------------------------- */ /* Create geometry fields. */ /* -------------------------------------------------------------------- */ - const int nSrcGeomFieldCount = poSrcDefn->GetGeomFieldCount(); if (nSrcGeomFieldCount > 1 && TestCapability(ODsCCreateGeomFieldAfterCreateLayer)) { diff --git a/gcore/gdalmultidim_subsetdimension.cpp b/gcore/gdalmultidim_subsetdimension.cpp index 0c5a0f77bd8f..9fb9dfc10758 100644 --- a/gcore/gdalmultidim_subsetdimension.cpp +++ b/gcore/gdalmultidim_subsetdimension.cpp @@ -371,7 +371,7 @@ bool GDALSubsetArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count, if (arrayStep[0] > 0) arrayIdx += arrayStep[0]; else - arrayIdx -= -arrayStep[0]; + arrayIdx -= static_cast(-arrayStep[0]); pabyDstBuffer += bufferStride[0] * nBufferDTSize; } return true; diff --git a/gcore/gdalpamdataset.cpp b/gcore/gdalpamdataset.cpp index 1f0c326762e1..44ebedd932f8 100644 --- a/gcore/gdalpamdataset.cpp +++ b/gcore/gdalpamdataset.cpp @@ -298,10 +298,9 @@ CPLXMLNode *GDALPamDataset::SerializeToXML(const char *pszUnused) /* -------------------------------------------------------------------- */ /* GCPs */ /* -------------------------------------------------------------------- */ - if (psPam->nGCPCount > 0) + if (!psPam->asGCPs.empty()) { - GDALSerializeGCPListToXML(psDSTree, psPam->pasGCPList, psPam->nGCPCount, - psPam->poGCP_SRS); + GDALSerializeGCPListToXML(psDSTree, psPam->asGCPs, psPam->poGCP_SRS); } /* -------------------------------------------------------------------- */ @@ -405,11 +404,6 @@ void GDALPamDataset::PamClear() psPam->poSRS->Release(); if (psPam->poGCP_SRS) psPam->poGCP_SRS->Release(); - if (psPam->nGCPCount > 0) - { - GDALDeinitGCPs(psPam->nGCPCount, psPam->pasGCPList); - CPLFree(psPam->pasGCPList); - } delete psPam; psPam = nullptr; @@ -491,16 +485,9 @@ CPLErr GDALPamDataset::XMLInit(const CPLXMLNode *psTree, const char *pszUnused) // Make sure any previous GCPs, perhaps from an .aux file, are cleared // if we have new ones. - if (psPam->nGCPCount > 0) - { - GDALDeinitGCPs(psPam->nGCPCount, psPam->pasGCPList); - CPLFree(psPam->pasGCPList); - psPam->nGCPCount = 0; - psPam->pasGCPList = nullptr; - } - - GDALDeserializeGCPListFromXML(psGCPList, &(psPam->pasGCPList), - &(psPam->nGCPCount), &(psPam->poGCP_SRS)); + psPam->asGCPs.clear(); + GDALDeserializeGCPListFromXML(psGCPList, psPam->asGCPs, + &(psPam->poGCP_SRS)); } /* -------------------------------------------------------------------- */ @@ -602,23 +589,21 @@ CPLErr GDALPamDataset::XMLInit(const CPLXMLNode *psTree, const char *pszUnused) adfSource.size() == adfTarget.size() && (adfSource.size() % 2) == 0) { - std::vector asGCPs; - asGCPs.resize(adfSource.size() / 2); - char szEmptyString[] = {0}; + std::vector asGCPs; for (size_t i = 0; i + 1 < adfSource.size(); i += 2) { - asGCPs[i / 2].pszId = szEmptyString; - asGCPs[i / 2].pszInfo = szEmptyString; - asGCPs[i / 2].dfGCPPixel = adfSource[i]; - asGCPs[i / 2].dfGCPLine = ySourceAllNegative - ? -adfSource[i + 1] - : adfSource[i + 1]; - asGCPs[i / 2].dfGCPX = adfTarget[i]; - asGCPs[i / 2].dfGCPY = adfTarget[i + 1]; - asGCPs[i / 2].dfGCPZ = 0; + asGCPs.emplace_back("", "", + /* pixel = */ adfSource[i], + /* line = */ + ySourceAllNegative + ? -adfSource[i + 1] + : adfSource[i + 1], + /* X = */ adfTarget[i], + /* Y = */ adfTarget[i + 1]); } GDALPamDataset::SetGCPs(static_cast(asGCPs.size()), - &asGCPs[0], psPam->poSRS); + gdal::GCP::c_ptr(asGCPs), + psPam->poSRS); delete psPam->poSRS; psPam->poSRS = nullptr; } @@ -1471,8 +1456,8 @@ void GDALPamDataset::DeleteGeoTransform() int GDALPamDataset::GetGCPCount() { - if (psPam && psPam->nGCPCount > 0) - return psPam->nGCPCount; + if (psPam && !psPam->asGCPs.empty()) + return static_cast(psPam->asGCPs.size()); return GDALDataset::GetGCPCount(); } @@ -1497,8 +1482,8 @@ const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const const GDAL_GCP *GDALPamDataset::GetGCPs() { - if (psPam && psPam->nGCPCount > 0) - return psPam->pasGCPList; + if (psPam && !psPam->asGCPs.empty()) + return gdal::GCP::c_ptr(psPam->asGCPs); return GDALDataset::GetGCPs(); } @@ -1517,16 +1502,8 @@ CPLErr GDALPamDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList, { if (psPam->poGCP_SRS) psPam->poGCP_SRS->Release(); - if (psPam->nGCPCount > 0) - { - GDALDeinitGCPs(psPam->nGCPCount, psPam->pasGCPList); - CPLFree(psPam->pasGCPList); - } - psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr; - psPam->nGCPCount = nGCPCount; - psPam->pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPList); - + psPam->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); MarkPamDirty(); return CE_None; @@ -1719,9 +1696,8 @@ CPLErr GDALPamDataset::TryLoadAux(char **papszSiblingFiles) /* -------------------------------------------------------------------- */ if (poAuxDS->GetGCPCount() > 0) { - psPam->nGCPCount = poAuxDS->GetGCPCount(); - psPam->pasGCPList = - GDALDuplicateGCPs(psPam->nGCPCount, poAuxDS->GetGCPs()); + psPam->asGCPs = + gdal::GCP::fromC(poAuxDS->GetGCPs(), poAuxDS->GetGCPCount()); } /* -------------------------------------------------------------------- */ diff --git a/gcore/overview.cpp b/gcore/overview.cpp index 6eb7dd57b11a..130d569d904f 100644 --- a/gcore/overview.cpp +++ b/gcore/overview.cpp @@ -245,104 +245,6 @@ std::vector ReadColorTable(const GDALColorTable &table, } // unnamed namespace -/************************************************************************/ -/* GetReplacementValueIfNoData() */ -/************************************************************************/ - -static double GetReplacementValueIfNoData(GDALDataType dt, bool bHasNoData, - double dfNoDataValue) -{ - double dfReplacementVal = 0.0f; - if (bHasNoData) - { - if (dt == GDT_Byte) - { - if (dfNoDataValue == std::numeric_limits::max()) - dfReplacementVal = - std::numeric_limits::max() - 1; - else - dfReplacementVal = dfNoDataValue + 1; - } - else if (dt == GDT_Int8) - { - if (dfNoDataValue == std::numeric_limits::max()) - dfReplacementVal = std::numeric_limits::max() - 1; - else - dfReplacementVal = dfNoDataValue + 1; - } - else if (dt == GDT_UInt16) - { - if (dfNoDataValue == std::numeric_limits::max()) - dfReplacementVal = std::numeric_limits::max() - 1; - else - dfReplacementVal = dfNoDataValue + 1; - } - else if (dt == GDT_Int16) - { - if (dfNoDataValue == std::numeric_limits::max()) - dfReplacementVal = std::numeric_limits::max() - 1; - else - dfReplacementVal = dfNoDataValue + 1; - } - else if (dt == GDT_UInt32) - { - // Be careful to limited precision of float - dfReplacementVal = dfNoDataValue + 1; - double dfVal = dfNoDataValue; - if (dfReplacementVal >= std::numeric_limits::max() - 128) - { - while (dfReplacementVal == dfNoDataValue) - { - dfVal -= 1.0; - dfReplacementVal = dfVal; - } - } - else - { - while (dfReplacementVal == dfNoDataValue) - { - dfVal += 1.0; - dfReplacementVal = dfVal; - } - } - } - else if (dt == GDT_Int32) - { - // Be careful to limited precision of float - dfReplacementVal = dfNoDataValue + 1; - double dfVal = dfNoDataValue; - if (dfReplacementVal >= std::numeric_limits::max() - 64) - { - while (dfReplacementVal == dfNoDataValue) - { - dfVal -= 1.0; - dfReplacementVal = dfVal; - } - } - else - { - while (dfReplacementVal == dfNoDataValue) - { - dfVal += 1.0; - dfReplacementVal = dfVal; - } - } - } - else if (dt == GDT_Float32 || dt == GDT_Float64) - { - if (dfNoDataValue == 0) - { - dfReplacementVal = std::numeric_limits::min(); - } - else - { - dfReplacementVal = dfNoDataValue + 1e-7 * dfNoDataValue; - } - } - } - return dfReplacementVal; -} - /************************************************************************/ /* SQUARE() */ /************************************************************************/ @@ -1229,8 +1131,10 @@ static CPLErr GDALResampleChunk_AverageOrRMS_T( tNoDataValue = 0; else tNoDataValue = static_cast(dfNoDataValue); - const T tReplacementVal = static_cast(GetReplacementValueIfNoData( - poOverview->GetRasterDataType(), bHasNoData, dfNoDataValue)); + const T tReplacementVal = + bHasNoData ? static_cast(GDALGetNoDataReplacementValue( + poOverview->GetRasterDataType(), dfNoDataValue)) + : 0; int nChunkRightXOff = nChunkXOff + nChunkXSize; int nChunkBottomYOff = nChunkYOff + nChunkYSize; @@ -3094,7 +2998,8 @@ static CPLErr GDALResampleChunk_ConvolutionT( const auto dstDataType = poDstBand->GetRasterDataType(); const int nDstDataTypeSize = GDALGetDataTypeSizeBytes(dstDataType); const double dfReplacementVal = - GetReplacementValueIfNoData(dstDataType, bHasNoData, dfNoDataValue); + bHasNoData ? GDALGetNoDataReplacementValue(dstDataType, dfNoDataValue) + : dfNoDataValue; // cppcheck-suppress unreadVariable const int isIntegerDT = GDALDataTypeIsInteger(dstDataType); const auto nNodataValueInt64 = static_cast(dfNoDataValue); diff --git a/gnm/gnm_frmts/db/gnmdb.h b/gnm/gnm_frmts/db/gnmdb.h index c12af5ae809b..c148c16516ac 100644 --- a/gnm/gnm_frmts/db/gnmdb.h +++ b/gnm/gnm_frmts/db/gnmdb.h @@ -45,11 +45,10 @@ class GNMDatabaseNetwork : public GNMGenericNetwork char **papszOptions) override; protected: - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + virtual int CheckNetworkExist(const char *pszFilename, char **papszOptions) override; diff --git a/gnm/gnm_frmts/db/gnmdbnetwork.cpp b/gnm/gnm_frmts/db/gnmdbnetwork.cpp index 7ad9ed5b1ca6..32124493f8c3 100644 --- a/gnm/gnm_frmts/db/gnmdbnetwork.cpp +++ b/gnm/gnm_frmts/db/gnmdbnetwork.cpp @@ -414,8 +414,8 @@ OGRErr GNMDatabaseNetwork::DeleteLayer(int nIndex) OGRLayer * GNMDatabaseNetwork::ICreateLayer(const char *pszName, - const OGRSpatialReference * /*poSpatialRef*/, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { // check if layer with such name exist for (int i = 0; i < GetLayerCount(); ++i) @@ -433,6 +433,7 @@ GNMDatabaseNetwork::ICreateLayer(const char *pszName, OGRSpatialReference oSpaRef(m_oSRS); + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; OGRLayer *poLayer = m_poDS->CreateLayer(pszName, &oSpaRef, eGType, papszOptions); if (poLayer == nullptr) diff --git a/gnm/gnm_frmts/file/gnmfile.h b/gnm/gnm_frmts/file/gnmfile.h index 1c5fd3636ceb..6e9b7d551168 100644 --- a/gnm/gnm_frmts/file/gnmfile.h +++ b/gnm/gnm_frmts/file/gnmfile.h @@ -49,11 +49,10 @@ class GNMFileNetwork : public GNMGenericNetwork char **papszOptions) override; protected: - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + virtual int CheckNetworkExist(const char *pszFilename, char **papszOptions) override; diff --git a/gnm/gnm_frmts/file/gnmfilenetwork.cpp b/gnm/gnm_frmts/file/gnmfilenetwork.cpp index d470c2e1b9fa..cb1a1d08fc3b 100644 --- a/gnm/gnm_frmts/file/gnmfilenetwork.cpp +++ b/gnm/gnm_frmts/file/gnmfilenetwork.cpp @@ -521,10 +521,9 @@ OGRErr GNMFileNetwork::DeleteLayer(int nIndex) return GNMGenericNetwork::DeleteLayer(nIndex); } -OGRLayer * -GNMFileNetwork::ICreateLayer(const char *pszName, - const OGRSpatialReference * /* poSpatialRef */, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer *GNMFileNetwork::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (nullptr == m_poLayerDriver) { @@ -533,6 +532,8 @@ GNMFileNetwork::ICreateLayer(const char *pszName, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + // check if layer with such name exist for (int i = 0; i < GetLayerCount(); ++i) { diff --git a/ogr/CMakeLists.txt b/ogr/CMakeLists.txt index b5ea53dbd0cb..258564dfc9d7 100644 --- a/ogr/CMakeLists.txt +++ b/ogr/CMakeLists.txt @@ -10,6 +10,7 @@ add_library( ogrpolygon.cpp ogrtriangle.cpp ogrutils.cpp + ogrgeomcoordinateprecision.cpp ogrgeometry.cpp ogrgeometrycollection.cpp ogrmultipolygon.cpp @@ -156,6 +157,7 @@ target_public_header( ogr_featurestyle.h ogr_geocoding.h ogr_geometry.h + ogr_geomcoordinateprecision.h ogr_p.h ogr_spatialref.h ogr_swq.h diff --git a/ogr/ogr2gmlgeometry.cpp b/ogr/ogr2gmlgeometry.cpp index bf88174fb00b..adf90a9999a5 100644 --- a/ogr/ogr2gmlgeometry.cpp +++ b/ogr/ogr2gmlgeometry.cpp @@ -72,10 +72,12 @@ enum GMLSRSNameFormat /************************************************************************/ static void MakeGMLCoordinate(char *pszTarget, double x, double y, double z, - bool b3D) + bool b3D, const OGRWktOptions &coordOpts) { - OGRMakeWktCoordinate(pszTarget, x, y, z, b3D ? 3 : 2); + std::string wkt = OGRMakeWktCoordinate(x, y, z, b3D ? 3 : 2, coordOpts); + memcpy(pszTarget, wkt.data(), wkt.size() + 1); + while (*pszTarget != '\0') { if (*pszTarget == ' ') @@ -117,7 +119,8 @@ static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength, /************************************************************************/ static void AppendCoordinateList(const OGRLineString *poLine, char **ppszText, - size_t *pnLength, size_t *pnMaxLength) + size_t *pnLength, size_t *pnMaxLength, + const OGRWktOptions &coordOpts) { const bool b3D = wkbHasZ(poLine->getGeometryType()) != FALSE; @@ -132,7 +135,8 @@ static void AppendCoordinateList(const OGRLineString *poLine, char **ppszText, for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++) { MakeGMLCoordinate(szCoordinate, poLine->getX(iPoint), - poLine->getY(iPoint), poLine->getZ(iPoint), b3D); + poLine->getY(iPoint), poLine->getZ(iPoint), b3D, + coordOpts); _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText, pnMaxLength); @@ -155,7 +159,8 @@ static void AppendCoordinateList(const OGRLineString *poLine, char **ppszText, static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, char **ppszText, size_t *pnLength, size_t *pnMaxLength, bool bIsSubGeometry, - const char *pszNamespaceDecl) + const char *pszNamespaceDecl, + const OGRWktOptions &coordOpts) { /* -------------------------------------------------------------------- */ @@ -207,7 +212,7 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, char szCoordinate[256] = {}; MakeGMLCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(), 0.0, - false); + false, coordOpts); _GrowBuffer(*pnLength + strlen(szCoordinate) + 60 + nAttrsLength, ppszText, pnMaxLength); @@ -228,7 +233,7 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, char szCoordinate[256] = {}; MakeGMLCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(), - poPoint->getZ(), true); + poPoint->getZ(), true, coordOpts); _GrowBuffer(*pnLength + strlen(szCoordinate) + 70 + nAttrsLength, ppszText, pnMaxLength); @@ -273,7 +278,8 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, CPLFree(pszLineTagName); const auto poLineString = poGeometry->toLineString(); - AppendCoordinateList(poLineString, ppszText, pnLength, pnMaxLength); + AppendCoordinateList(poLineString, ppszText, pnLength, pnMaxLength, + coordOpts); if (bRing) AppendString(ppszText, pnLength, pnMaxLength, ""); @@ -309,9 +315,9 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, AppendString(ppszText, pnLength, pnMaxLength, ""); - CPL_IGNORE_RET_VAL( - OGR2GMLGeometryAppend(poPolygon->getExteriorRing(), ppszText, - pnLength, pnMaxLength, true, nullptr)); + CPL_IGNORE_RET_VAL(OGR2GMLGeometryAppend( + poPolygon->getExteriorRing(), ppszText, pnLength, pnMaxLength, + true, nullptr, coordOpts)); AppendString(ppszText, pnLength, pnMaxLength, ""); @@ -324,8 +330,9 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, AppendString(ppszText, pnLength, pnMaxLength, ""); - CPL_IGNORE_RET_VAL(OGR2GMLGeometryAppend( - poRing, ppszText, pnLength, pnMaxLength, true, nullptr)); + CPL_IGNORE_RET_VAL(OGR2GMLGeometryAppend(poRing, ppszText, pnLength, + pnMaxLength, true, nullptr, + coordOpts)); AppendString(ppszText, pnLength, pnMaxLength, ""); } @@ -394,7 +401,7 @@ static bool OGR2GMLGeometryAppend(const OGRGeometry *poGeometry, AppendString(ppszText, pnLength, pnMaxLength, pszMemberElem); if (!OGR2GMLGeometryAppend(poMember, ppszText, pnLength, - pnMaxLength, true, nullptr)) + pnMaxLength, true, nullptr, coordOpts)) { CPLFree(pszElemOpen); return false; @@ -446,8 +453,11 @@ CPLXMLNode *OGR_G_ExportEnvelopeToGMLTree(OGRGeometryH hGeometry) /* -------------------------------------------------------------------- */ CPLXMLNode *psCoord = CPLCreateXMLNode(psBox, CXT_Element, "gml:coord"); + OGRWktOptions coordOpts; + char szCoordinate[256] = {}; - MakeGMLCoordinate(szCoordinate, sEnvelope.MinX, sEnvelope.MinY, 0.0, false); + MakeGMLCoordinate(szCoordinate, sEnvelope.MinX, sEnvelope.MinY, 0.0, false, + coordOpts); char *pszY = strstr(szCoordinate, ","); // There must be more after the comma or we have an internal consistency // bug in MakeGMLCoordinate. @@ -467,7 +477,8 @@ CPLXMLNode *OGR_G_ExportEnvelopeToGMLTree(OGRGeometryH hGeometry) /* -------------------------------------------------------------------- */ psCoord = CPLCreateXMLNode(psBox, CXT_Element, "gml:coord"); - MakeGMLCoordinate(szCoordinate, sEnvelope.MaxX, sEnvelope.MaxY, 0.0, false); + MakeGMLCoordinate(szCoordinate, sEnvelope.MaxX, sEnvelope.MaxY, 0.0, false, + coordOpts); pszY = strstr(szCoordinate, ",") + 1; pszY[-1] = '\0'; @@ -484,7 +495,8 @@ CPLXMLNode *OGR_G_ExportEnvelopeToGMLTree(OGRGeometryH hGeometry) static void AppendGML3CoordinateList(const OGRSimpleCurve *poLine, bool bCoordSwap, char **ppszText, size_t *pnLength, size_t *pnMaxLength, - int nSRSDimensionLocFlags) + int nSRSDimensionLocFlags, + const OGRWktOptions &coordOpts) { bool b3D = wkbHasZ(poLine->getGeometryType()); @@ -503,13 +515,19 @@ static void AppendGML3CoordinateList(const OGRSimpleCurve *poLine, for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++) { if (bCoordSwap) - OGRMakeWktCoordinate(szCoordinate, poLine->getY(iPoint), - poLine->getX(iPoint), poLine->getZ(iPoint), - b3D ? 3 : 2); + { + const std::string wkt = OGRMakeWktCoordinate( + poLine->getY(iPoint), poLine->getX(iPoint), + poLine->getZ(iPoint), b3D ? 3 : 2, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } else - OGRMakeWktCoordinate(szCoordinate, poLine->getX(iPoint), - poLine->getY(iPoint), poLine->getZ(iPoint), - b3D ? 3 : 2); + { + const std::string wkt = OGRMakeWktCoordinate( + poLine->getX(iPoint), poLine->getY(iPoint), + poLine->getZ(iPoint), b3D ? 3 : 2, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } _GrowBuffer(*pnLength + strlen(szCoordinate) + 1, ppszText, pnMaxLength); @@ -535,7 +553,7 @@ static bool OGR2GML3GeometryAppend( GMLSRSNameFormat eSRSNameFormat, bool bCoordSwap, bool bLineStringAsCurve, const char *pszGMLId, int nSRSDimensionLocFlags, bool bForceLineStringAsLinearRing, const char *pszNamespaceDecl, - const char *pszOverriddenElementName) + const char *pszOverriddenElementName, const OGRWktOptions &coordOpts) { @@ -628,12 +646,17 @@ static bool OGR2GML3GeometryAppend( char szCoordinate[256] = {}; if (bCoordSwap) - OGRMakeWktCoordinate(szCoordinate, poPoint->getY(), poPoint->getX(), - 0.0, 2); + { + const auto wkt = OGRMakeWktCoordinate( + poPoint->getY(), poPoint->getX(), 0.0, 2, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } else - OGRMakeWktCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(), - 0.0, 2); - + { + const auto wkt = OGRMakeWktCoordinate( + poPoint->getX(), poPoint->getY(), 0.0, 2, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } _GrowBuffer(*pnLength + strlen(szCoordinate) + 60 + nAttrsLength, ppszText, pnMaxLength); @@ -652,11 +675,19 @@ static bool OGR2GML3GeometryAppend( char szCoordinate[256] = {}; if (bCoordSwap) - OGRMakeWktCoordinate(szCoordinate, poPoint->getY(), poPoint->getX(), - poPoint->getZ(), 3); + { + const auto wkt = + OGRMakeWktCoordinate(poPoint->getY(), poPoint->getX(), + poPoint->getZ(), 3, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } else - OGRMakeWktCoordinate(szCoordinate, poPoint->getX(), poPoint->getY(), - poPoint->getZ(), 3); + { + const auto wkt = + OGRMakeWktCoordinate(poPoint->getX(), poPoint->getY(), + poPoint->getZ(), 3, coordOpts); + memcpy(szCoordinate, wkt.data(), wkt.size() + 1); + } _GrowBuffer(*pnLength + strlen(szCoordinate) + 70 + nAttrsLength, ppszText, pnMaxLength); @@ -684,7 +715,7 @@ static bool OGR2GML3GeometryAppend( const auto poLineString = poGeometry->toLineString(); AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText, pnLength, pnMaxLength, - nSRSDimensionLocFlags); + nSRSDimensionLocFlags, coordOpts); AppendString(ppszText, pnLength, pnMaxLength, ""); } @@ -718,7 +749,7 @@ static bool OGR2GML3GeometryAppend( AppendGML3CoordinateList(poLineString, bCoordSwap, ppszText, pnLength, pnMaxLength, - nSRSDimensionLocFlags); + nSRSDimensionLocFlags, coordOpts); if (bRing) AppendString(ppszText, pnLength, pnMaxLength, @@ -763,7 +794,8 @@ static bool OGR2GML3GeometryAppend( AppendString(ppszText, pnLength, pnMaxLength, ">"); AppendGML3CoordinateList(poLS, bCoordSwap, ppszText, pnLength, - pnMaxLength, nSRSDimensionLocFlags); + pnMaxLength, nSRSDimensionLocFlags, + coordOpts); AppendString(ppszText, pnLength, pnMaxLength, ""); delete poLS; @@ -773,7 +805,8 @@ static bool OGR2GML3GeometryAppend( AppendString(ppszText, pnLength, pnMaxLength, ">"); AppendGML3CoordinateList(poSC, bCoordSwap, ppszText, pnLength, - pnMaxLength, nSRSDimensionLocFlags); + pnMaxLength, nSRSDimensionLocFlags, + coordOpts); AppendString(ppszText, pnLength, pnMaxLength, ""); } @@ -800,7 +833,7 @@ static bool OGR2GML3GeometryAppend( CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend( poCC->getCurve(i), poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub, - nSRSDimensionLocFlags, false, nullptr, nullptr)); + nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts)); CPLFree(pszGMLIdSub); @@ -856,7 +889,7 @@ static bool OGR2GML3GeometryAppend( poCC->getCurve(i), poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub, nSRSDimensionLocFlags, - false, nullptr, nullptr)); + false, nullptr, nullptr, coordOpts)); CPLFree(pszGMLIdSub); @@ -876,8 +909,8 @@ static bool OGR2GML3GeometryAppend( CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend( poRing, poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, - pszGMLIdRing, nSRSDimensionLocFlags, true, nullptr, - nullptr)); + pszGMLIdRing, nSRSDimensionLocFlags, true, nullptr, nullptr, + coordOpts)); if (eRingType != wkbLineString) { @@ -940,7 +973,7 @@ static bool OGR2GML3GeometryAppend( poTri->getExteriorRingCurve(), poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, nullptr, nSRSDimensionLocFlags, true, - nullptr, nullptr)); + nullptr, nullptr, coordOpts)); AppendString(ppszText, pnLength, pnMaxLength, ""); } @@ -1019,7 +1052,7 @@ static bool OGR2GML3GeometryAppend( if (!OGR2GML3GeometryAppend( poMember, poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLIdSub, - nSRSDimensionLocFlags, false, nullptr, nullptr)) + nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts)) { CPLFree(pszGMLIdSub); return false; @@ -1056,10 +1089,11 @@ static bool OGR2GML3GeometryAppend( if (pszGMLId != nullptr) pszGMLIdSub = CPLStrdup(CPLSPrintf("%s.%d", pszGMLId, iMember)); - if (!OGR2GML3GeometryAppend( - poMember, poSRS, ppszText, pnLength, pnMaxLength, true, - eSRSNameFormat, bCoordSwap, bLineStringAsCurve, nullptr, - nSRSDimensionLocFlags, false, nullptr, "PolygonPatch")) + if (!OGR2GML3GeometryAppend(poMember, poSRS, ppszText, pnLength, + pnMaxLength, true, eSRSNameFormat, + bCoordSwap, bLineStringAsCurve, nullptr, + nSRSDimensionLocFlags, false, nullptr, + "PolygonPatch", coordOpts)) { CPLFree(pszGMLIdSub); return false; @@ -1112,7 +1146,7 @@ static bool OGR2GML3GeometryAppend( CPL_IGNORE_RET_VAL(OGR2GML3GeometryAppend( poMember, poSRS, ppszText, pnLength, pnMaxLength, true, eSRSNameFormat, bCoordSwap, bLineStringAsCurve, nullptr, - nSRSDimensionLocFlags, false, nullptr, nullptr)); + nSRSDimensionLocFlags, false, nullptr, nullptr, coordOpts)); CPLFree(pszGMLIdSub); } @@ -1189,9 +1223,11 @@ char *OGR_G_ExportToGML(OGRGeometryH hGeometry) *
    *
  • FORMAT=GML2/GML3/GML32 (GML2 or GML32 added in GDAL 2.1). * If not set, it will default to GML 2.1.2 output. + *
  • *
  • GML3_LINESTRING_ELEMENT=curve. (Only valid for FORMAT=GML3) * To use gml:Curve element for linestrings. * Otherwise gml:LineString will be used . + *
  • *
  • GML3_LONGSRS=YES/NO. (Only valid for FORMAT=GML3, deprecated by * SRSNAME_FORMAT in GDAL >=2.2). Defaults to YES. * If YES, SRS with EPSG authority will be written with the @@ -1201,6 +1237,7 @@ char *OGR_G_ExportToGML(OGRGeometryH hGeometry) * swapping if the data axis to CRS axis mapping indicates it. * If set to NO, SRS with EPSG authority will be written with the "EPSG:" * prefix, even if they are in lat/long order. + *
  • *
  • SRSNAME_FORMAT=SHORT/OGC_URN/OGC_URL (Only valid for FORMAT=GML3, added * in GDAL 2.2). Defaults to OGC_URN. If SHORT, then srsName will be in * the form AUTHORITY_NAME:AUTHORITY_CODE. If OGC_URN, then srsName will be @@ -1208,22 +1245,34 @@ char *OGR_G_ExportToGML(OGRGeometryH hGeometry) * then srsName will be in the form * http://www.opengis.net/def/crs/AUTHORITY_NAME/0/AUTHORITY_CODE. For * OGC_URN and OGC_URL, in the case the SRS should be treated as lat/long - or - * northing/easting, then the function will take care of coordinate order - * swapping if the data axis to CRS axis mapping indicates it. + * or northing/easting, then the function will take care of coordinate + * order swapping if the data axis to CRS axis mapping indicates it. + *
  • *
  • GMLID=astring. If specified, a gml:id attribute will be written in the * top-level geometry element with the provided value. * Required for GML 3.2 compatibility. + *
  • *
  • SRSDIMENSION_LOC=POSLIST/GEOMETRY/GEOMETRY,POSLIST. (Only valid for * FORMAT=GML3/GML32, GDAL >= 2.0) Default to POSLIST. * For 2.5D geometries, define the location where to attach the * srsDimension attribute. * There are diverging implementations. Some put in on the * <gml:posList> element, other on the top geometry element. - + *
  • *
  • NAMESPACE_DECL=YES/NO. If set to YES, * xmlns:gml="http://www.opengis.net/gml" will be added to the root node * for GML < 3.2 or xmlns:gml="http://www.opengis.net/gml/3.2" for GML 3.2 + *
  • + *
  • XY_COORD_RESOLUTION=double (added in GDAL 3.9): + * Resolution for the coordinate precision of the X and Y coordinates. + * Expressed in the units of the X and Y axis of the SRS. eg 1e-5 for up + * to 5 decimal digits. 0 for the default behavior. + *
  • + *
  • Z_COORD_RESOLUTION=double (added in GDAL 3.9): + * Resolution for the coordinate precision of the Z coordinates. + * Expressed in the units of the Z axis of the SRS. + * 0 for the default behavior. + *
  • *
* * Note that curve geometries like CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, @@ -1247,6 +1296,28 @@ char *OGR_G_ExportToGMLEx(OGRGeometryH hGeometry, char **papszOptions) // Do not use hGeometry after here. OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry); + OGRWktOptions coordOpts; + + const char *pszXYCoordRes = + CSLFetchNameValue(papszOptions, "XY_COORD_RESOLUTION"); + if (pszXYCoordRes) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.xyPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + CPLAtof(pszXYCoordRes)); + } + + const char *pszZCoordRes = + CSLFetchNameValue(papszOptions, "Z_COORD_RESOLUTION"); + if (pszZCoordRes) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.zPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + CPLAtof(pszZCoordRes)); + } + size_t nLength = 0; size_t nMaxLength = 1; @@ -1339,10 +1410,11 @@ char *OGR_G_ExportToGMLEx(OGRGeometryH hGeometry, char **papszOptions) } } - if (!OGR2GML3GeometryAppend( - poGeometry, nullptr, &pszText, &nLength, &nMaxLength, false, - eSRSNameFormat, bCoordSwap, bLineStringAsCurve, pszGMLId, - nSRSDimensionLocFlags, false, pszNamespaceDecl, nullptr)) + if (!OGR2GML3GeometryAppend(poGeometry, nullptr, &pszText, &nLength, + &nMaxLength, false, eSRSNameFormat, + bCoordSwap, bLineStringAsCurve, pszGMLId, + nSRSDimensionLocFlags, false, + pszNamespaceDecl, nullptr, coordOpts)) { CPLFree(pszText); return nullptr; @@ -1355,7 +1427,7 @@ char *OGR_G_ExportToGMLEx(OGRGeometryH hGeometry, char **papszOptions) if (bNamespaceDecl) pszNamespaceDecl = "http://www.opengis.net/gml"; if (!OGR2GMLGeometryAppend(poGeometry, &pszText, &nLength, &nMaxLength, - false, pszNamespaceDecl)) + false, pszNamespaceDecl, coordOpts)) { CPLFree(pszText); return nullptr; diff --git a/ogr/ogr_api.h b/ogr/ogr_api.h index f5874aaf5bd0..0c26888813cf 100644 --- a/ogr/ogr_api.h +++ b/ogr/ogr_api.h @@ -88,6 +88,39 @@ typedef void *OGRCoordinateTransformationH; struct _CPLXMLNode; +/* OGRGeomCoordinatePrecisionH */ + +/** Value for a unknown coordinate precision. */ +#define OGR_GEOM_COORD_PRECISION_UNKNOWN 0 + +/** Opaque type for OGRGeomCoordinatePrecision */ +typedef struct OGRGeomCoordinatePrecision *OGRGeomCoordinatePrecisionH; + +OGRGeomCoordinatePrecisionH CPL_DLL OGRGeomCoordinatePrecisionCreate(void); +void CPL_DLL OGRGeomCoordinatePrecisionDestroy(OGRGeomCoordinatePrecisionH); +double CPL_DLL + OGRGeomCoordinatePrecisionGetXYResolution(OGRGeomCoordinatePrecisionH); +double CPL_DLL + OGRGeomCoordinatePrecisionGetZResolution(OGRGeomCoordinatePrecisionH); +double CPL_DLL + OGRGeomCoordinatePrecisionGetMResolution(OGRGeomCoordinatePrecisionH); +char CPL_DLL ** + OGRGeomCoordinatePrecisionGetFormats(OGRGeomCoordinatePrecisionH); +CSLConstList CPL_DLL OGRGeomCoordinatePrecisionGetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH, const char *pszFormatName); +void CPL_DLL OGRGeomCoordinatePrecisionSet(OGRGeomCoordinatePrecisionH, + double dfXYResolution, + double dfZResolution, + double dfMResolution); +void CPL_DLL OGRGeomCoordinatePrecisionSetFromMeter(OGRGeomCoordinatePrecisionH, + OGRSpatialReferenceH hSRS, + double dfXYMeterResolution, + double dfZMeterResolution, + double dfMResolution); +void CPL_DLL OGRGeomCoordinatePrecisionSetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH, const char *pszFormatName, + CSLConstList papszOptions); + /* From base OGRGeometry class */ OGRErr CPL_DLL OGR_G_CreateFromWkb(const void *, OGRSpatialReferenceH, @@ -137,6 +170,21 @@ OGRErr CPL_DLL OGR_G_ExportToWkb(OGRGeometryH, OGRwkbByteOrder, unsigned char *); OGRErr CPL_DLL OGR_G_ExportToIsoWkb(OGRGeometryH, OGRwkbByteOrder, unsigned char *); + +/** Opaque type for WKB export options */ +typedef struct OGRwkbExportOptions OGRwkbExportOptions; + +OGRwkbExportOptions CPL_DLL *OGRwkbExportOptionsCreate(void); +void CPL_DLL OGRwkbExportOptionsDestroy(OGRwkbExportOptions *); +void CPL_DLL OGRwkbExportOptionsSetByteOrder(OGRwkbExportOptions *, + OGRwkbByteOrder); +void CPL_DLL OGRwkbExportOptionsSetVariant(OGRwkbExportOptions *, + OGRwkbVariant); +void CPL_DLL OGRwkbExportOptionsSetPrecision(OGRwkbExportOptions *, + OGRGeomCoordinatePrecisionH); +OGRErr CPL_DLL OGR_G_ExportToWkbEx(OGRGeometryH, unsigned char *, + const OGRwkbExportOptions *); + int CPL_DLL OGR_G_WkbSize(OGRGeometryH hGeom); size_t CPL_DLL OGR_G_WkbSizeEx(OGRGeometryH hGeom); OGRErr CPL_DLL OGR_G_ImportFromWkt(OGRGeometryH, char **); @@ -247,6 +295,17 @@ OGRGeometryH CPL_DLL OGR_G_Normalize(OGRGeometryH) CPL_WARN_UNUSED_RESULT; int CPL_DLL OGR_G_IsSimple(OGRGeometryH); int CPL_DLL OGR_G_IsRing(OGRGeometryH); +/** This option causes OGR_G_SetPrecision() + * to not attempt at preserving the topology */ +#define OGR_GEOS_PREC_NO_TOPO (1 << 0) + +/** This option causes OGR_G_SetPrecision() + * to retain collapsed elements */ +#define OGR_GEOS_PREC_KEEP_COLLAPSED (1 << 1) + +OGRGeometryH CPL_DLL OGR_G_SetPrecision(OGRGeometryH, double dfGridSize, + int nFlags) CPL_WARN_UNUSED_RESULT; + OGRGeometryH CPL_DLL OGR_G_Polygonize(OGRGeometryH) CPL_WARN_UNUSED_RESULT; /*! @cond Doxygen_Suppress */ @@ -435,6 +494,11 @@ void CPL_DLL OGR_GFld_SetNullable(OGRGeomFieldDefnH hDefn, int); int CPL_DLL OGR_GFld_IsIgnored(OGRGeomFieldDefnH hDefn); void CPL_DLL OGR_GFld_SetIgnored(OGRGeomFieldDefnH hDefn, int); +OGRGeomCoordinatePrecisionH + CPL_DLL OGR_GFld_GetCoordinatePrecision(OGRGeomFieldDefnH); +void CPL_DLL OGR_GFld_SetCoordinatePrecision(OGRGeomFieldDefnH, + OGRGeomCoordinatePrecisionH); + /* OGRFeatureDefn */ OGRFeatureDefnH CPL_DLL OGR_FD_Create(const char *) CPL_WARN_UNUSED_RESULT; diff --git a/ogr/ogr_feature.h b/ogr/ogr_feature.h index 30f5667ce7f2..e86e1e6c3b49 100644 --- a/ogr/ogr_feature.h +++ b/ogr/ogr_feature.h @@ -34,6 +34,7 @@ #include "cpl_atomic_ops.h" #include "ogr_featurestyle.h" #include "ogr_geometry.h" +#include "ogr_geomcoordinateprecision.h" #include @@ -339,6 +340,7 @@ class CPL_DLL OGRGeomFieldDefn int bIgnore = false; mutable int bNullable = true; bool m_bSealed = false; + OGRGeomCoordinatePrecision m_oCoordPrecision{}; void Initialize(const char *, OGRwkbGeometryType); //! @endcond @@ -378,6 +380,13 @@ class CPL_DLL OGRGeomFieldDefn } void SetNullable(int bNullableIn); + const OGRGeomCoordinatePrecision &GetCoordinatePrecision() const + { + return m_oCoordPrecision; + } + + void SetCoordinatePrecision(const OGRGeomCoordinatePrecision &prec); + int IsSame(const OGRGeomFieldDefn *) const; /** Convert a OGRGeomFieldDefn* to a OGRGeomFieldDefnH. diff --git a/ogr/ogr_geomcoordinateprecision.h b/ogr/ogr_geomcoordinateprecision.h new file mode 100644 index 000000000000..7194f90455e2 --- /dev/null +++ b/ogr/ogr_geomcoordinateprecision.h @@ -0,0 +1,95 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: Definition of OGRGeomCoordinatePrecision. + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2024, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#ifndef OGR_GEOMCOORDINATEPRECISION_H +#define OGR_GEOMCOORDINATEPRECISION_H + +#if !defined(DOXYGEN_SKIP) +#include +#include "cpl_string.h" +#endif + +class OGRSpatialReference; + +/** Geometry coordinate precision. + * + * This may affect how many decimal digits (for text-based output) or bits + * (for binary encodings) are used to encode geometries. + * + * It is important to note that the coordinate precision has no direct + * relationship with the "physical" accuracy. It is generally advised that + * the resolution (precision) be at least 10 times smaller than the accuracy. + * @since GDAL 3.9 + */ +struct CPL_DLL OGRGeomCoordinatePrecision +{ + /** Constant for a UNKNOWN resolution. */ + static constexpr double UNKNOWN = 0; + + /** Resolution for the coordinate precision of the X and Y coordinates. + * Expressed in the units of the X and Y axis of the SRS. + * For example for a projected SRS with X,Y axis unit in meter, a value + * of 1e-3 corresponds to a 1 mm precision. + * For a geographic SRS (on Earth) with axis unit in degree, a value + * of 8.9e-9 (degree) also corresponds to a 1 mm precision. + * Set to UNKNOWN if unknown. + */ + double dfXYResolution = UNKNOWN; + + /** Resolution for the coordinate precision of the Z coordinate. + * Expressed in the units of the Z axis of the SRS. + * Set to UNKNOWN if unknown. + */ + double dfZResolution = UNKNOWN; + + /** Resolution for the coordinate precision of the M coordinate. + * Set to UNKNOWN if unknown. + */ + double dfMResolution = UNKNOWN; + + /** Map from a format name to a list of format specific options. + * + * This can be for example used to store FileGeodatabase + * xytolerance, xorigin, yorigin, etc. coordinate precision grids + * options, which can be help to maximize preservation of coordinates in + * FileGDB -> FileGDB conversion processes. + */ + std::map oFormatSpecificOptions{}; + + void SetFromMeter(const OGRSpatialReference *poSRS, + double dfXYMeterResolution, double dfZMeterResolution, + double dfMResolution); + + OGRGeomCoordinatePrecision + ConvertToOtherSRS(const OGRSpatialReference *poSRSSrc, + const OGRSpatialReference *poSRSDst) const; + + static int ResolutionToPrecision(double dfResolution); +}; + +#endif /* OGR_GEOMCOORDINATEPRECISION_H */ diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 735d699197a6..8413e7a206c5 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -35,8 +35,10 @@ #include "cpl_conv.h" #include "cpl_json.h" #include "ogr_core.h" +#include "ogr_geomcoordinateprecision.h" #include "ogr_spatialref.h" +#include #include #include @@ -70,24 +72,23 @@ struct CPL_DLL OGRWktOptions { public: /// Type of WKT output to produce. - OGRwkbVariant variant; - /// Precision of output. Interpretation depends on \c format. - int precision; + OGRwkbVariant variant = wkbVariantOldOgc; + /// Precision of output for X,Y coordinates. Interpretation depends on \c format. + int xyPrecision; + /// Precision of output for Z coordinates. Interpretation depends on \c format. + int zPrecision; + /// Precision of output for M coordinates. Interpretation depends on \c format. + int mPrecision; /// Whether GDAL-special rounding should be applied. - bool round; + bool round = getDefaultRound(); /// Formatting type. - OGRWktFormat format; + OGRWktFormat format = OGRWktFormat::Default; /// Constructor. OGRWktOptions() - : variant(wkbVariantOldOgc), precision(15), round(true), - format(OGRWktFormat::Default) + : xyPrecision(getDefaultPrecision()), zPrecision(xyPrecision), + mPrecision(zPrecision) { - static int defPrecision = getDefaultPrecision(); - static bool defRound = getDefaultRound(); - - precision = defPrecision; - round = defRound; } /// Copy constructor @@ -307,6 +308,44 @@ class CPL_DLL OGRDefaultConstGeometryVisitor : public IOGRConstGeometryVisitor void visit(const OGRTriangulatedSurface *) override; }; +/************************************************************************/ +/* OGRGeomCoordinateBinaryPrecision */ +/************************************************************************/ + +/** Geometry coordinate precision for a binary representation. + * + * @since GDAL 3.9 + */ +struct CPL_DLL OGRGeomCoordinateBinaryPrecision +{ + int nXYBitPrecision = + INT_MIN; /**< Number of bits needed to achieved XY precision. Typically + computed with SetFromResolution() */ + int nZBitPrecision = + INT_MIN; /**< Number of bits needed to achieved Z precision. Typically + computed with SetFromResolution() */ + int nMBitPrecision = + INT_MIN; /**< Number of bits needed to achieved M precision. Typically + computed with SetFromResolution() */ + + void SetFrom(const OGRGeomCoordinatePrecision &); +}; + +/************************************************************************/ +/* OGRwkbExportOptions */ +/************************************************************************/ + +/** WKB export options. + * + * @since GDAL 3.9 + */ +struct CPL_DLL OGRwkbExportOptions +{ + OGRwkbByteOrder eByteOrder = wkbNDR; /**< Byte order */ + OGRwkbVariant eWkbVariant = wkbVariantOldOgc; /**< WKB variant. */ + OGRGeomCoordinateBinaryPrecision sPrecision{}; /**< Binary precision. */ +}; + /************************************************************************/ /* OGRGeometry */ /************************************************************************/ @@ -428,8 +467,10 @@ class CPL_DLL OGRGeometry OGRwkbVariant = wkbVariantOldOgc); virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) = 0; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const = 0; + OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, + OGRwkbVariant = wkbVariantOldOgc) const; + virtual OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const = 0; virtual OGRErr importFromWkt(const char **ppszInput) = 0; #ifndef DOXYGEN_XML @@ -466,7 +507,7 @@ class CPL_DLL OGRGeometry virtual void flattenTo2D() = 0; virtual char *exportToGML(const char *const *papszOptions = nullptr) const; virtual char *exportToKML() const; - virtual char *exportToJson() const; + virtual char *exportToJson(CSLConstList papszOptions = nullptr) const; /** Accept a visitor. */ virtual void accept(IOGRGeometryVisitor *visitor) = 0; @@ -485,6 +526,10 @@ class CPL_DLL OGRGeometry double dfMaxAngleStepSizeDegrees = 0, const char *const *papszOptions = nullptr) const CPL_WARN_UNUSED_RESULT; + void roundCoordinates(const OGRGeomCoordinatePrecision &sPrecision); + void + roundCoordinatesIEEE754(const OGRGeomCoordinateBinaryPrecision &options); + // SFCGAL interfacing methods. //! @cond Doxygen_Suppress static sfcgal_geometry_t *OGRexportToSFCGAL(const OGRGeometry *poGeom); @@ -551,6 +596,8 @@ class CPL_DLL OGRGeometry virtual double Distance3D(const OGRGeometry *poOtherGeom) const; + OGRGeometry *SetPrecision(double dfGridSize, int nFlags) const; + //! @cond Doxygen_Suppress // backward compatibility to non-standard method names. OGRBoolean Intersect(OGRGeometry *) const @@ -1102,8 +1149,8 @@ class CPL_DLL OGRPoint : public OGRGeometry size_t WkbSize() const override; OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -1539,8 +1586,9 @@ class CPL_DLL OGRSimpleCurve : public OGRCurve virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + virtual OGRErr + exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -1775,8 +1823,8 @@ class CPL_DLL OGRLinearRing : public OGRLineString virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; protected: //! @cond Doxygen_Suppress @@ -1788,8 +1836,8 @@ class CPL_DLL OGRLinearRing : public OGRLineString virtual OGRErr _importFromWkb(OGRwkbByteOrder, int _flags, const unsigned char *, size_t, size_t &nBytesConsumedOut); - virtual OGRErr _exportToWkb(OGRwkbByteOrder, int _flags, - unsigned char *) const; + virtual OGRErr _exportToWkb(int _flags, unsigned char *, + const OGRwkbExportOptions *) const; virtual OGRCurveCasterToLineString GetCasterToLineString() const override; virtual OGRCurveCasterToLinearRing GetCasterToLinearRing() const override; @@ -1882,8 +1930,8 @@ class CPL_DLL OGRCircularString : public OGRSimpleCurve // IWks Interface. virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -2033,8 +2081,8 @@ class CPL_DLL OGRCurveCollection OGRwkbVariant eWkbVariant, size_t &nBytesConsumedOut); std::string exportToWkt(const OGRGeometry *geom, const OGRWktOptions &opts, OGRErr *err) const; - OGRErr exportToWkb(const OGRGeometry *poGeom, OGRwkbByteOrder, - unsigned char *, OGRwkbVariant eWkbVariant) const; + OGRErr exportToWkb(const OGRGeometry *poGeom, unsigned char *, + const OGRwkbExportOptions * = nullptr) const; OGRBoolean Equals(const OGRCurveCollection *poOCC) const; void setCoordinateDimension(OGRGeometry *poGeom, int nNewDimension); void set3D(OGRGeometry *poGeom, OGRBoolean bIs3D); @@ -2137,8 +2185,8 @@ class CPL_DLL OGRCompoundCurve : public OGRCurve virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -2397,8 +2445,8 @@ class CPL_DLL OGRCurvePolygon : public OGRSurface virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -2580,8 +2628,8 @@ class CPL_DLL OGRPolygon : public OGRCurvePolygon virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -2826,8 +2874,8 @@ class CPL_DLL OGRGeometryCollection : public OGRGeometry virtual size_t WkbSize() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ @@ -3288,8 +3336,8 @@ class CPL_DLL OGRPolyhedralSurface : public OGRSurface virtual OGRwkbGeometryType getGeometryType() const override; virtual OGRErr importFromWkb(const unsigned char *, size_t, OGRwkbVariant, size_t &nBytesConsumedOut) override; - virtual OGRErr exportToWkb(OGRwkbByteOrder, unsigned char *, - OGRwkbVariant = wkbVariantOldOgc) const override; + OGRErr exportToWkb(unsigned char *, + const OGRwkbExportOptions * = nullptr) const override; #ifndef DOXYGEN_XML using OGRGeometry::importFromWkt; /** deprecated */ diff --git a/ogr/ogr_p.h b/ogr/ogr_p.h index ef396798f45a..6fde155e9c1f 100644 --- a/ogr/ogr_p.h +++ b/ogr/ogr_p.h @@ -42,6 +42,8 @@ #include "ogr_core.h" +#include + class OGRGeometry; class OGRFieldDefn; @@ -86,12 +88,12 @@ OGRWktReadPointsM(const char *pszInput, OGRRawPoint **ppaoPoints, void CPL_DLL OGRMakeWktCoordinate(char *, double, double, double, int); std::string CPL_DLL OGRMakeWktCoordinate(double, double, double, int, - OGRWktOptions opts); + const OGRWktOptions &opts); void CPL_DLL OGRMakeWktCoordinateM(char *, double, double, double, double, OGRBoolean, OGRBoolean); std::string CPL_DLL OGRMakeWktCoordinateM(double, double, double, double, OGRBoolean, OGRBoolean, - OGRWktOptions opts); + const OGRWktOptions &opts); #endif @@ -100,7 +102,8 @@ void CPL_DLL OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal, char chConversionSpecifier = 'f'); #ifdef OGR_GEOMETRY_H_INCLUDED -std::string CPL_DLL OGRFormatDouble(double val, const OGRWktOptions &opts); +std::string CPL_DLL OGRFormatDouble(double val, const OGRWktOptions &opts, + int nDimIdx); #endif int OGRFormatFloat(char *pszBuffer, int nBufferLen, float fVal, int nPrecision, @@ -237,4 +240,102 @@ OGRErr CPL_DLL OGRReadWKTGeometryType(const char *pszWKT, void CPL_DLL OGRUpdateFieldType(OGRFieldDefn *poFDefn, OGRFieldType eNewType, OGRFieldSubType eNewSubType); +/************************************************************************/ +/* OGRRoundValueIEEE754() */ +/************************************************************************/ + +/** Set to zero least significants bits of a double precision floating-point + * number (passed as an integer), taking into account a desired bit precision. + * + * @param nVal Integer representation of a IEEE754 double-precision number. + * @param nBitsPrecision Desired precision (number of bits after integral part) + * @return quantized nVal. + * @since GDAL 3.9 + */ +inline uint64_t OGRRoundValueIEEE754(uint64_t nVal, + int nBitsPrecision) CPL_WARN_UNUSED_RESULT; + +inline uint64_t OGRRoundValueIEEE754(uint64_t nVal, int nBitsPrecision) +{ + constexpr int MANTISSA_SIZE = std::numeric_limits::digits - 1; + constexpr int MAX_EXPONENT = std::numeric_limits::max_exponent; +#if __cplusplus >= 201703L + static_assert(MANTISSA_SIZE == 52); + static_assert(MAX_EXPONENT == 1024); +#endif + // Extract the binary exponent from the IEEE754 representation + const int nExponent = + ((nVal >> MANTISSA_SIZE) & (2 * MAX_EXPONENT - 1)) - (MAX_EXPONENT - 1); + // Add 1 to round-up and the desired precision + const int nBitsRequired = 1 + nExponent + nBitsPrecision; + // Compute number of nullified bits + int nNullifiedBits = MANTISSA_SIZE - nBitsRequired; + // this will also capture NaN and Inf since nExponent = 1023, + // and thus nNullifiedBits < 0 + if (nNullifiedBits <= 0) + return nVal; + if (nNullifiedBits >= MANTISSA_SIZE) + nNullifiedBits = MANTISSA_SIZE; + nVal &= std::numeric_limits::max() << nNullifiedBits; + return nVal; +} + +/************************************************************************/ +/* OGRRoundCoordinatesIEEE754XYValues() */ +/************************************************************************/ + +/** Quantize XY values. + * + * @since GDAL 3.9 + */ +template +inline void OGRRoundCoordinatesIEEE754XYValues(int nBitsPrecision, + GByte *pabyBase, size_t nPoints) +{ + // Note: we use SPACING as template for improved code generation. + + if (nBitsPrecision != INT_MIN) + { + for (size_t i = 0; i < nPoints; i++) + { + uint64_t nVal; + + memcpy(&nVal, pabyBase + SPACING * i, sizeof(uint64_t)); + nVal = OGRRoundValueIEEE754(nVal, nBitsPrecision); + memcpy(pabyBase + SPACING * i, &nVal, sizeof(uint64_t)); + + memcpy(&nVal, pabyBase + sizeof(uint64_t) + SPACING * i, + sizeof(uint64_t)); + nVal = OGRRoundValueIEEE754(nVal, nBitsPrecision); + memcpy(pabyBase + sizeof(uint64_t) + SPACING * i, &nVal, + sizeof(uint64_t)); + } + } +} + +/************************************************************************/ +/* OGRRoundCoordinatesIEEE754() */ +/************************************************************************/ + +/** Quantize Z or M values. + * + * @since GDAL 3.9 + */ +template +inline void OGRRoundCoordinatesIEEE754(int nBitsPrecision, GByte *pabyBase, + size_t nPoints) +{ + if (nBitsPrecision != INT_MIN) + { + for (size_t i = 0; i < nPoints; i++) + { + uint64_t nVal; + + memcpy(&nVal, pabyBase + SPACING * i, sizeof(uint64_t)); + nVal = OGRRoundValueIEEE754(nVal, nBitsPrecision); + memcpy(pabyBase + SPACING * i, &nVal, sizeof(uint64_t)); + } + } +} + #endif /* ndef OGR_P_H_INCLUDED */ diff --git a/ogr/ogr_srs_api.h b/ogr/ogr_srs_api.h index 0a3fc4992286..c7a8ec5d9f3c 100644 --- a/ogr/ogr_srs_api.h +++ b/ogr/ogr_srs_api.h @@ -575,6 +575,8 @@ OGRErr CPL_DLL OSRSetWellKnownGeogCS(OGRSpatialReferenceH hSRS, const char *pszName); OGRErr CPL_DLL CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS, const char *); +OGRErr CPL_DLL OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *, + CSLConstList papszOptions); OGRErr CPL_DLL OSRCopyGeogCSFrom(OGRSpatialReferenceH hSRS, const OGRSpatialReferenceH hSrcSRS); OGRErr CPL_DLL OSRSetTOWGS84(OGRSpatialReferenceH hSRS, double, double, double, diff --git a/ogr/ogrcircularstring.cpp b/ogr/ogrcircularstring.cpp index d70dd308194e..76a9dbfa0b66 100644 --- a/ogr/ogrcircularstring.cpp +++ b/ogr/ogrcircularstring.cpp @@ -168,9 +168,9 @@ OGRErr OGRCircularString::importFromWkb(const unsigned char *pabyData, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRCircularString::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr +OGRCircularString::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { if (!IsValidFast()) @@ -178,10 +178,13 @@ OGRErr OGRCircularString::exportToWkb(OGRwkbByteOrder eByteOrder, return OGRERR_FAILURE; } + OGRwkbExportOptions sOptions(psOptions ? *psOptions + : OGRwkbExportOptions()); + // Does not make sense for new geometries, so patch it. - if (eWkbVariant == wkbVariantOldOgc) - eWkbVariant = wkbVariantIso; - return OGRSimpleCurve::exportToWkb(eByteOrder, pabyData, eWkbVariant); + if (sOptions.eWkbVariant == wkbVariantOldOgc) + sOptions.eWkbVariant = wkbVariantIso; + return OGRSimpleCurve::exportToWkb(pabyData, &sOptions); } /************************************************************************/ diff --git a/ogr/ogrcompoundcurve.cpp b/ogr/ogrcompoundcurve.cpp index e824b1bb2621..d14151e9907d 100644 --- a/ogr/ogrcompoundcurve.cpp +++ b/ogr/ogrcompoundcurve.cpp @@ -178,14 +178,17 @@ OGRErr OGRCompoundCurve::importFromWkb(const unsigned char *pabyData, /************************************************************************/ /* exportToWkb() */ /************************************************************************/ -OGRErr OGRCompoundCurve::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const + +OGRErr OGRCompoundCurve::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + OGRwkbExportOptions sOptions(psOptions ? *psOptions + : OGRwkbExportOptions()); + // Does not make sense for new geometries, so patch it. - if (eWkbVariant == wkbVariantOldOgc) - eWkbVariant = wkbVariantIso; - return oCC.exportToWkb(this, eByteOrder, pabyData, eWkbVariant); + if (sOptions.eWkbVariant == wkbVariantOldOgc) + sOptions.eWkbVariant = wkbVariantIso; + return oCC.exportToWkb(this, pabyData, &sOptions); } /************************************************************************/ diff --git a/ogr/ogrcurvecollection.cpp b/ogr/ogrcurvecollection.cpp index d06bd48ea7e6..9a3bd3f6dcc8 100644 --- a/ogr/ogrcurvecollection.cpp +++ b/ogr/ogrcurvecollection.cpp @@ -351,23 +351,29 @@ std::string OGRCurveCollection::exportToWkt(const OGRGeometry *baseGeom, /* exportToWkb() */ /************************************************************************/ -OGRErr OGRCurveCollection::exportToWkb(const OGRGeometry *poGeom, - OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr +OGRCurveCollection::exportToWkb(const OGRGeometry *poGeom, + unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + if (psOptions == nullptr) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } + /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(psOptions->eByteOrder)); /* -------------------------------------------------------------------- */ /* Set the geometry feature type, ensuring that 3D flag is */ /* preserved. */ /* -------------------------------------------------------------------- */ GUInt32 nGType = poGeom->getIsoGeometryType(); - if (eWkbVariant == wkbVariantPostGIS1) + if (psOptions->eWkbVariant == wkbVariantPostGIS1) { const bool bIs3D = wkbHasZ(static_cast(nGType)); nGType = wkbFlatten(nGType); @@ -379,7 +385,7 @@ OGRErr OGRCurveCollection::exportToWkb(const OGRGeometry *poGeom, static_cast(nGType | wkb25DBitInternalUse); } - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { nGType = CPL_SWAP32(nGType); } @@ -389,7 +395,7 @@ OGRErr OGRCurveCollection::exportToWkb(const OGRGeometry *poGeom, /* -------------------------------------------------------------------- */ /* Copy in the raw data. */ /* -------------------------------------------------------------------- */ - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { const int nCount = CPL_SWAP32(nCurveCount); memcpy(pabyData + 5, &nCount, 4); @@ -407,7 +413,7 @@ OGRErr OGRCurveCollection::exportToWkb(const OGRGeometry *poGeom, /* ==================================================================== */ for (auto &&poSubGeom : *this) { - poSubGeom->exportToWkb(eByteOrder, pabyData + nOffset, eWkbVariant); + poSubGeom->exportToWkb(pabyData + nOffset, psOptions); nOffset += poSubGeom->WkbSize(); } diff --git a/ogr/ogrcurvepolygon.cpp b/ogr/ogrcurvepolygon.cpp index ac22bed68d9b..4edd7cef60eb 100644 --- a/ogr/ogrcurvepolygon.cpp +++ b/ogr/ogrcurvepolygon.cpp @@ -515,15 +515,16 @@ OGRErr OGRCurvePolygon::importFromWkb(const unsigned char *pabyData, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRCurvePolygon::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const - +OGRErr OGRCurvePolygon::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { - if (eWkbVariant == wkbVariantOldOgc) - // Does not make sense for new geometries, so patch it. - eWkbVariant = wkbVariantIso; - return oCC.exportToWkb(this, eByteOrder, pabyData, eWkbVariant); + OGRwkbExportOptions sOptions(psOptions ? *psOptions + : OGRwkbExportOptions()); + + // Does not make sense for new geometries, so patch it. + if (sOptions.eWkbVariant == wkbVariantOldOgc) + sOptions.eWkbVariant = wkbVariantIso; + return oCC.exportToWkb(this, pabyData, &sOptions); } /************************************************************************/ diff --git a/ogr/ogrfeature.cpp b/ogr/ogrfeature.cpp index 3ecafe900546..66cdd49ef13f 100644 --- a/ogr/ogrfeature.cpp +++ b/ogr/ogrfeature.cpp @@ -58,6 +58,12 @@ #include "cpl_json_header.h" +// Too many false positives from gcc 13.2.1 in that file... +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + /************************************************************************/ /* OGRFeature() */ /************************************************************************/ @@ -5642,16 +5648,42 @@ std::string OGRFeature::DumpReadableAsString(CSLConstList papszOptions) const { for (int iField = 0; iField < nGeomFieldCount; iField++) { - OGRGeomFieldDefn *poFDefn = poDefn->GetGeomFieldDefn(iField); + const OGRGeomFieldDefn *poFDefn = + poDefn->GetGeomFieldDefn(iField); if (papoGeometries[iField] != nullptr) { + CPLStringList aosGeomOptions(papszOptions); + + const auto &oCoordPrec = poFDefn->GetCoordinatePrecision(); + + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + aosGeomOptions.SetNameValue( + "XY_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision:: + ResolutionToPrecision( + oCoordPrec.dfXYResolution))); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + aosGeomOptions.SetNameValue( + "Z_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision:: + ResolutionToPrecision( + oCoordPrec.dfZResolution))); + } + osRet += " "; if (strlen(poFDefn->GetNameRef()) > 0 && GetGeomFieldCount() > 1) osRet += CPLOPrintf("%s = ", poFDefn->GetNameRef()); - osRet += papoGeometries[iField]->dumpReadable(nullptr, - papszOptions); + osRet += papoGeometries[iField]->dumpReadable( + nullptr, aosGeomOptions.List()); } } } @@ -7814,3 +7846,7 @@ OGRFeature::FieldValue::operator CSLConstList() const const_cast(m_poPrivate->m_poSelf) ->GetFieldAsStringList(GetIndex())); } + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/ogr/ogrgeomcoordinateprecision.cpp b/ogr/ogrgeomcoordinateprecision.cpp new file mode 100644 index 000000000000..352d9e8273e9 --- /dev/null +++ b/ogr/ogrgeomcoordinateprecision.cpp @@ -0,0 +1,399 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: Implements OGRGeomCoordinatePrecision. + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2024, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_core.h" +#include "ogr_api.h" +#include "ogr_spatialref.h" +#include "ogr_geomcoordinateprecision.h" + +#include +#include + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionCreate() */ +/************************************************************************/ + +/** Creates a new instance of OGRGeomCoordinatePrecision. + * + * The default X,Y,Z,M resolutions are set to OGR_GEOM_COORD_PRECISION_UNKNOWN. + * + * @since GDAL 3.9 + */ +OGRGeomCoordinatePrecisionH OGRGeomCoordinatePrecisionCreate(void) +{ + static_assert(OGR_GEOM_COORD_PRECISION_UNKNOWN == + OGRGeomCoordinatePrecision::UNKNOWN); + + return new OGRGeomCoordinatePrecision(); +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionDestroy() */ +/************************************************************************/ + +/** Destroy a OGRGeomCoordinatePrecision. + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance or nullptr + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecisionDestroy( + OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + delete hGeomCoordPrec; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionGetXYResolution() */ +/************************************************************************/ + +/** Get the X/Y resolution of a OGRGeomCoordinatePrecision + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @return the the X/Y resolution of a OGRGeomCoordinatePrecision or + * OGR_GEOM_COORD_PRECISION_UNKNOWN + * @since GDAL 3.9 + */ +double OGRGeomCoordinatePrecisionGetXYResolution( + OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + VALIDATE_POINTER1(hGeomCoordPrec, + "OGRGeomCoordinatePrecisionGetXYResolution", 0); + return hGeomCoordPrec->dfXYResolution; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionGetZResolution() */ +/************************************************************************/ + +/** Get the Z resolution of a OGRGeomCoordinatePrecision + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @return the the Z resolution of a OGRGeomCoordinatePrecision or + * OGR_GEOM_COORD_PRECISION_UNKNOWN + * @since GDAL 3.9 + */ +double OGRGeomCoordinatePrecisionGetZResolution( + OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + VALIDATE_POINTER1(hGeomCoordPrec, + "OGRGeomCoordinatePrecisionGetZResolution", 0); + return hGeomCoordPrec->dfZResolution; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionGetMResolution() */ +/************************************************************************/ + +/** Get the M resolution of a OGRGeomCoordinatePrecision + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @return the the M resolution of a OGRGeomCoordinatePrecision or + * OGR_GEOM_COORD_PRECISION_UNKNOWN + * @since GDAL 3.9 + */ +double OGRGeomCoordinatePrecisionGetMResolution( + OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + VALIDATE_POINTER1(hGeomCoordPrec, + "OGRGeomCoordinatePrecisionGetMResolution", 0); + return hGeomCoordPrec->dfMResolution; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionGetFormats() */ +/************************************************************************/ + +/** Get the list of format names for coordinate precision format specific + * options. + * + * An example of a supported value for pszFormatName is + * "FileGeodatabase" for layers of the OpenFileGDB driver. + * + * The returned values may be used for the pszFormatName argument of + * OGRGeomCoordinatePrecisionGetFormatSpecificOptions(). + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @return a null-terminated list to free with CSLDestroy(), or nullptr. + * @since GDAL 3.9 + */ +char ** +OGRGeomCoordinatePrecisionGetFormats(OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + VALIDATE_POINTER1(hGeomCoordPrec, "OGRGeomCoordinatePrecisionGetFormats", + nullptr); + CPLStringList aosFormats; + for (const auto &kv : hGeomCoordPrec->oFormatSpecificOptions) + { + aosFormats.AddString(kv.first.c_str()); + } + return aosFormats.StealList(); +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionGetFormatSpecificOptions() */ +/************************************************************************/ + +/** Get format specific coordinate precision options. + * + * An example of a supported value for pszFormatName is + * "FileGeodatabase" for layers of the OpenFileGDB driver. + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @param pszFormatName A format name (one of those returned by + * OGRGeomCoordinatePrecisionGetFormats()) + * @return a null-terminated list, or nullptr. The list must *not* be freed, + * and is owned by hGeomCoordPrec + * @since GDAL 3.9 + */ +CSLConstList OGRGeomCoordinatePrecisionGetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH hGeomCoordPrec, const char *pszFormatName) +{ + VALIDATE_POINTER1(hGeomCoordPrec, + "OGRGeomCoordinatePrecisionGetFormatSpecificOptions", + nullptr); + const auto oIter = + hGeomCoordPrec->oFormatSpecificOptions.find(pszFormatName); + if (oIter == hGeomCoordPrec->oFormatSpecificOptions.end()) + { + return nullptr; + } + return oIter->second.List(); +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionSetFormatSpecificOptions() */ +/************************************************************************/ + +/** Set format specific coordinate precision options. + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @param pszFormatName A format name (must not be null) + * @param papszOptions null-terminated list of options. + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecisionSetFormatSpecificOptions( + OGRGeomCoordinatePrecisionH hGeomCoordPrec, const char *pszFormatName, + CSLConstList papszOptions) +{ + VALIDATE_POINTER0(hGeomCoordPrec, + "OGRGeomCoordinatePrecisionSetFormatSpecificOptions"); + hGeomCoordPrec->oFormatSpecificOptions[pszFormatName] = papszOptions; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionSet() */ +/************************************************************************/ + +/** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z ordinates, the precision should be expressed in the units + * of the CRS of the geometry. So typically degrees for geographic CRS, or + * meters/feet/US-feet for projected CRS. + * Users might use OGRGeomCoordinatePrecisionSetFromMeter() for an even more + * convenient interface. + * + * For a projected CRS with meters as linear unit, 1e-3 corresponds to a + * millimetric precision. + * For a geographic CRS in 8.9e-9 corresponds to a millimetric precision + * (for a Earth CRS) + * + * Resolution should be stricty positive, or set to + * OGR_GEOM_COORD_PRECISION_UNKNOWN when unknown. + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @param dfXYResolution Resolution for for X and Y coordinates. + * @param dfZResolution Resolution for for Z coordinates. + * @param dfMResolution Resolution for for M coordinates. + * @since GDAL 3.9 + */ + +void OGRGeomCoordinatePrecisionSet(OGRGeomCoordinatePrecisionH hGeomCoordPrec, + double dfXYResolution, double dfZResolution, + double dfMResolution) +{ + VALIDATE_POINTER0(hGeomCoordPrec, "OGRGeomCoordinatePrecisionSet"); + hGeomCoordPrec->dfXYResolution = dfXYResolution; + hGeomCoordPrec->dfZResolution = dfZResolution; + hGeomCoordPrec->dfMResolution = dfMResolution; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionSetFromMeter() */ +/************************************************************************/ + +/** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z ordinates, the precision should be expressed in meter, + * e.g 1e-3 for millimetric precision. + * + * Resolution should be stricty positive, or set to + * OGR_GEOM_COORD_PRECISION_UNKNOWN when unknown. + * + * @param hGeomCoordPrec OGRGeomCoordinatePrecision instance (must not be null) + * @param hSRS Spatial reference system, used for metric to SRS unit conversion + * (must not be null) + * @param dfXYMeterResolution Resolution for for X and Y coordinates, in meter. + * @param dfZMeterResolution Resolution for for Z coordinates, in meter. + * @param dfMResolution Resolution for for M coordinates. + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecisionSetFromMeter( + OGRGeomCoordinatePrecisionH hGeomCoordPrec, OGRSpatialReferenceH hSRS, + double dfXYMeterResolution, double dfZMeterResolution, double dfMResolution) +{ + VALIDATE_POINTER0(hGeomCoordPrec, "OGRGeomCoordinatePrecisionSet"); + VALIDATE_POINTER0(hSRS, "OGRGeomCoordinatePrecisionSet"); + return hGeomCoordPrec->SetFromMeter(OGRSpatialReference::FromHandle(hSRS), + dfXYMeterResolution, dfZMeterResolution, + dfMResolution); +} + +/************************************************************************/ +/* GetConversionFactors() */ +/************************************************************************/ + +static void GetConversionFactors(const OGRSpatialReference *poSRS, + double &dfXYFactor, double &dfZFactor) +{ + dfXYFactor = 1; + dfZFactor = 1; + + if (poSRS) + { + if (poSRS->IsGeographic()) + { + dfXYFactor = poSRS->GetSemiMajor(nullptr) * M_PI / 180; + } + else + { + dfXYFactor = poSRS->GetLinearUnits(nullptr); + } + + if (poSRS->GetAxesCount() == 3) + { + poSRS->GetAxis(nullptr, 2, nullptr, &dfZFactor); + } + } +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecision::SetFromMeter() */ +/************************************************************************/ + +/** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z coordinates, the precision should be expressed in meter, + * e.g 1e-3 for millimetric precision. + * + * Resolution should be stricty positive, or set to + * OGRGeomCoordinatePrecision::UNKNOWN when unknown. + * + * @param poSRS Spatial reference system, used for metric to SRS unit conversion + * (must not be null) + * @param dfXYMeterResolution Resolution for for X and Y coordinates, in meter. + * @param dfZMeterResolution Resolution for for Z coordinates, in meter. + * @param dfMResolutionIn Resolution for for M coordinates. + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecision::SetFromMeter(const OGRSpatialReference *poSRS, + double dfXYMeterResolution, + double dfZMeterResolution, + double dfMResolutionIn) +{ + double dfXYFactor = 1; + double dfZFactor = 1; + GetConversionFactors(poSRS, dfXYFactor, dfZFactor); + + dfXYResolution = dfXYMeterResolution / dfXYFactor; + dfZResolution = dfZMeterResolution / dfZFactor; + dfMResolution = dfMResolutionIn; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecision::ConvertToOtherSRS() */ +/************************************************************************/ + +/** + * \brief Return equivalent coordinate precision setting taking into account + * a change of SRS. + * + * @param poSRSSrc Spatial reference system of the current instance + * (if null, meter unit is assumed) + * @param poSRSDst Spatial reference system of the returned instance + * (if null, meter unit is assumed) + * @return a new OGRGeomCoordinatePrecision instance, with a poSRSDst SRS. + * @since GDAL 3.9 + */ +OGRGeomCoordinatePrecision OGRGeomCoordinatePrecision::ConvertToOtherSRS( + const OGRSpatialReference *poSRSSrc, + const OGRSpatialReference *poSRSDst) const +{ + double dfXYFactorSrc = 1; + double dfZFactorSrc = 1; + GetConversionFactors(poSRSSrc, dfXYFactorSrc, dfZFactorSrc); + + double dfXYFactorDst = 1; + double dfZFactorDst = 1; + GetConversionFactors(poSRSDst, dfXYFactorDst, dfZFactorDst); + + OGRGeomCoordinatePrecision oNewPrec; + oNewPrec.dfXYResolution = dfXYResolution * dfXYFactorSrc / dfXYFactorDst; + oNewPrec.dfZResolution = dfZResolution * dfZFactorSrc / dfZFactorDst; + oNewPrec.dfMResolution = dfMResolution; + + // Only preserve source forma specific options if no reprojection is + // involved + if ((!poSRSSrc && !poSRSDst) || + (poSRSSrc && poSRSDst && poSRSSrc->IsSame(poSRSDst))) + { + oNewPrec.oFormatSpecificOptions = oFormatSpecificOptions; + } + + return oNewPrec; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecision::ResolutionToPrecision() */ +/************************************************************************/ + +/** + * \brief Return the number of decimal digits after the decimal point to + * get the specified resolution. + * + * @since GDAL 3.9 + */ + +/* static */ +int OGRGeomCoordinatePrecision::ResolutionToPrecision(double dfResolution) +{ + return static_cast( + std::ceil(std::log10(1. / std::min(1.0, dfResolution)))); +} diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index c5cf6f511a62..476772b8a793 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -201,6 +201,10 @@ void OGRGeometry::dumpReadable(FILE *fp, const char *pszPrefix, *
  • DISPLAY_GEOMETRY=NO : to hide the dump of the geometry
  • *
  • DISPLAY_GEOMETRY=WKT or YES (default) : dump the geometry as a WKT
  • *
  • DISPLAY_GEOMETRY=SUMMARY : to get only a summary of the geometry
  • + *
  • XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates + * in WKT (added in GDAL 3.9)
  • + *
  • Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates in + * WKT (added in GDAL 3.9)
  • * * * @param pszPrefix the prefix to put on each line of output. @@ -217,6 +221,35 @@ std::string OGRGeometry::dumpReadable(const char *pszPrefix, pszPrefix = ""; std::string osRet; + + const auto exportToWktWithOpts = + [this, pszPrefix, papszOptions, &osRet](bool bIso) + { + OGRErr err(OGRERR_NONE); + OGRWktOptions opts; + if (const char *pszXYPrecision = + CSLFetchNameValue(papszOptions, "XY_COORD_PRECISION")) + { + opts.format = OGRWktFormat::F; + opts.xyPrecision = atoi(pszXYPrecision); + } + if (const char *pszZPrecision = + CSLFetchNameValue(papszOptions, "Z_COORD_PRECISION")) + { + opts.format = OGRWktFormat::F; + opts.zPrecision = atoi(pszZPrecision); + } + if (bIso) + opts.variant = wkbVariantIso; + std::string wkt = exportToWkt(opts, &err); + if (err == OGRERR_NONE) + { + osRet = pszPrefix; + osRet += wkt.data(); + osRet += '\n'; + } + }; + const char *pszDisplayGeometry = CSLFetchNameValue(papszOptions, "DISPLAY_GEOMETRY"); if (pszDisplayGeometry != nullptr && EQUAL(pszDisplayGeometry, "SUMMARY")) @@ -392,30 +425,14 @@ std::string OGRGeometry::dumpReadable(const char *pszPrefix, } else if (pszDisplayGeometry != nullptr && EQUAL(pszDisplayGeometry, "WKT")) { - OGRErr err(OGRERR_NONE); - std::string wkt = exportToWkt(OGRWktOptions(), &err); - if (err == OGRERR_NONE) - { - osRet += pszPrefix; - osRet += wkt.data(); - osRet += '\n'; - } + exportToWktWithOpts(/* bIso=*/false); } else if (pszDisplayGeometry == nullptr || CPLTestBool(pszDisplayGeometry) || EQUAL(pszDisplayGeometry, "ISO_WKT")) { - OGRErr err(OGRERR_NONE); - OGRWktOptions opts; - - opts.variant = wkbVariantIso; - std::string wkt = exportToWkt(opts, &err); - if (err == OGRERR_NONE) - { - osRet += pszPrefix; - osRet += wkt.data(); - osRet += '\n'; - } + exportToWktWithOpts(/* bIso=*/true); } + return osRet; } @@ -1527,12 +1544,12 @@ OGRErr OGR_G_ImportFromWkb(OGRGeometryH hGeom, const void *pabyData, int nSize) static_cast(pabyData), nSize); } +/************************************************************************/ +/* OGRGeometry::exportToWkb() */ +/************************************************************************/ + /* clang-format off */ /** - * \fn OGRErr OGRGeometry::exportToWkb( OGRwkbByteOrder eByteOrder, - unsigned char * pabyData, - OGRwkbVariant eWkbVariant=wkbVariantOldOgc ) const - * * \brief Convert a geometry into well known binary format. * * This method relates to the SFCOM IWks::ExportToWKB() method. @@ -1555,6 +1572,15 @@ OGRErr OGR_G_ImportFromWkb(OGRGeometryH hGeom, const void *pabyData, int nSize) * @return Currently OGRERR_NONE is always returned. */ /* clang-format on */ +OGRErr OGRGeometry::exportToWkb(OGRwkbByteOrder eByteOrder, + unsigned char *pabyData, + OGRwkbVariant eWkbVariant) const +{ + OGRwkbExportOptions sOptions; + sOptions.eByteOrder = eByteOrder; + sOptions.eWkbVariant = eWkbVariant; + return exportToWkb(pabyData, &sOptions); +} /************************************************************************/ /* OGR_G_ExportToWkb() */ @@ -1631,6 +1657,60 @@ OGRErr OGR_G_ExportToIsoWkb(OGRGeometryH hGeom, OGRwkbByteOrder eOrder, wkbVariantIso); } +/************************************************************************/ +/* OGR_G_ExportToWkbEx() */ +/************************************************************************/ + +/* clang-format off */ +/** + * \fn OGRErr OGRGeometry::exportToWkb(unsigned char *pabyDstBuffer, const OGRwkbExportOptions *psOptions=nullptr) const + * + * \brief Convert a geometry into well known binary format + * + * This function relates to the SFCOM IWks::ExportToWKB() method. + * + * This function is the same as the C function OGR_G_ExportToWkbEx(). + * + * @param pabyDstBuffer a buffer into which the binary representation is + * written. This buffer must be at least + * OGR_G_WkbSize() byte in size. + * @param psOptions WKB export options. + + * @return Currently OGRERR_NONE is always returned. + * + * @since GDAL 3.9 + */ +/* clang-format on */ + +/** + * \brief Convert a geometry into well known binary format + * + * This function relates to the SFCOM IWks::ExportToWKB() method. + * + * This function is the same as the CPP method + * OGRGeometry::exportToWkb(unsigned char *, const OGRwkbExportOptions*) + * + * @param hGeom handle on the geometry to convert to a well know binary + * data from. + * @param pabyDstBuffer a buffer into which the binary representation is + * written. This buffer must be at least + * OGR_G_WkbSize() byte in size. + * @param psOptions WKB export options. + + * @return Currently OGRERR_NONE is always returned. + * + * @since GDAL 3.9 + */ + +OGRErr OGR_G_ExportToWkbEx(OGRGeometryH hGeom, unsigned char *pabyDstBuffer, + const OGRwkbExportOptions *psOptions) +{ + VALIDATE_POINTER1(hGeom, "OGR_G_ExportToWkbEx", OGRERR_FAILURE); + + return OGRGeometry::FromHandle(hGeom)->exportToWkb(pabyDstBuffer, + psOptions); +} + /** * \fn OGRErr OGRGeometry::importFromWkt( const char ** ppszInput ); * @@ -2953,21 +3033,60 @@ void OGR_G_FlattenTo2D(OGRGeometryH hGeom) * types assuming the this is available in the gml namespace. The returned * string should be freed with CPLFree() when no longer required. * - * The supported options in OGR 1.8.0 are : + * The supported options are : *
      - *
    • FORMAT=GML3. Otherwise it will default to GML 2.1.2 output. - *
    • GML3_LINESTRING_ELEMENT=curve. (Only valid for FORMAT=GML3) To - * use gml:Curve element for linestrings. Otherwise - * gml:LineString will be used . - *
    • GML3_LONGSRS=YES/NO. (Only valid for FORMAT=GML3) Default to - * YES. If YES, SRS with EPSG authority will be written with the - * "urn:ogc:def:crs:EPSG::" prefix. In the case, if the SRS is a - * geographic SRS without explicit AXIS order, but that the same - * SRS authority code imported with ImportFromEPSGA() should be - * treated as lat/long, then the function will take care of - * coordinate order swapping. If set to NO, SRS with EPSG - * authority will be written with the "EPSG:" prefix, even if - * they are in lat/long order. + *
    • FORMAT=GML2/GML3/GML32 (GML2 or GML32 added in GDAL 2.1). + * If not set, it will default to GML 2.1.2 output. + *
    • + *
    • GML3_LINESTRING_ELEMENT=curve. (Only valid for FORMAT=GML3) + * To use gml:Curve element for linestrings. + * Otherwise gml:LineString will be used . + *
    • + *
    • GML3_LONGSRS=YES/NO. (Only valid for FORMAT=GML3, deprecated by + * SRSNAME_FORMAT in GDAL >=2.2). Defaults to YES. + * If YES, SRS with EPSG authority will be written with the + * "urn:ogc:def:crs:EPSG::" prefix. + * In the case the SRS should be treated as lat/long or + * northing/easting, then the function will take care of coordinate order + * swapping if the data axis to CRS axis mapping indicates it. + * If set to NO, SRS with EPSG authority will be written with the "EPSG:" + * prefix, even if they are in lat/long order. + *
    • + *
    • SRSNAME_FORMAT=SHORT/OGC_URN/OGC_URL (Only valid for FORMAT=GML3, added + * in GDAL 2.2). Defaults to OGC_URN. If SHORT, then srsName will be in + * the form AUTHORITY_NAME:AUTHORITY_CODE. If OGC_URN, then srsName will be + * in the form urn:ogc:def:crs:AUTHORITY_NAME::AUTHORITY_CODE. If OGC_URL, + * then srsName will be in the form + * http://www.opengis.net/def/crs/AUTHORITY_NAME/0/AUTHORITY_CODE. For + * OGC_URN and OGC_URL, in the case the SRS should be treated as lat/long + * or northing/easting, then the function will take care of coordinate + * order swapping if the data axis to CRS axis mapping indicates it. + *
    • + *
    • GMLID=astring. If specified, a gml:id attribute will be written in the + * top-level geometry element with the provided value. + * Required for GML 3.2 compatibility. + *
    • + *
    • SRSDIMENSION_LOC=POSLIST/GEOMETRY/GEOMETRY,POSLIST. (Only valid for + * FORMAT=GML3/GML32, GDAL >= 2.0) Default to POSLIST. + * For 2.5D geometries, define the location where to attach the + * srsDimension attribute. + * There are diverging implementations. Some put in on the + * <gml:posList> element, other on the top geometry element. + *
    • + *
    • NAMESPACE_DECL=YES/NO. If set to YES, + * xmlns:gml="http://www.opengis.net/gml" will be added to the root node + * for GML < 3.2 or xmlns:gml="http://www.opengis.net/gml/3.2" for GML 3.2 + *
    • + *
    • XY_COORD_RESOLUTION=double (added in GDAL 3.9): + * Resolution for the coordinate precision of the X and Y coordinates. + * Expressed in the units of the X and Y axis of the SRS. eg 1e-5 for up + * to 5 decimal digits. 0 for the default behavior. + *
    • + *
    • Z_COORD_RESOLUTION=double (added in GDAL 3.9): + * Resolution for the coordinate precision of the Z coordinates. + * Expressed in the units of the Z axis of the SRS. + * 0 for the default behavior. + *
    • *
    * * This method is the same as the C function OGR_G_ExportToGMLEx(). @@ -3016,15 +3135,25 @@ char *OGRGeometry::exportToKML() const * * The returned string should be freed with CPLFree() when no longer required. * + * The following options are supported : + *
      + *
    • XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates + * (added in GDAL 3.9)
    • + *
    • Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates + * (added in GDAL 3.9)
    • + *
    + * * This method is the same as the C function OGR_G_ExportToJson(). * + * @param papszOptions Null terminated list of options, or null (added in 3.9) * @return A GeoJSON fragment or NULL in case of error. */ -char *OGRGeometry::exportToJson() const +char *OGRGeometry::exportToJson(CSLConstList papszOptions) const { OGRGeometry *poGeometry = const_cast(this); - return OGR_G_ExportToJson(OGRGeometry::ToHandle(poGeometry)); + return OGR_G_ExportToJsonEx(OGRGeometry::ToHandle(poGeometry), + const_cast(papszOptions)); } /************************************************************************/ @@ -6015,6 +6144,177 @@ OGRGeometryH OGR_G_SimplifyPreserveTopology(OGRGeometryH hThis, OGRGeometry::FromHandle(hThis)->SimplifyPreserveTopology(dTolerance)); } +/************************************************************************/ +/* roundCoordinates() */ +/************************************************************************/ + +/** Round coordinates of the geometry to the specified precision. + * + * Note that this is not the same as OGRGeometry::SetPrecision(). The later + * will return valid geometries, whereas roundCoordinates() does not make + * such guarantee and may return geometries with invalidities, if they are + * not compatible of the specified precision. roundCoordinates() supports + * curve geometries, whereas SetPrecision() does not currently. + * + * One use case for roundCoordinates() is to undo the effect of + * quantizeCoordinates(). + * + * @param sPrecision Contains the precision requirements. + * @since GDAL 3.9 + */ +void OGRGeometry::roundCoordinates(const OGRGeomCoordinatePrecision &sPrecision) +{ + struct Rounder : public OGRDefaultGeometryVisitor + { + const OGRGeomCoordinatePrecision &m_precision; + const double m_invXYResolution; + const double m_invZResolution; + const double m_invMResolution; + explicit Rounder(const OGRGeomCoordinatePrecision &sPrecisionIn) + : m_precision(sPrecisionIn), + m_invXYResolution(m_precision.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN + ? 1.0 / m_precision.dfXYResolution + : 0.0), + m_invZResolution(m_precision.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN + ? 1.0 / m_precision.dfZResolution + : 0.0), + m_invMResolution(m_precision.dfMResolution != + OGRGeomCoordinatePrecision::UNKNOWN + ? 1.0 / m_precision.dfMResolution + : 0.0) + { + } + + using OGRDefaultGeometryVisitor::visit; + void visit(OGRPoint *poPoint) override + { + if (m_precision.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + poPoint->setX(std::round(poPoint->getX() * m_invXYResolution) * + m_precision.dfXYResolution); + poPoint->setY(std::round(poPoint->getY() * m_invXYResolution) * + m_precision.dfXYResolution); + } + if (m_precision.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN && + poPoint->Is3D()) + { + poPoint->setZ(std::round(poPoint->getZ() * m_invZResolution) * + m_precision.dfZResolution); + } + if (m_precision.dfMResolution != + OGRGeomCoordinatePrecision::UNKNOWN && + poPoint->IsMeasured()) + { + poPoint->setM(std::round(poPoint->getM() * m_invMResolution) * + m_precision.dfMResolution); + } + } + }; + + Rounder rounder(sPrecision); + accept(&rounder); +} + +/************************************************************************/ +/* SetPrecision() */ +/************************************************************************/ + +/** Set the geometry's precision, rounding all its coordinates to the precision + * grid, and making sure the geometry is still valid. + * + * This is a stronger version of roundCoordinates(). + * + * Note that at time of writing GEOS does no supported curve geometries. So + * currently if this function is called on such a geometry, OGR will first call + * getLinearGeometry() on the input and getCurveGeometry() on the output, but + * that it is unlikely to yield to the expected result. + * + * This function is the same as the C function OGR_G_SetPrecision(). + * + * This function is built on the GEOSGeom_setPrecision_r() function of the + * GEOS library. Check it for the definition of the geometry operation. + * If OGR is built without the GEOS library, this function will always fail, + * issuing a CPLE_NotSupported error. + * + * @param dfGridSize size of the precision grid, or 0 for FLOATING + * precision. + * @param nFlags The bitwise OR of zero, one or several of OGR_GEOS_PREC_NO_TOPO + * and OGR_GEOS_PREC_KEEP_COLLAPSED + * + * @return a new geometry or NULL if an error occurs. + * + * @since GDAL 3.9 + */ + +OGRGeometry *OGRGeometry::SetPrecision(UNUSED_IF_NO_GEOS double dfGridSize, + UNUSED_IF_NO_GEOS int nFlags) const +{ +#ifndef HAVE_GEOS + CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled."); + return nullptr; + +#else + OGRGeometry *poOGRProduct = nullptr; + + GEOSContextHandle_t hGEOSCtxt = createGEOSContext(); + GEOSGeom hThisGeosGeom = exportToGEOS(hGEOSCtxt); + if (hThisGeosGeom != nullptr) + { + GEOSGeom hGeosProduct = GEOSGeom_setPrecision_r( + hGEOSCtxt, hThisGeosGeom, dfGridSize, nFlags); + GEOSGeom_destroy_r(hGEOSCtxt, hThisGeosGeom); + poOGRProduct = + BuildGeometryFromGEOS(hGEOSCtxt, hGeosProduct, this, nullptr); + } + freeGEOSContext(hGEOSCtxt); + return poOGRProduct; + +#endif // HAVE_GEOS +} + +/************************************************************************/ +/* OGR_G_SetPrecision() */ +/************************************************************************/ + +/** Set the geometry's precision, rounding all its coordinates to the precision + * grid, and making sure the geometry is still valid. + * + * This is a stronger version of roundCoordinates(). + * + * Note that at time of writing GEOS does no supported curve geometries. So + * currently if this function is called on such a geometry, OGR will first call + * getLinearGeometry() on the input and getCurveGeometry() on the output, but + * that it is unlikely to yield to the expected result. + * + * This function is the same as the C++ method OGRGeometry::SetPrecision(). + * + * This function is built on the GEOSGeom_setPrecision_r() function of the + * GEOS library. Check it for the definition of the geometry operation. + * If OGR is built without the GEOS library, this function will always fail, + * issuing a CPLE_NotSupported error. + * + * @param hThis the geometry. + * @param dfGridSize size of the precision grid, or 0 for FLOATING + * precision. + * @param nFlags The bitwise OR of zero, one or several of OGR_GEOS_PREC_NO_TOPO + * and OGR_GEOS_PREC_KEEP_COLLAPSED + * + * @return a new geometry or NULL if an error occurs. + * + * @since GDAL 3.9 + */ +OGRGeometryH OGR_G_SetPrecision(OGRGeometryH hThis, double dfGridSize, + int nFlags) +{ + VALIDATE_POINTER1(hThis, "OGR_G_SetPrecision", nullptr); + return OGRGeometry::ToHandle( + OGRGeometry::FromHandle(hThis)->SetPrecision(dfGridSize, nFlags)); +} + /************************************************************************/ /* DelaunayTriangulation() */ /************************************************************************/ @@ -7490,6 +7790,78 @@ OGRBoolean OGRGeometry::IsSFCGALCompatible() const } //! @endcond +/************************************************************************/ +/* roundCoordinatesIEEE754() */ +/************************************************************************/ + +/** Round coordinates of a geometry, exploiting characteristics of the IEEE-754 + * double-precision binary representation. + * + * Determines the number of bits (N) required to represent a coordinate value + * with a specified number of digits after the decimal point, and then sets all + * but the N most significant bits to zero. The resulting coordinate value will + * still round to the original value (e.g. after roundCoordinates()), but wil + * have improved compressiblity. + * + * @param options Contains the precision requirements. + * @since GDAL 3.9 + */ +void OGRGeometry::roundCoordinatesIEEE754( + const OGRGeomCoordinateBinaryPrecision &options) +{ + struct Quantizer : public OGRDefaultGeometryVisitor + { + const OGRGeomCoordinateBinaryPrecision &m_options; + explicit Quantizer(const OGRGeomCoordinateBinaryPrecision &optionsIn) + : m_options(optionsIn) + { + } + + using OGRDefaultGeometryVisitor::visit; + void visit(OGRPoint *poPoint) override + { + if (m_options.nXYBitPrecision != INT_MIN) + { + uint64_t i; + double d; + d = poPoint->getX(); + memcpy(&i, &d, sizeof(i)); + i = OGRRoundValueIEEE754(i, m_options.nXYBitPrecision); + memcpy(&d, &i, sizeof(i)); + poPoint->setX(d); + d = poPoint->getY(); + memcpy(&i, &d, sizeof(i)); + i = OGRRoundValueIEEE754(i, m_options.nXYBitPrecision); + memcpy(&d, &i, sizeof(i)); + poPoint->setY(d); + } + if (m_options.nZBitPrecision != INT_MIN && poPoint->Is3D()) + { + uint64_t i; + double d; + d = poPoint->getZ(); + memcpy(&i, &d, sizeof(i)); + i = OGRRoundValueIEEE754(i, m_options.nZBitPrecision); + memcpy(&d, &i, sizeof(i)); + poPoint->setZ(d); + } + if (m_options.nMBitPrecision != INT_MIN && poPoint->IsMeasured()) + { + uint64_t i; + double d; + d = poPoint->getM(); + memcpy(&i, &d, sizeof(i)); + i = OGRRoundValueIEEE754(i, m_options.nMBitPrecision); + memcpy(&d, &i, sizeof(i)); + poPoint->setM(d); + } + } + }; + + Quantizer quantizer(options); + accept(&quantizer); +} + /************************************************************************/ /* visit() */ /************************************************************************/ @@ -7711,3 +8083,120 @@ void OGRGeometry::HomogenizeDimensionalityWith(OGRGeometry *poOtherGeom) poOtherGeom->setMeasured(TRUE); } //! @endcond + +/************************************************************************/ +/* OGRGeomCoordinateBinaryPrecision::SetFrom() */ +/************************************************************************/ + +/** Set binary precision options from resolution. + * + * @since GDAL 3.9 + */ +void OGRGeomCoordinateBinaryPrecision::SetFrom( + const OGRGeomCoordinatePrecision &prec) +{ + if (prec.dfXYResolution != 0) + { + nXYBitPrecision = + static_cast(ceil(log2(1. / prec.dfXYResolution))); + } + if (prec.dfZResolution != 0) + { + nZBitPrecision = static_cast(ceil(log2(1. / prec.dfZResolution))); + } + if (prec.dfMResolution != 0) + { + nMBitPrecision = static_cast(ceil(log2(1. / prec.dfMResolution))); + } +} +/************************************************************************/ +/* OGRwkbExportOptionsCreate() */ +/************************************************************************/ + +/** + * \brief Create geometry WKB export options. + * + * The default is Intel order, old-OGC wkb variant and 0 discarded lsb bits. + * + * @return object to be freed with OGRwkbExportOptionsDestroy(). + * @since GDAL 3.9 + */ +OGRwkbExportOptions *OGRwkbExportOptionsCreate() +{ + return new OGRwkbExportOptions; +} + +/************************************************************************/ +/* OGRwkbExportOptionsDestroy() */ +/************************************************************************/ + +/** + * \brief Destroy object returned by OGRwkbExportOptionsCreate() + * + * @param psOptions WKB export options + * @since GDAL 3.9 + */ + +void OGRwkbExportOptionsDestroy(OGRwkbExportOptions *psOptions) +{ + delete psOptions; +} + +/************************************************************************/ +/* OGRwkbExportOptionsSetByteOrder() */ +/************************************************************************/ + +/** + * \brief Set the WKB byte order. + * + * @param psOptions WKB export options + * @param eByteOrder Byte order: wkbXDR (big-endian) or wkbNDR (little-endian, + * Intel) + * @since GDAL 3.9 + */ + +void OGRwkbExportOptionsSetByteOrder(OGRwkbExportOptions *psOptions, + OGRwkbByteOrder eByteOrder) +{ + psOptions->eByteOrder = eByteOrder; +} + +/************************************************************************/ +/* OGRwkbExportOptionsSetVariant() */ +/************************************************************************/ + +/** + * \brief Set the WKB variant + * + * @param psOptions WKB export options + * @param eWkbVariant variant: wkbVariantOldOgc, wkbVariantIso, + * wkbVariantPostGIS1 + * @since GDAL 3.9 + */ + +void OGRwkbExportOptionsSetVariant(OGRwkbExportOptions *psOptions, + OGRwkbVariant eWkbVariant) +{ + psOptions->eWkbVariant = eWkbVariant; +} + +/************************************************************************/ +/* OGRwkbExportOptionsSetPrecision() */ +/************************************************************************/ + +/** + * \brief Set precision options + * + * @param psOptions WKB export options + * @param hPrecisionOptions Precision options (might be null to reset them) + * @since GDAL 3.9 + */ + +void OGRwkbExportOptionsSetPrecision( + OGRwkbExportOptions *psOptions, + OGRGeomCoordinatePrecisionH hPrecisionOptions) +{ + psOptions->sPrecision = OGRGeomCoordinateBinaryPrecision(); + if (hPrecisionOptions) + psOptions->sPrecision.SetFrom(*hPrecisionOptions); +} diff --git a/ogr/ogrgeometrycollection.cpp b/ogr/ogrgeometrycollection.cpp index d074ff898718..d005d7ae6755 100644 --- a/ogr/ogrgeometrycollection.cpp +++ b/ogr/ogrgeometrycollection.cpp @@ -627,24 +627,32 @@ OGRErr OGRGeometryCollection::importFromWkb(const unsigned char *pabyData, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr +OGRGeometryCollection::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { - if (eWkbVariant == wkbVariantOldOgc && + if (psOptions == nullptr) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } + + OGRwkbExportOptions sOptions(*psOptions); + + if (sOptions.eWkbVariant == wkbVariantOldOgc && (wkbFlatten(getGeometryType()) == wkbMultiCurve || wkbFlatten(getGeometryType()) == wkbMultiSurface)) { // Does not make sense for new geometries, so patch it. - eWkbVariant = wkbVariantIso; + sOptions.eWkbVariant = wkbVariantIso; } /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(sOptions.eByteOrder)); /* -------------------------------------------------------------------- */ /* Set the geometry feature type, ensuring that 3D flag is */ @@ -652,9 +660,9 @@ OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder, /* -------------------------------------------------------------------- */ GUInt32 nGType = getGeometryType(); - if (eWkbVariant == wkbVariantIso) + if (sOptions.eWkbVariant == wkbVariantIso) nGType = getIsoGeometryType(); - else if (eWkbVariant == wkbVariantPostGIS1) + else if (sOptions.eWkbVariant == wkbVariantPostGIS1) { const bool bIs3D = wkbHasZ(static_cast(nGType)); nGType = wkbFlatten(nGType); @@ -668,7 +676,7 @@ OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder, static_cast(nGType | wkb25DBitInternalUse); } - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(sOptions.eByteOrder)) { nGType = CPL_SWAP32(nGType); } @@ -678,7 +686,7 @@ OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder, /* -------------------------------------------------------------------- */ /* Copy in the raw data. */ /* -------------------------------------------------------------------- */ - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(sOptions.eByteOrder)) { int nCount = CPL_SWAP32(nGeomCount); memcpy(pabyData + 5, &nCount, 4); @@ -696,7 +704,7 @@ OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder, int iGeom = 0; for (auto &&poSubGeom : *this) { - poSubGeom->exportToWkb(eByteOrder, pabyData + nOffset, eWkbVariant); + poSubGeom->exportToWkb(pabyData + nOffset, &sOptions); // Should normally not happen if everyone else does its job, // but has happened sometimes. (#6332) if (poSubGeom->getCoordinateDimension() != getCoordinateDimension()) diff --git a/ogr/ogrgeomfielddefn.cpp b/ogr/ogrgeomfielddefn.cpp index 54fed7d35776..bcbecba67e6a 100644 --- a/ogr/ogrgeomfielddefn.cpp +++ b/ogr/ogrgeomfielddefn.cpp @@ -86,6 +86,7 @@ OGRGeomFieldDefn::OGRGeomFieldDefn(const OGRGeomFieldDefn *poPrototype) l_poSRS->Release(); } SetNullable(poPrototype->IsNullable()); + SetCoordinatePrecision(poPrototype->GetCoordinatePrecision()); } /************************************************************************/ @@ -598,7 +599,13 @@ int OGRGeomFieldDefn::IsSame(const OGRGeomFieldDefn *poOtherFieldDefn) const { if (!(strcmp(GetNameRef(), poOtherFieldDefn->GetNameRef()) == 0 && GetType() == poOtherFieldDefn->GetType() && - IsNullable() == poOtherFieldDefn->IsNullable())) + IsNullable() == poOtherFieldDefn->IsNullable() && + m_oCoordPrecision.dfXYResolution == + poOtherFieldDefn->m_oCoordPrecision.dfXYResolution && + m_oCoordPrecision.dfZResolution == + poOtherFieldDefn->m_oCoordPrecision.dfZResolution && + m_oCoordPrecision.dfMResolution == + poOtherFieldDefn->m_oCoordPrecision.dfMResolution)) return FALSE; const OGRSpatialReference *poMySRS = GetSpatialRef(); const OGRSpatialReference *poOtherSRS = poOtherFieldDefn->GetSpatialRef(); @@ -728,6 +735,96 @@ void OGR_GFld_SetNullable(OGRGeomFieldDefnH hDefn, int bNullableIn) OGRGeomFieldDefn::FromHandle(hDefn)->SetNullable(bNullableIn); } +/************************************************************************/ +/* GetCoordinatePrecision() */ +/************************************************************************/ + +/** + * \fn int OGRGeomFieldDefn::GetCoordinatePrecision() const + * + * \brief Return the coordinate precision associated to this geometry field. + * + * This method is the same as the C function OGR_GFld_GetCoordinatePrecision(). + * + * @return the coordinate precision + * @since GDAL 3.9 + */ + +/************************************************************************/ +/* OGR_GFld_GetCoordinatePrecision() */ +/************************************************************************/ + +/** + * \brief Return the coordinate precision associated to this geometry field. + * + * This method is the same as the C++ method OGRGeomFieldDefn::GetCoordinatePrecision() + * + * @param hDefn handle to the field definition + * @return the coordinate precision + * @since GDAL 3.9 + */ + +OGRGeomCoordinatePrecisionH +OGR_GFld_GetCoordinatePrecision(OGRGeomFieldDefnH hDefn) +{ + return const_cast( + &(OGRGeomFieldDefn::FromHandle(hDefn)->GetCoordinatePrecision())); +} + +/************************************************************************/ +/* SetCoordinatePrecision() */ +/************************************************************************/ + +/** + * \brief Set coordinate precision associated to this geometry field. + * + * This method is the same as the C function OGR_GFld_SetCoordinatePrecision(). + * + * Note that once a OGRGeomFieldDefn has been added to a layer definition with + * OGRLayer::AddGeomFieldDefn(), its setter methods should not be called on the + * object returned with OGRLayer::GetLayerDefn()->GetGeomFieldDefn(). + * + * @param prec Coordinate precision + * @since GDAL 3.9 + */ +void OGRGeomFieldDefn::SetCoordinatePrecision( + const OGRGeomCoordinatePrecision &prec) +{ + if (m_bSealed) + { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRGeomFieldDefn::SetCoordinatePrecision() not allowed on a " + "sealed object"); + return; + } + m_oCoordPrecision = prec; +} + +/************************************************************************/ +/* OGR_GFld_SetCoordinatePrecision() */ +/************************************************************************/ + +/** + * \brief Set coordinate precision associated to this geometry field. + * + * This method is the same as the C++ method OGRGeomFieldDefn::SetCoordinatePrecision() + * + * Note that once a OGRGeomFieldDefn has been added to a layer definition with + * OGRLayer::AddGeomFieldDefn(), its setter methods should not be called on the + * object returned with OGRLayer::GetLayerDefn()->GetGeomFieldDefn(). + * + * @param hDefn handle to the field definition. Must not be NULL. + * @param hGeomCoordPrec Coordinate precision. Must not be NULL. + * @since GDAL 3.9 + */ +void OGR_GFld_SetCoordinatePrecision(OGRGeomFieldDefnH hDefn, + OGRGeomCoordinatePrecisionH hGeomCoordPrec) +{ + VALIDATE_POINTER0(hGeomCoordPrec, "OGR_GFld_SetCoordinatePrecision"); + OGRGeomFieldDefn::FromHandle(hDefn)->SetCoordinatePrecision( + *hGeomCoordPrec); +} + /************************************************************************/ /* OGRGeomFieldDefn::Seal() */ /************************************************************************/ diff --git a/ogr/ogrlinearring.cpp b/ogr/ogrlinearring.cpp index b3480ac045b7..ffe247e97bf3 100644 --- a/ogr/ogrlinearring.cpp +++ b/ogr/ogrlinearring.cpp @@ -163,9 +163,8 @@ OGRErr OGRLinearRing::importFromWkb(const unsigned char * /*pabyData*/, /* Disable method for this class. */ /************************************************************************/ -OGRErr OGRLinearRing::exportToWkb(CPL_UNUSED OGRwkbByteOrder eByteOrder, - CPL_UNUSED unsigned char *pabyData, - CPL_UNUSED OGRwkbVariant eWkbVariant) const +OGRErr OGRLinearRing::exportToWkb(CPL_UNUSED unsigned char *pabyData, + CPL_UNUSED const OGRwkbExportOptions *) const { return OGRERR_UNSUPPORTED_OPERATION; @@ -307,8 +306,8 @@ OGRErr OGRLinearRing::_importFromWkb(OGRwkbByteOrder eByteOrder, int _flags, /* exportToWkb() METHOD. */ /************************************************************************/ -OGRErr OGRLinearRing::_exportToWkb(OGRwkbByteOrder eByteOrder, int _flags, - unsigned char *pabyData) const +OGRErr OGRLinearRing::_exportToWkb(int _flags, unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { @@ -337,6 +336,14 @@ OGRErr OGRLinearRing::_exportToWkb(OGRwkbByteOrder eByteOrder, int _flags, else memcpy(pabyData + 4 + i * 32 + 24, padfM + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<32>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 4, nPointCount); + OGRRoundCoordinatesIEEE754<32>(psOptions->sPrecision.nZBitPrecision, + pabyData + 4 + 2 * sizeof(uint64_t), + nPointCount); + OGRRoundCoordinatesIEEE754<32>(psOptions->sPrecision.nMBitPrecision, + pabyData + 4 + 3 * sizeof(uint64_t), + nPointCount); } else if (_flags & OGR_G_MEASURED) { @@ -350,6 +357,11 @@ OGRErr OGRLinearRing::_exportToWkb(OGRwkbByteOrder eByteOrder, int _flags, else memcpy(pabyData + 4 + i * 24 + 16, padfM + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<24>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 4, nPointCount); + OGRRoundCoordinatesIEEE754<24>(psOptions->sPrecision.nMBitPrecision, + pabyData + 4 + 2 * sizeof(uint64_t), + nPointCount); } else if (_flags & OGR_G_3D) { @@ -363,17 +375,24 @@ OGRErr OGRLinearRing::_exportToWkb(OGRwkbByteOrder eByteOrder, int _flags, else memcpy(pabyData + 4 + i * 24 + 16, padfZ + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<24>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 4, nPointCount); + OGRRoundCoordinatesIEEE754<24>(psOptions->sPrecision.nZBitPrecision, + pabyData + 4 + 2 * sizeof(uint64_t), + nPointCount); } else { nWords = 2 * static_cast(nPointCount); memcpy(pabyData + 4, paoPoints, 16 * static_cast(nPointCount)); + OGRRoundCoordinatesIEEE754XYValues<16>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 4, nPointCount); } /* -------------------------------------------------------------------- */ /* Swap if needed. */ /* -------------------------------------------------------------------- */ - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { const int nCount = CPL_SWAP32(nPointCount); memcpy(pabyData, &nCount, 4); diff --git a/ogr/ogrlinestring.cpp b/ogr/ogrlinestring.cpp index b75d1a288a69..d729ca602257 100644 --- a/ogr/ogrlinestring.cpp +++ b/ogr/ogrlinestring.cpp @@ -1632,23 +1632,28 @@ OGRErr OGRSimpleCurve::importFromWkb(const unsigned char *pabyData, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRSimpleCurve::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr OGRSimpleCurve::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + if (psOptions == nullptr) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } + /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(psOptions->eByteOrder)); /* -------------------------------------------------------------------- */ /* Set the geometry feature type. */ /* -------------------------------------------------------------------- */ GUInt32 nGType = getGeometryType(); - if (eWkbVariant == wkbVariantPostGIS1) + if (psOptions->eWkbVariant == wkbVariantPostGIS1) { nGType = wkbFlatten(nGType); if (Is3D()) @@ -1658,10 +1663,10 @@ OGRErr OGRSimpleCurve::exportToWkb(OGRwkbByteOrder eByteOrder, if (IsMeasured()) nGType = static_cast(nGType | 0x40000000); } - else if (eWkbVariant == wkbVariantIso) + else if (psOptions->eWkbVariant == wkbVariantIso) nGType = getIsoGeometryType(); - if (eByteOrder == wkbNDR) + if (psOptions->eByteOrder == wkbNDR) { CPL_LSBPTR32(&nGType); } @@ -1688,6 +1693,14 @@ OGRErr OGRSimpleCurve::exportToWkb(OGRwkbByteOrder eByteOrder, memcpy(pabyData + 9 + 16 + 32 * i, padfZ + i, 8); memcpy(pabyData + 9 + 24 + 32 * i, padfM + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<32>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 9, nPointCount); + OGRRoundCoordinatesIEEE754<32>(psOptions->sPrecision.nZBitPrecision, + pabyData + 9 + 2 * sizeof(uint64_t), + nPointCount); + OGRRoundCoordinatesIEEE754<32>(psOptions->sPrecision.nMBitPrecision, + pabyData + 9 + 3 * sizeof(uint64_t), + nPointCount); } else if (flags & OGR_G_MEASURED) { @@ -1696,6 +1709,11 @@ OGRErr OGRSimpleCurve::exportToWkb(OGRwkbByteOrder eByteOrder, memcpy(pabyData + 9 + 24 * i, paoPoints + i, 16); memcpy(pabyData + 9 + 16 + 24 * i, padfM + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<24>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 9, nPointCount); + OGRRoundCoordinatesIEEE754<24>(psOptions->sPrecision.nMBitPrecision, + pabyData + 9 + 2 * sizeof(uint64_t), + nPointCount); } else if (flags & OGR_G_3D) { @@ -1704,14 +1722,23 @@ OGRErr OGRSimpleCurve::exportToWkb(OGRwkbByteOrder eByteOrder, memcpy(pabyData + 9 + 24 * i, paoPoints + i, 16); memcpy(pabyData + 9 + 16 + 24 * i, padfZ + i, 8); } + OGRRoundCoordinatesIEEE754XYValues<24>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 9, nPointCount); + OGRRoundCoordinatesIEEE754<24>(psOptions->sPrecision.nZBitPrecision, + pabyData + 9 + 2 * sizeof(uint64_t), + nPointCount); } else if (nPointCount) + { memcpy(pabyData + 9, paoPoints, 16 * static_cast(nPointCount)); + OGRRoundCoordinatesIEEE754XYValues<16>( + psOptions->sPrecision.nXYBitPrecision, pabyData + 9, nPointCount); + } /* -------------------------------------------------------------------- */ /* Swap if needed. */ /* -------------------------------------------------------------------- */ - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { const int nCount = CPL_SWAP32(nPointCount); memcpy(pabyData + 5, &nCount, 4); diff --git a/ogr/ogrpoint.cpp b/ogr/ogrpoint.cpp index 9a6100986fec..aa35407f0ea5 100644 --- a/ogr/ogrpoint.cpp +++ b/ogr/ogrpoint.cpp @@ -380,16 +380,21 @@ OGRErr OGRPoint::importFromWkb(const unsigned char *pabyData, size_t nSize, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRPoint::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr OGRPoint::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + if (!psOptions) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } + /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(psOptions->eByteOrder)); pabyData += 1; /* -------------------------------------------------------------------- */ @@ -398,7 +403,7 @@ OGRErr OGRPoint::exportToWkb(OGRwkbByteOrder eByteOrder, GUInt32 nGType = getGeometryType(); - if (eWkbVariant == wkbVariantPostGIS1) + if (psOptions->eWkbVariant == wkbVariantPostGIS1) { nGType = wkbFlatten(nGType); if (Is3D()) @@ -408,12 +413,12 @@ OGRErr OGRPoint::exportToWkb(OGRwkbByteOrder eByteOrder, if (IsMeasured()) nGType = static_cast(nGType | 0x40000000); } - else if (eWkbVariant == wkbVariantIso) + else if (psOptions->eWkbVariant == wkbVariantIso) { nGType = getIsoGeometryType(); } - if (eByteOrder == wkbNDR) + if (psOptions->eByteOrder == wkbNDR) { CPL_LSBPTR32(&nGType); } @@ -429,52 +434,58 @@ OGRErr OGRPoint::exportToWkb(OGRwkbByteOrder eByteOrder, /* Copy in the raw data. Swap if needed. */ /* -------------------------------------------------------------------- */ - if (IsEmpty() && eWkbVariant == wkbVariantIso) + if (IsEmpty() && psOptions->eWkbVariant == wkbVariantIso) { const double dNan = std::numeric_limits::quiet_NaN(); memcpy(pabyData, &dNan, 8); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); pabyData += 8; memcpy(pabyData, &dNan, 8); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); pabyData += 8; if (flags & OGR_G_3D) { memcpy(pabyData, &dNan, 8); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); pabyData += 8; } if (flags & OGR_G_MEASURED) { memcpy(pabyData, &dNan, 8); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); } } else { memcpy(pabyData, &x, 8); - if (OGR_SWAP(eByteOrder)) - CPL_SWAPDOUBLE(pabyData); - pabyData += 8; - memcpy(pabyData, &y, 8); - if (OGR_SWAP(eByteOrder)) + memcpy(pabyData + 8, &y, 8); + OGRRoundCoordinatesIEEE754XYValues<0>( + psOptions->sPrecision.nXYBitPrecision, pabyData, 1); + if (OGR_SWAP(psOptions->eByteOrder)) + { CPL_SWAPDOUBLE(pabyData); - pabyData += 8; + CPL_SWAPDOUBLE(pabyData + 8); + } + pabyData += 16; if (flags & OGR_G_3D) { memcpy(pabyData, &z, 8); - if (OGR_SWAP(eByteOrder)) + OGRRoundCoordinatesIEEE754<0>(psOptions->sPrecision.nZBitPrecision, + pabyData, 1); + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); pabyData += 8; } if (flags & OGR_G_MEASURED) { memcpy(pabyData, &m, 8); - if (OGR_SWAP(eByteOrder)) + OGRRoundCoordinatesIEEE754<0>(psOptions->sPrecision.nMBitPrecision, + pabyData, 1); + if (OGR_SWAP(psOptions->eByteOrder)) CPL_SWAPDOUBLE(pabyData); } } diff --git a/ogr/ogrpolygon.cpp b/ogr/ogrpolygon.cpp index 1d7dc60e2ede..249f4378f572 100644 --- a/ogr/ogrpolygon.cpp +++ b/ogr/ogrpolygon.cpp @@ -427,24 +427,28 @@ OGRErr OGRPolygon::importFromWkb(const unsigned char *pabyData, size_t nSize, /* Build a well known binary representation of this object. */ /************************************************************************/ -OGRErr OGRPolygon::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant eWkbVariant) const +OGRErr OGRPolygon::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + if (psOptions == nullptr) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(psOptions->eByteOrder)); /* -------------------------------------------------------------------- */ /* Set the geometry feature type. */ /* -------------------------------------------------------------------- */ GUInt32 nGType = getGeometryType(); - if (eWkbVariant == wkbVariantPostGIS1) + if (psOptions->eWkbVariant == wkbVariantPostGIS1) { nGType = wkbFlatten(nGType); if (Is3D()) @@ -454,10 +458,10 @@ OGRErr OGRPolygon::exportToWkb(OGRwkbByteOrder eByteOrder, if (IsMeasured()) nGType = static_cast(nGType | 0x40000000); } - else if (eWkbVariant == wkbVariantIso) + else if (psOptions->eWkbVariant == wkbVariantIso) nGType = getIsoGeometryType(); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { nGType = CPL_SWAP32(nGType); } @@ -467,7 +471,7 @@ OGRErr OGRPolygon::exportToWkb(OGRwkbByteOrder eByteOrder, /* -------------------------------------------------------------------- */ /* Copy in the raw data. */ /* -------------------------------------------------------------------- */ - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { const int nCount = CPL_SWAP32(oCC.nCurveCount); memcpy(pabyData + 5, &nCount, 4); @@ -484,7 +488,7 @@ OGRErr OGRPolygon::exportToWkb(OGRwkbByteOrder eByteOrder, for (auto &&poRing : *this) { - poRing->_exportToWkb(eByteOrder, flags, pabyData + nOffset); + poRing->_exportToWkb(flags, pabyData + nOffset, psOptions); nOffset += poRing->_WkbSize(flags); } diff --git a/ogr/ogrpolyhedralsurface.cpp b/ogr/ogrpolyhedralsurface.cpp index 37c5ae86eb82..6ee762b42856 100644 --- a/ogr/ogrpolyhedralsurface.cpp +++ b/ogr/ogrpolyhedralsurface.cpp @@ -279,16 +279,22 @@ OGRErr OGRPolyhedralSurface::importFromWkb(const unsigned char *pabyData, /* exportToWkb() */ /************************************************************************/ -OGRErr OGRPolyhedralSurface::exportToWkb(OGRwkbByteOrder eByteOrder, - unsigned char *pabyData, - OGRwkbVariant /*eWkbVariant*/) const +OGRErr +OGRPolyhedralSurface::exportToWkb(unsigned char *pabyData, + const OGRwkbExportOptions *psOptions) const { + if (!psOptions) + { + static const OGRwkbExportOptions defaultOptions; + psOptions = &defaultOptions; + } + /* -------------------------------------------------------------------- */ /* Set the byte order. */ /* -------------------------------------------------------------------- */ - pabyData[0] = - DB2_V72_UNFIX_BYTE_ORDER(static_cast(eByteOrder)); + pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER( + static_cast(psOptions->eByteOrder)); /* -------------------------------------------------------------------- */ /* Set the geometry feature type, ensuring that 3D flag is */ @@ -296,7 +302,7 @@ OGRErr OGRPolyhedralSurface::exportToWkb(OGRwkbByteOrder eByteOrder, /* -------------------------------------------------------------------- */ GUInt32 nGType = getIsoGeometryType(); - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { nGType = CPL_SWAP32(nGType); } @@ -304,7 +310,7 @@ OGRErr OGRPolyhedralSurface::exportToWkb(OGRwkbByteOrder eByteOrder, memcpy(pabyData + 1, &nGType, 4); // Copy the raw data - if (OGR_SWAP(eByteOrder)) + if (OGR_SWAP(psOptions->eByteOrder)) { int nCount = CPL_SWAP32(oMP.nGeomCount); memcpy(pabyData + 5, &nCount, 4); @@ -317,7 +323,7 @@ OGRErr OGRPolyhedralSurface::exportToWkb(OGRwkbByteOrder eByteOrder, // serialize each of the geometries for (auto &&poSubGeom : *this) { - poSubGeom->exportToWkb(eByteOrder, pabyData + nOffset, wkbVariantIso); + poSubGeom->exportToWkb(pabyData + nOffset, psOptions); nOffset += poSubGeom->WkbSize(); } diff --git a/ogr/ogrsf_frmts/amigocloud/ogr_amigocloud.h b/ogr/ogrsf_frmts/amigocloud/ogr_amigocloud.h index 4f9ebed5d2c1..251dad6d1852 100644 --- a/ogr/ogrsf_frmts/amigocloud/ogr_amigocloud.h +++ b/ogr/ogrsf_frmts/amigocloud/ogr_amigocloud.h @@ -290,11 +290,9 @@ class OGRAmigoCloudDataSource final : public OGRDataSource virtual int TestCapability(const char *) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr DeleteLayer(int) override; virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand, diff --git a/ogr/ogrsf_frmts/amigocloud/ogramigoclouddatasource.cpp b/ogr/ogrsf_frmts/amigocloud/ogramigoclouddatasource.cpp index c8141069fadd..3cd0ed5cfe01 100644 --- a/ogr/ogrsf_frmts/amigocloud/ogramigoclouddatasource.cpp +++ b/ogr/ogrsf_frmts/amigocloud/ogramigoclouddatasource.cpp @@ -370,9 +370,10 @@ int OGRAmigoCloudDataSource::FetchSRSId(OGRSpatialReference *poSRS) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRAmigoCloudDataSource::ICreateLayer( - const char *pszNameIn, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRAmigoCloudDataSource::ICreateLayer(const char *pszNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!bReadWrite) { @@ -381,6 +382,10 @@ OGRLayer *OGRAmigoCloudDataSource::ICreateLayer( return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + CPLString osName(pszNameIn); OGRAmigoCloudTableLayer *poLayer = new OGRAmigoCloudTableLayer(this, osName); diff --git a/ogr/ogrsf_frmts/arrow/ogr_feather.h b/ogr/ogrsf_frmts/arrow/ogr_feather.h index e15642f5ab6e..59bc6b51df16 100644 --- a/ogr/ogrsf_frmts/arrow/ogr_feather.h +++ b/ogr/ogrsf_frmts/arrow/ogr_feather.h @@ -234,9 +234,8 @@ class OGRFeatherWriterDataset final : public GDALPamDataset protected: OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; }; #endif // OGR_FEATHER_H diff --git a/ogr/ogrsf_frmts/arrow/ogrfeatherwriterdataset.cpp b/ogr/ogrsf_frmts/arrow/ogrfeatherwriterdataset.cpp index cbb2b86a1d1f..2f922fd14b3c 100644 --- a/ogr/ogrsf_frmts/arrow/ogrfeatherwriterdataset.cpp +++ b/ogr/ogrsf_frmts/arrow/ogrfeatherwriterdataset.cpp @@ -78,9 +78,10 @@ int OGRFeatherWriterDataset::TestCapability(const char *pszCap) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRFeatherWriterDataset::ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRFeatherWriterDataset::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (m_poLayer) { @@ -88,6 +89,11 @@ OGRLayer *OGRFeatherWriterDataset::ICreateLayer( "Can write only one layer in a Feather file"); return nullptr; } + + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + m_poLayer = std::make_unique( m_poMemoryPool.get(), m_poOutputStream, pszName); if (!m_poLayer->SetOptions(m_osFilename, papszOptions, poSpatialRef, diff --git a/ogr/ogrsf_frmts/arrow_common/ograrrowwriterlayer.hpp b/ogr/ogrsf_frmts/arrow_common/ograrrowwriterlayer.hpp index 72598b04e613..118bd2d6cdb8 100644 --- a/ogr/ogrsf_frmts/arrow_common/ograrrowwriterlayer.hpp +++ b/ogr/ogrsf_frmts/arrow_common/ograrrowwriterlayer.hpp @@ -1024,7 +1024,9 @@ inline OGRErr OGRArrowWriterLayer::BuildGeometry(OGRGeometry *poGeom, if (m_nWKTCoordinatePrecision >= 0) { options.format = OGRWktFormat::F; - options.precision = m_nWKTCoordinatePrecision; + options.xyPrecision = m_nWKTCoordinatePrecision; + options.zPrecision = m_nWKTCoordinatePrecision; + options.mPrecision = m_nWKTCoordinatePrecision; } OGR_ARROW_RETURN_OGRERR_NOT_OK( static_cast(poBuilder)->Append( diff --git a/ogr/ogrsf_frmts/carto/ogr_carto.h b/ogr/ogrsf_frmts/carto/ogr_carto.h index fb552dcf78c5..4c5edc9faf05 100644 --- a/ogr/ogrsf_frmts/carto/ogr_carto.h +++ b/ogr/ogrsf_frmts/carto/ogr_carto.h @@ -302,11 +302,9 @@ class OGRCARTODataSource final : public OGRDataSource virtual int TestCapability(const char *) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr DeleteLayer(int) override; virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand, diff --git a/ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp b/ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp index f3756669e1fc..3b8ca2da213f 100644 --- a/ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp +++ b/ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp @@ -410,8 +410,8 @@ int OGRCARTODataSource::FetchSRSId(const OGRSpatialReference *poSRS) OGRLayer * OGRCARTODataSource::ICreateLayer(const char *pszNameIn, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!bReadWrite) { @@ -420,6 +420,10 @@ OGRCARTODataSource::ICreateLayer(const char *pszNameIn, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Do we already have this layer? If so, set it up for overwrite */ /* away? */ diff --git a/ogr/ogrsf_frmts/csv/ogr_csv.h b/ogr/ogrsf_frmts/csv/ogr_csv.h index fb42dc20c820..ae60a80ec4e0 100644 --- a/ogr/ogrsf_frmts/csv/ogr_csv.h +++ b/ogr/ogrsf_frmts/csv/ogr_csv.h @@ -298,11 +298,9 @@ class OGRCSVDataSource final : public OGRDataSource char **GetFileList() override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr DeleteLayer(int) override; diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp index e11483e31579..62d2e1c4ebdb 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp @@ -919,8 +919,8 @@ bool OGRCSVDataSource::OpenTable(const char *pszFilename, OGRLayer * OGRCSVDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { // Verify we are in update mode. if (!bUpdate) @@ -933,6 +933,11 @@ OGRCSVDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + // Verify that the datasource is a directory. VSIStatBufL sStatBuf; @@ -1121,6 +1126,14 @@ OGRCSVDataSource::ICreateLayer(const char *pszLayerName, if (pszWriteBOM) poCSVLayer->SetWriteBOM(CPLTestBool(pszWriteBOM)); + if (poCSVLayer->GetLayerDefn()->GetGeomFieldCount() > 0 && + poSrcGeomFieldDefn) + { + auto poGeomFieldDefn = poCSVLayer->GetLayerDefn()->GetGeomFieldDefn(0); + poGeomFieldDefn->SetCoordinatePrecision( + poSrcGeomFieldDefn->GetCoordinatePrecision()); + } + if (osFilename != "/vsistdout/") m_apoLayers.emplace_back(std::make_unique( poCSVLayer.release(), nullptr)); diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp index 9be0538cbb01..74fb83eca498 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp @@ -441,6 +441,7 @@ void RegisterOGRCSV() "StringList"); poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean Int16 Float32"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnOpen = OGRCSVDriverOpen; poDriver->pfnIdentify = OGRCSVDriverIdentify; diff --git a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp index 606da4719cec..44b56726291b 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp @@ -2221,33 +2221,67 @@ OGRErr OGRCSVLayer::ICreateFeature(OGRFeature *poNewFeature) bool bNeedDelimiter = false; bool bEmptyLine = true; + const auto GetWktOptions = [](const OGRGeomFieldDefn *poGeomFieldDefn) + { + const auto &sCoordPrec = poGeomFieldDefn->GetCoordinatePrecision(); + + OGRWktOptions wktOptions; + wktOptions.variant = wkbVariantIso; + if (sCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + wktOptions.format = OGRWktFormat::F; + wktOptions.xyPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + sCoordPrec.dfXYResolution); + } + if (sCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + wktOptions.format = OGRWktFormat::F; + wktOptions.zPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + sCoordPrec.dfZResolution); + } + if (sCoordPrec.dfMResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + wktOptions.format = OGRWktFormat::F; + wktOptions.mPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + sCoordPrec.dfMResolution); + } + + return wktOptions; + }; + // Write out the geometry. if (eGeometryFormat == OGR_CSV_GEOM_AS_XYZ || eGeometryFormat == OGR_CSV_GEOM_AS_XY || eGeometryFormat == OGR_CSV_GEOM_AS_YX) { - OGRGeometry *poGeom = poNewFeature->GetGeometryRef(); + const OGRGeometry *poGeom = poNewFeature->GetGeometryRef(); if (poGeom && wkbFlatten(poGeom->getGeometryType()) == wkbPoint) { - OGRPoint *poPoint = poGeom->toPoint(); - char szBuffer[75] = {}; + const auto poGeomFieldDefn = poFeatureDefn->GetGeomFieldDefn(0); + const OGRPoint *poPoint = poGeom->toPoint(); + std::string osCoord; if (eGeometryFormat == OGR_CSV_GEOM_AS_XYZ) - OGRMakeWktCoordinate(szBuffer, poPoint->getX(), poPoint->getY(), - poPoint->getZ(), 3); + osCoord = OGRMakeWktCoordinate(poPoint->getX(), poPoint->getY(), + poPoint->getZ(), 3, + GetWktOptions(poGeomFieldDefn)); else if (eGeometryFormat == OGR_CSV_GEOM_AS_XY) - OGRMakeWktCoordinate(szBuffer, poPoint->getX(), poPoint->getY(), - 0, 2); + osCoord = + OGRMakeWktCoordinate(poPoint->getX(), poPoint->getY(), 0, 2, + GetWktOptions(poGeomFieldDefn)); else - OGRMakeWktCoordinate(szBuffer, poPoint->getY(), poPoint->getX(), - 0, 2); - char *pc = szBuffer; - while (*pc != '\0') - { - if (*pc == ' ') - *pc = szDelimiter[0]; - pc++; + osCoord = + OGRMakeWktCoordinate(poPoint->getY(), poPoint->getX(), 0, 2, + GetWktOptions(poGeomFieldDefn)); + + for (char &ch : osCoord) + { + if (ch == ' ') + ch = szDelimiter[0]; } - bRet &= VSIFPrintfL(fpCSV, "%s", szBuffer) > 0; + bRet &= VSIFPrintfL(fpCSV, "%s", osCoord.c_str()) > 0; } else { @@ -2260,17 +2294,20 @@ OGRErr OGRCSVLayer::ICreateFeature(OGRFeature *poNewFeature) } else if (bHiddenWKTColumn) { - char *pszWKT = nullptr; - OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(0); - if (poGeom && - poGeom->exportToWkt(&pszWKT, wkbVariantIso) == OGRERR_NONE) + const OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(0); + if (poGeom) { - bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0; - bRet &= VSIFWriteL(pszWKT, strlen(pszWKT), 1, fpCSV) > 0; - bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0; - bEmptyLine = false; + const auto poGeomFieldDefn = poFeatureDefn->GetGeomFieldDefn(0); + const std::string wkt = + poGeom->exportToWkt(GetWktOptions(poGeomFieldDefn)); + if (!wkt.empty()) + { + bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0; + bRet &= VSIFWriteL(wkt.c_str(), wkt.size(), 1, fpCSV) > 0; + bRet &= VSIFWriteL("\"", 1, 1, fpCSV) > 0; + bEmptyLine = false; + } } - CPLFree(pszWKT); bNeedDelimiter = true; } @@ -2290,19 +2327,29 @@ OGRErr OGRCSVLayer::ICreateFeature(OGRFeature *poNewFeature) panGeomFieldIndex[iField] >= 0) { const int iGeom = panGeomFieldIndex[iField]; - OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(iGeom); - if (poGeom && - poGeom->exportToWkt(&pszEscaped, wkbVariantIso) == OGRERR_NONE) - { - const int nLenWKT = static_cast(strlen(pszEscaped)); - char *pszNew = - static_cast(CPLMalloc(1 + nLenWKT + 1 + 1)); - pszNew[0] = '"'; - memcpy(pszNew + 1, pszEscaped, nLenWKT); - pszNew[1 + nLenWKT] = '"'; - pszNew[1 + nLenWKT + 1] = '\0'; - CPLFree(pszEscaped); - pszEscaped = pszNew; + const OGRGeometry *poGeom = poNewFeature->GetGeomFieldRef(iGeom); + if (poGeom) + { + const auto poGeomFieldDefn = + poFeatureDefn->GetGeomFieldDefn(iGeom); + const std::string wkt = + poGeom->exportToWkt(GetWktOptions(poGeomFieldDefn)); + if (!wkt.empty()) + { + char *pszNew = + static_cast(CPLMalloc(1 + wkt.size() + 1 + 1)); + pszNew[0] = '"'; + memcpy(pszNew + 1, wkt.c_str(), wkt.size()); + pszNew[1 + wkt.size()] = '"'; + pszNew[1 + wkt.size() + 1] = '\0'; + CPLFree(pszEscaped); + pszEscaped = pszNew; + } + else + { + CPLFree(pszEscaped); + pszEscaped = CPLStrdup(""); + } } else { diff --git a/ogr/ogrsf_frmts/dgn/ogr_dgn.h b/ogr/ogrsf_frmts/dgn/ogr_dgn.h index b9e5a3a92574..f68fbb94ab4f 100644 --- a/ogr/ogrsf_frmts/dgn/ogr_dgn.h +++ b/ogr/ogrsf_frmts/dgn/ogr_dgn.h @@ -115,10 +115,9 @@ class OGRDGNDataSource final : public OGRDataSource int Open(const char *, int bTestOpen, int bUpdate); bool PreCreate(const char *, char **); - OGRLayer *ICreateLayer(const char *, const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; - + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList) override; const char *GetName() override { return pszName; diff --git a/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp b/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp index bf5f49893181..4fbf9a887c84 100644 --- a/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp +++ b/ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp @@ -170,10 +170,10 @@ bool OGRDGNDataSource::PreCreate(const char *pszFilename, char **papszOptionsIn) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRDGNDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGeomType, - char **papszExtraOptions) +OGRLayer * +OGRDGNDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszExtraOptions) { /* -------------------------------------------------------------------- */ @@ -187,6 +187,11 @@ OGRLayer *OGRDGNDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eGeomType = + poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* If the coordinate system is geographic, we should use a */ /* localized default origin and resolution. */ diff --git a/ogr/ogrsf_frmts/dwg/ogr_dgnv8.h b/ogr/ogrsf_frmts/dwg/ogr_dgnv8.h index f0fe9370dc3b..78b2b8fc38b8 100644 --- a/ogr/ogrsf_frmts/dwg/ogr_dgnv8.h +++ b/ogr/ogrsf_frmts/dwg/ogr_dgnv8.h @@ -144,9 +144,9 @@ class OGRDGNV8DataSource final : public GDALDataset int Open(const char *, bool bUpdate); bool PreCreate(const char *, char **); - OGRLayer *ICreateLayer(const char *, const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int GetLayerCount() override { diff --git a/ogr/ogrsf_frmts/dwg/ogrdgnv8datasource.cpp b/ogr/ogrsf_frmts/dwg/ogrdgnv8datasource.cpp index e1cb60d30c5b..d0afe907b17c 100644 --- a/ogr/ogrsf_frmts/dwg/ogrdgnv8datasource.cpp +++ b/ogr/ogrsf_frmts/dwg/ogrdgnv8datasource.cpp @@ -521,9 +521,10 @@ OdString OGRDGNV8DataSource::FromUTF8(const CPLString &str) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRDGNV8DataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference * /*poSRS*/, - OGRwkbGeometryType /*eGeomType*/, char **papszOptions) +OGRLayer * +OGRDGNV8DataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn * /*poGeomFieldDefn*/, + CSLConstList papszOptions) { if (!m_bUpdate) diff --git a/ogr/ogrsf_frmts/dxf/ogr_dxf.h b/ogr/ogrsf_frmts/dxf/ogr_dxf.h index ac9b3693fa3a..5130aafc303a 100644 --- a/ogr/ogrsf_frmts/dxf/ogr_dxf.h +++ b/ogr/ogrsf_frmts/dxf/ogr_dxf.h @@ -946,9 +946,8 @@ class OGRDXFWriterDS final : public OGRDataSource int TestCapability(const char *) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; bool CheckEntityID(const char *pszEntityID); bool WriteEntityID(VSILFILE *fp, long &nAssignedFID, diff --git a/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp b/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp index f13fa5cd3f38..dd63e9f4c59a 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp @@ -274,9 +274,10 @@ int OGRDXFWriterDS::Open(const char *pszFilename, char **papszOptions) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRDXFWriterDS::ICreateLayer(const char *pszName, - const OGRSpatialReference *, - OGRwkbGeometryType, char **) +OGRLayer * +OGRDXFWriterDS::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn * /*poGeomFieldDefn*/, + CSLConstList /*papszOptions*/) { if (EQUAL(pszName, "blocks") && poBlocksLayer == nullptr) diff --git a/ogr/ogrsf_frmts/elastic/ogr_elastic.h b/ogr/ogrsf_frmts/elastic/ogr_elastic.h index 821437d60f73..28cc659aa511 100644 --- a/ogr/ogrsf_frmts/elastic/ogr_elastic.h +++ b/ogr/ogrsf_frmts/elastic/ogr_elastic.h @@ -176,7 +176,8 @@ class OGRElasticLayer final : public OGRLayer public: OGRElasticLayer(const char *pszLayerName, const char *pszIndexName, const char *pszMappingName, OGRElasticDataSource *poDS, - char **papszOptions, const char *pszESSearch = nullptr); + CSLConstList papszOptions, + const char *pszESSearch = nullptr); OGRElasticLayer(const char *pszLayerName, OGRElasticLayer *poReferenceLayer); virtual ~OGRElasticLayer(); @@ -376,10 +377,9 @@ class OGRElasticDataSource final : public GDALDataset virtual OGRLayer *GetLayer(int) override; virtual OGRLayer *GetLayerByName(const char *pszName) override; - virtual OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr DeleteLayer(int iLayer) override; virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand, diff --git a/ogr/ogrsf_frmts/elastic/ogrelasticdatasource.cpp b/ogr/ogrsf_frmts/elastic/ogrelasticdatasource.cpp index 6b4ace58f698..02a9b464c3e5 100644 --- a/ogr/ogrsf_frmts/elastic/ogrelasticdatasource.cpp +++ b/ogr/ogrsf_frmts/elastic/ogrelasticdatasource.cpp @@ -412,10 +412,10 @@ OGRErr OGRElasticDataSource::DeleteLayer(int iLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRElasticDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, - char **papszOptions) +OGRLayer * +OGRElasticDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (eAccess != GA_Update) { @@ -424,6 +424,10 @@ OGRLayer *OGRElasticDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + CPLString osLaunderedName(pszLayerName); const char *pszIndexName = CSLFetchNameValue(papszOptions, "INDEX_NAME"); diff --git a/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp b/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp index 4c9871c95b8c..9723611f47f0 100644 --- a/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp +++ b/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp @@ -84,7 +84,8 @@ OGRElasticLayer::OGRElasticLayer(const char *pszLayerName, const char *pszIndexName, const char *pszMappingName, OGRElasticDataSource *poDS, - char **papszOptions, const char *pszESSearch) + CSLConstList papszOptions, + const char *pszESSearch) : m_poDS(poDS), m_osIndexName(pszIndexName ? pszIndexName : ""), diff --git a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp index 2bf6a34a0b65..f0e0dacd0ae5 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp @@ -695,16 +695,16 @@ OGRLayer *FGdbDataSource::GetLayer(int iLayer) /* See FGdbLayer::Create for creation options */ /************************************************************************/ -OGRLayer *FGdbDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +FGdbDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { if (!m_bUpdate || m_pGeodatabase == nullptr) return nullptr; FGdbLayer *pLayer = new FGdbLayer(); - if (!pLayer->Create(this, pszLayerName, poSRS, eType, papszOptions)) + if (!pLayer->Create(this, pszLayerName, poSrcGeomFieldDefn, papszOptions)) { delete pLayer; return nullptr; diff --git a/ogr/ogrsf_frmts/filegdb/FGdbDriverCore.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDriverCore.cpp index d5ddd00e07ef..e72ab824689d 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDriverCore.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDriverCore.cpp @@ -207,6 +207,7 @@ void OGRFileGDBDriverSetCommonMetadata(GDALDriver *poDriver) "NATIVE OGRSQL SQLITE"); poDriver->SetMetadataItem(GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES, "features media"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnIdentify = OGRFileGDBDriverIdentify; poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES"); diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index 01d47fba5181..4ff1a2bd733b 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -38,6 +38,8 @@ #include "cpl_minixml.h" // the only way right now to extract schema information #include "filegdb_gdbtoogrfieldtype.h" #include "filegdb_fielddomain.h" +#include "filegdb_coordprec_read.h" +#include "filegdb_coordprec_write.h" // See https://github.com/Esri/file-geodatabase-api/issues/46 // On certain FileGDB datasets with binary fields, iterating over a result set @@ -2070,12 +2072,17 @@ OGRErr FGdbLayer::AlterFieldDefn(int iFieldToAlter, /* XMLSpatialReference() */ /* Build up an XML representation of an OGRSpatialReference. */ /* Used in layer creation. */ -/* */ +/* Fill oCoordPrec. */ /************************************************************************/ -static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, - char **papszOptions) +static CPLXMLNode * +XMLSpatialReference(const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions, + OGRGeomCoordinatePrecision &oCoordPrec) { + const auto poSRS = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + /* We always need a SpatialReference */ CPLXMLNode *srs_xml = CPLCreateXMLNode(nullptr, CXT_Element, "SpatialReference"); @@ -2249,77 +2256,19 @@ static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, } /* Handle Origin/Scale/Tolerance */ - const char *grid[10] = {"XOrigin", "YOrigin", "XYScale", "ZOrigin", - "ZScale", "MOrigin", "MScale", "XYTolerance", - "ZTolerance", "MTolerance"}; - const char *gridvalues[10]; - - /* - Need different default parameters for geographic and projected coordinate - systems. Try and use ArcGIS 10 default values. - */ - // default tolerance is 1mm in the units of the coordinate system - double ztol = - 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("VERT_CS") : 1.0); - // default scale is 10x the tolerance - long zscale = (long)(1 / ztol * 10); - - double mtol = 0.001; - long mscale = (long)(1 / mtol * 10); - - char s_xyscale[50], s_xytol[50], s_zscale[50], s_ztol[50], s_mscale[50], - s_mtol[50]; - CPLsnprintf(s_ztol, 50, "%f", ztol); - snprintf(s_zscale, 50, "%ld", zscale); - - CPLsnprintf(s_mtol, 50, "%f", mtol); - snprintf(s_mscale, 50, "%ld", mscale); - - if (poSRS == nullptr || poSRS->IsProjected()) - { - // default tolerance is 1mm in the units of the coordinate system - double xytol = - 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("PROJCS") : 1.0); - // default scale is 10x the tolerance - long xyscale = (long)(1 / xytol * 10); - - CPLsnprintf(s_xytol, 50, "%f", xytol); - snprintf(s_xyscale, 50, "%ld", xyscale); - - // Ideally we would use the same X/Y origins as ArcGIS, but we need the - // algorithm they use. - gridvalues[0] = "-2147483647"; - gridvalues[1] = "-2147483647"; - gridvalues[2] = s_xyscale; - gridvalues[3] = "-100000"; - gridvalues[4] = s_zscale; - gridvalues[5] = "-100000"; - gridvalues[6] = s_mscale; - gridvalues[7] = s_xytol; - gridvalues[8] = s_ztol; - gridvalues[9] = s_mtol; - } - else - { - gridvalues[0] = "-400"; - gridvalues[1] = "-400"; - gridvalues[2] = "1000000000"; - gridvalues[3] = "-100000"; - gridvalues[4] = s_zscale; - gridvalues[5] = "-100000"; - gridvalues[6] = s_mscale; - gridvalues[7] = "0.000000008983153"; - gridvalues[8] = s_ztol; - gridvalues[9] = s_mtol; - } - /* Convert any layer creation options available, use defaults otherwise */ - for (int i = 0; i < 10; i++) + oCoordPrec = GDBGridSettingsFromOGR(poSrcGeomFieldDefn, papszOptions); + const auto &oGridsOptions = + oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase")->second; + for (int i = 0; i < oGridsOptions.size(); ++i) { - if (CSLFetchNameValue(papszOptions, grid[i]) != nullptr) - gridvalues[i] = CSLFetchNameValue(papszOptions, grid[i]); - - CPLCreateXMLElementAndValue(srs_xml, grid[i], gridvalues[i]); + char *pszKey = nullptr; + const char *pszValue = CPLParseNameValue(oGridsOptions[i], &pszKey); + if (pszKey && pszValue) + { + CPLCreateXMLElementAndValue(srs_xml, pszKey, pszValue); + } + CPLFree(pszKey); } /* FGDB is always High Precision */ @@ -2343,8 +2292,8 @@ static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, - const OGRSpatialReference *poSRS, - char **papszOptions) + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { /* XML node */ CPLXMLNode *xml_xml = CPLCreateXMLNode(nullptr, CXT_Element, "?xml"); @@ -2383,12 +2332,11 @@ bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, CPLAddXMLChild(defn_xml, extent_xml); /* Add the SRS */ - if (true) // TODO: conditional on existence of SRS - { - CPLXMLNode *srs_xml = XMLSpatialReference(poSRS, papszOptions); - if (srs_xml) - CPLAddXMLChild(defn_xml, srs_xml); - } + OGRGeomCoordinatePrecision oCoordPrec; + CPLXMLNode *srs_xml = + XMLSpatialReference(poSrcGeomFieldDefn, papszOptions, oCoordPrec); + if (srs_xml) + CPLAddXMLChild(defn_xml, srs_xml); /* Convert our XML tree into a string for FGDB */ char *defn_str = CPLSerializeXMLTree(xml_xml); @@ -2431,8 +2379,8 @@ bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, const char *pszLayerNameIn, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **papszOptions) + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { std::string parent_path = ""; std::wstring wtable_path, wparent_path; @@ -2442,6 +2390,9 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, bool has_z = false; bool has_m = false; + const auto eType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + #ifdef EXTENT_WORKAROUND m_bLayerJustCreated = true; #endif @@ -2522,7 +2473,7 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, if (!bFeatureDataSetExists) { bool rv = CreateFeatureDataset(pParentDataSource, feature_dataset, - poSRS, papszOptions); + poSrcGeomFieldDefn, papszOptions); if (!rv) return rv; } @@ -2657,6 +2608,7 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, /* Feature Classes have an implicit geometry column, so we'll add it at * creation time */ CPLXMLNode *srs_xml = nullptr; + OGRGeomCoordinatePrecision oCoordPrec; if (eType != wkbNone) { CPLXMLNode *shape_xml = @@ -2684,7 +2636,8 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, (has_z ? "true" : "false")); /* Add the SRS if we have one */ - srs_xml = XMLSpatialReference(poSRS, papszOptions); + srs_xml = + XMLSpatialReference(poSrcGeomFieldDefn, papszOptions, oCoordPrec); if (srs_xml) CPLAddXMLChild(geom_xml, srs_xml); } @@ -2816,6 +2769,10 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, FGdbLayer::Initialize(pParentDataSource, table, wtable_path, L"Table"); if (bRet) { + if (m_pFeatureDefn->GetGeomFieldCount() != 0) + m_pFeatureDefn->GetGeomFieldDefn(0)->SetCoordinatePrecision( + oCoordPrec); + if (bCreateShapeArea) { OGRFieldDefn oField(pszAreaFieldName, OFTReal); @@ -2986,16 +2943,15 @@ bool FGdbLayer::Initialize(FGdbDataSource *pParentDataSource, Table *pTable, /* ParseGeometryDef() */ /************************************************************************/ -bool FGdbLayer::ParseGeometryDef(CPLXMLNode *psRoot) +bool FGdbLayer::ParseGeometryDef(const CPLXMLNode *psRoot) { - CPLXMLNode *psGeometryDefItem; - string geometryType; bool hasZ = false, hasM = false; string wkt, wkid, latestwkid; - for (psGeometryDefItem = psRoot->psChild; psGeometryDefItem != nullptr; - psGeometryDefItem = psGeometryDefItem->psNext) + OGRGeomCoordinatePrecision oCoordPrec; + for (const CPLXMLNode *psGeometryDefItem = psRoot->psChild; + psGeometryDefItem; psGeometryDefItem = psGeometryDefItem->psNext) { // loop through all "GeometryDef" elements // @@ -3013,6 +2969,7 @@ bool FGdbLayer::ParseGeometryDef(CPLXMLNode *psRoot) psGeometryDefItem, &wkt, &wkid, &latestwkid); // we don't check for success because it // may not be there + oCoordPrec = GDBGridSettingsToOGR(psGeometryDefItem); } else if (EQUAL(psGeometryDefItem->pszValue, "HasM")) { @@ -3035,6 +2992,9 @@ bool FGdbLayer::ParseGeometryDef(CPLXMLNode *psRoot) m_pFeatureDefn->SetGeomType(ogrGeoType); + if (m_pFeatureDefn->GetGeomFieldCount() != 0) + m_pFeatureDefn->GetGeomFieldDefn(0)->SetCoordinatePrecision(oCoordPrec); + if (wkbFlatten(ogrGeoType) == wkbMultiLineString || wkbFlatten(ogrGeoType) == wkbMultiPoint) m_forceMulti = true; @@ -3087,11 +3047,6 @@ bool FGdbLayer::ParseGeometryDef(CPLXMLNode *psRoot) "Failed Mapping ESRI Spatial Reference"); } } - else - { - // report error, but be passive about it - CPLError(CE_Warning, CPLE_AppDefined, "Empty Spatial Reference"); - } return true; } @@ -3100,7 +3055,7 @@ bool FGdbLayer::ParseGeometryDef(CPLXMLNode *psRoot) /* ParseSpatialReference() */ /************************************************************************/ -bool FGdbLayer::ParseSpatialReference(CPLXMLNode *psSpatialRefNode, +bool FGdbLayer::ParseSpatialReference(const CPLXMLNode *psSpatialRefNode, string *pOutWkt, string *pOutWKID, string *pOutLatestWKID) { @@ -3108,11 +3063,9 @@ bool FGdbLayer::ParseSpatialReference(CPLXMLNode *psSpatialRefNode, *pOutWKID = ""; *pOutLatestWKID = ""; - CPLXMLNode *psSRItemNode; - /* Loop through all the SRS elements we want to store */ - for (psSRItemNode = psSpatialRefNode->psChild; psSRItemNode != nullptr; - psSRItemNode = psSRItemNode->psNext) + for (const CPLXMLNode *psSRItemNode = psSpatialRefNode->psChild; + psSRItemNode; psSRItemNode = psSRItemNode->psNext) { /* The WKID maps (mostly) to an EPSG code */ if (psSRItemNode->eType == CXT_Element && diff --git a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h index d5661beb2fa2..d8544e626730 100644 --- a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h +++ b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h @@ -165,12 +165,12 @@ class FGdbLayer final : public FGdbBaseLayer const std::wstring &wstrTablePath, const std::wstring &wstrType); bool Create(FGdbDataSource *pParentDataSource, const char *pszLayerName, - const OGRSpatialReference *poSRS, OGRwkbGeometryType eType, - char **papszOptions); + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions); static bool CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, - const OGRSpatialReference *poSRS, - char **papszOptions); + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions); // virtual const char *GetName(); virtual const char *GetFIDColumn() override @@ -252,9 +252,9 @@ class FGdbLayer final : public FGdbBaseLayer protected: bool GDBToOGRFields(CPLXMLNode *psFields); - bool ParseGeometryDef(CPLXMLNode *psGeometryDef); + bool ParseGeometryDef(const CPLXMLNode *psGeometryDef); - static bool ParseSpatialReference(CPLXMLNode *psSpatialRefNode, + static bool ParseSpatialReference(const CPLXMLNode *psSpatialRefNode, std::string *pOutWkt, std::string *pOutWKID, std::string *pOutLatestWKID); @@ -347,10 +347,9 @@ class FGdbDataSource final : public OGRDataSource OGRLayer *GetLayer(int) override; - virtual OGRLayer *ICreateLayer(const char *, - const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRErr DeleteLayer(int) override; diff --git a/ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h b/ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h index 6621cdcc394b..90760edff5c3 100644 --- a/ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h +++ b/ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h @@ -168,7 +168,7 @@ class OGRFlatGeobufLayer final : public OGRLayer, static OGRFlatGeobufLayer * Create(GDALDataset *poDS, const char *pszLayerName, const char *pszFilename, const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType, - bool bCreateSpatialIndexAtClose, char **papszOptions); + bool bCreateSpatialIndexAtClose, CSLConstList papszOptions); virtual OGRFeature *GetFeature(GIntBig nFeatureId) override; virtual OGRFeature *GetNextFeature() override; @@ -271,11 +271,10 @@ class OGRFlatGeobufDataset final : public GDALDataset char **papszOptions); virtual OGRLayer *GetLayer(int) override; int TestCapability(const char *pszCap) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual int GetLayerCount() override { diff --git a/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp b/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp index 75a9afa40e2d..0598294a1f5f 100644 --- a/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp +++ b/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp @@ -405,9 +405,10 @@ static CPLString LaunderLayerName(const char *pszLayerName) return osRet; } -OGRLayer *OGRFlatGeobufDataset::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { // Verify we are in update mode. if (!m_bCreate) @@ -428,6 +429,10 @@ OGRLayer *OGRFlatGeobufDataset::ICreateLayer( return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + // Verify that the datasource is a directory. VSIStatBufL sStatBuf; diff --git a/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobuflayer.cpp b/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobuflayer.cpp index f4ce1873ca65..70676c757564 100644 --- a/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobuflayer.cpp +++ b/ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobuflayer.cpp @@ -2486,7 +2486,7 @@ VSILFILE *OGRFlatGeobufLayer::CreateOutputFile(const CPLString &osFilename, OGRFlatGeobufLayer *OGRFlatGeobufLayer::Create( GDALDataset *poDS, const char *pszLayerName, const char *pszFilename, const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType, - bool bCreateSpatialIndexAtClose, char **papszOptions) + bool bCreateSpatialIndexAtClose, CSLConstList papszOptions) { std::string osTempFile = GetTempFilePath(pszFilename, papszOptions); VSILFILE *poFpWrite = diff --git a/ogr/ogrsf_frmts/generic/ogremulatedtransaction.cpp b/ogr/ogrsf_frmts/generic/ogremulatedtransaction.cpp index 8480da949db1..d4ad959da86e 100644 --- a/ogr/ogrsf_frmts/generic/ogremulatedtransaction.cpp +++ b/ogr/ogrsf_frmts/generic/ogremulatedtransaction.cpp @@ -117,11 +117,10 @@ class OGRDataSourceWithTransaction final : public OGRDataSource virtual int TestCapability(const char *) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + virtual OGRLayer *CopyLayer(OGRLayer *poSrcLayer, const char *pszNewName, char **papszOptions = nullptr) override; @@ -325,13 +324,13 @@ int OGRDataSourceWithTransaction::TestCapability(const char *pszCap) } OGRLayer *OGRDataSourceWithTransaction::ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) + const char *pszName, const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!m_poBaseDataSource) return nullptr; - return WrapLayer(m_poBaseDataSource->CreateLayer(pszName, poSpatialRef, - eGType, papszOptions)); + return WrapLayer(m_poBaseDataSource->CreateLayer(pszName, poGeomFieldDefn, + papszOptions)); } OGRLayer *OGRDataSourceWithTransaction::CopyLayer(OGRLayer *poSrcLayer, diff --git a/ogr/ogrsf_frmts/generic/ogrlayer.cpp b/ogr/ogrsf_frmts/generic/ogrlayer.cpp index aa44e80fde9c..945c45f8b753 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayer.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayer.cpp @@ -655,28 +655,80 @@ OGRFeatureH OGR_L_GetNextFeature(OGRLayerH hLayer) void OGRLayer::ConvertGeomsIfNecessary(OGRFeature *poFeature) { - const bool bSupportsCurve = CPL_TO_BOOL(TestCapability(OLCCurveGeometries)); - const bool bSupportsM = CPL_TO_BOOL(TestCapability(OLCMeasuredGeometries)); - if (!bSupportsCurve || !bSupportsM) + if (!m_poPrivate->m_bConvertGeomsIfNecessaryAlreadyCalled) { - int nGeomFieldCount = GetLayerDefn()->GetGeomFieldCount(); - for (int i = 0; i < nGeomFieldCount; i++) + // One time initialization + m_poPrivate->m_bConvertGeomsIfNecessaryAlreadyCalled = true; + m_poPrivate->m_bSupportsCurve = + CPL_TO_BOOL(TestCapability(OLCCurveGeometries)); + m_poPrivate->m_bSupportsM = + CPL_TO_BOOL(TestCapability(OLCMeasuredGeometries)); + if (CPLTestBool( + CPLGetConfigOption("OGR_APPLY_GEOM_SET_PRECISION", "FALSE"))) { - OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i); - if (poGeom != nullptr && - (!bSupportsM && OGR_GT_HasM(poGeom->getGeometryType()))) + const auto poFeatureDefn = GetLayerDefn(); + const int nGeomFieldCount = poFeatureDefn->GetGeomFieldCount(); + for (int i = 0; i < nGeomFieldCount; i++) { - poGeom->setMeasured(FALSE); + const double dfXYResolution = poFeatureDefn->GetGeomFieldDefn(i) + ->GetCoordinatePrecision() + .dfXYResolution; + if (dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN && + OGRGeometryFactory::haveGEOS()) + { + m_poPrivate->m_bApplyGeomSetPrecision = true; + break; + } } - if (poGeom != nullptr && - (!bSupportsCurve && - OGR_GT_IsNonLinear(poGeom->getGeometryType()))) + } + } + + if (!m_poPrivate->m_bSupportsCurve || !m_poPrivate->m_bSupportsM || + m_poPrivate->m_bApplyGeomSetPrecision) + { + const auto poFeatureDefn = GetLayerDefn(); + const int nGeomFieldCount = poFeatureDefn->GetGeomFieldCount(); + for (int i = 0; i < nGeomFieldCount; i++) + { + OGRGeometry *poGeom = poFeature->GetGeomFieldRef(i); + if (poGeom) { - OGRwkbGeometryType eTargetType = - OGR_GT_GetLinear(poGeom->getGeometryType()); - poFeature->SetGeomFieldDirectly( - i, OGRGeometryFactory::forceTo(poFeature->StealGeometry(i), - eTargetType)); + if (!m_poPrivate->m_bSupportsM && + OGR_GT_HasM(poGeom->getGeometryType())) + { + poGeom->setMeasured(FALSE); + } + + if (!m_poPrivate->m_bSupportsCurve && + OGR_GT_IsNonLinear(poGeom->getGeometryType())) + { + OGRwkbGeometryType eTargetType = + OGR_GT_GetLinear(poGeom->getGeometryType()); + poGeom = OGRGeometryFactory::forceTo( + poFeature->StealGeometry(i), eTargetType); + poFeature->SetGeomFieldDirectly(i, poGeom); + } + + if (m_poPrivate->m_bApplyGeomSetPrecision) + { + const double dfXYResolution = + poFeatureDefn->GetGeomFieldDefn(i) + ->GetCoordinatePrecision() + .dfXYResolution; + if (dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN && + !poGeom->hasCurveGeometry()) + { + auto poNewGeom = poGeom->SetPrecision(dfXYResolution, + /* nFlags = */ 0); + if (poNewGeom) + { + poFeature->SetGeomFieldDirectly(i, poNewGeom); + poGeom = poNewGeom; + } + } + } + + CPL_IGNORE_RET_VAL(poGeom); } } } diff --git a/ogr/ogrsf_frmts/generic/ogrlayer_private.h b/ogr/ogrsf_frmts/generic/ogrlayer_private.h index 21b3475eb3c1..e86fa8b0fa1e 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayer_private.h +++ b/ogr/ogrsf_frmts/generic/ogrlayer_private.h @@ -44,6 +44,18 @@ struct OGRLayer::Private // We should probably have CreateFieldFromArrowSchema() and // WriteArrowBatch() explicitly returning and accepting that mapping. std::map m_oMapArrowFieldNameToOGRFieldName{}; + + //! Whether OGRLayer::ConvertGeomsIfNecessary() has already been called + bool m_bConvertGeomsIfNecessaryAlreadyCalled = false; + + //! Value of TestCapability(OLCCurveGeometries). Only valid after ConvertGeomsIfNecessary() has been called. + bool m_bSupportsCurve = false; + + //! Value of TestCapability(OLCMeasuredGeometries). Only valid after ConvertGeomsIfNecessary() has been called. + bool m_bSupportsM = false; + + //! Whether OGRGeometry::SetPrecision() should be applied. Only valid after ConvertGeomsIfNecessary() has been called. + bool m_bApplyGeomSetPrecision = false; }; //! @endcond diff --git a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp index 55865c74efd1..67a4848d0d14 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp @@ -548,37 +548,51 @@ int OGRLayer::GetArrowSchema(struct ArrowArrayStream *, if (!oMetadata.empty()) { - size_t nLen = sizeof(int32_t); + uint64_t nLen64 = sizeof(int32_t); for (const auto &oPair : oMetadata) { - nLen += sizeof(int32_t) + oPair.first.size() + sizeof(int32_t) + - oPair.second.size(); + nLen64 += sizeof(int32_t); + nLen64 += oPair.first.size(); + nLen64 += sizeof(int32_t); + nLen64 += oPair.second.size(); } - char *pszMetadata = static_cast(CPLMalloc(nLen)); - psChild->metadata = pszMetadata; - size_t offsetMD = 0; - *reinterpret_cast(pszMetadata + offsetMD) = - static_cast(oMetadata.size()); - offsetMD += sizeof(int32_t); - for (const auto &oPair : oMetadata) + if (nLen64 < + static_cast(std::numeric_limits::max())) { - *reinterpret_cast(pszMetadata + offsetMD) = - static_cast(oPair.first.size()); + const size_t nLen = static_cast(nLen64); + char *pszMetadata = static_cast(CPLMalloc(nLen)); + psChild->metadata = pszMetadata; + size_t offsetMD = 0; + int32_t nSize = static_cast(oMetadata.size()); + memcpy(pszMetadata + offsetMD, &nSize, sizeof(nSize)); offsetMD += sizeof(int32_t); - memcpy(pszMetadata + offsetMD, oPair.first.data(), - oPair.first.size()); - offsetMD += oPair.first.size(); + for (const auto &oPair : oMetadata) + { + nSize = static_cast(oPair.first.size()); + memcpy(pszMetadata + offsetMD, &nSize, sizeof(nSize)); + offsetMD += sizeof(int32_t); + memcpy(pszMetadata + offsetMD, oPair.first.data(), + oPair.first.size()); + offsetMD += oPair.first.size(); + + nSize = static_cast(oPair.second.size()); + memcpy(pszMetadata + offsetMD, &nSize, sizeof(nSize)); + offsetMD += sizeof(int32_t); + memcpy(pszMetadata + offsetMD, oPair.second.data(), + oPair.second.size()); + offsetMD += oPair.second.size(); + } - *reinterpret_cast(pszMetadata + offsetMD) = - static_cast(oPair.second.size()); - offsetMD += sizeof(int32_t); - memcpy(pszMetadata + offsetMD, oPair.second.data(), - oPair.second.size()); - offsetMD += oPair.second.size(); + CPLAssert(offsetMD == nLen); + CPL_IGNORE_RET_VAL(offsetMD); + } + else + { + // Extremely unlikely ! + CPLError(CE_Warning, CPLE_AppDefined, + "Cannot write ArrowSchema::metadata due to " + "too large content"); } - - CPLAssert(offsetMD == nLen); - CPL_IGNORE_RET_VAL(offsetMD); } } diff --git a/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.cpp b/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.cpp index 020b1d44e707..4aac929f1133 100644 --- a/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.cpp +++ b/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.cpp @@ -127,13 +127,14 @@ int OGRMutexedDataSource::TestCapability(const char *pszCap) return m_poBaseDataSource->TestCapability(pszCap); } -OGRLayer *OGRMutexedDataSource::ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRMutexedDataSource::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { CPLMutexHolderOptionalLockD(m_hGlobalMutex); return WrapLayerIfNecessary(m_poBaseDataSource->CreateLayer( - pszName, poSpatialRef, eGType, papszOptions)); + pszName, poGeomFieldDefn, papszOptions)); } OGRLayer *OGRMutexedDataSource::CopyLayer(OGRLayer *poSrcLayer, diff --git a/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.h b/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.h index 92b843541150..e3665dfa5ee5 100644 --- a/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.h +++ b/ogr/ogrsf_frmts/generic/ogrmutexeddatasource.h @@ -81,11 +81,9 @@ class CPL_DLL OGRMutexedDataSource : public OGRDataSource virtual int TestCapability(const char *) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual OGRLayer *CopyLayer(OGRLayer *poSrcLayer, const char *pszNewName, char **papszOptions = nullptr) override; diff --git a/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.cpp b/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.cpp index 2b44ec488c49..a8d8cfbfd662 100644 --- a/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.cpp +++ b/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.cpp @@ -292,10 +292,10 @@ int OGRGeoconceptDataSource::Create(const char *pszName, char **papszOptions) /* FEATURETYPE : TYPE.SUBTYPE */ /************************************************************************/ -OGRLayer *OGRGeoconceptDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRS /* = NULL */, - OGRwkbGeometryType eType /* = wkbUnknown */, - char **papszOptions /* = NULL */) +OGRLayer * +OGRGeoconceptDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (_hGXT == nullptr) @@ -305,7 +305,8 @@ OGRLayer *OGRGeoconceptDataSource::ICreateLayer( return nullptr; } - if (poSRS == nullptr && !_bUpdate) + if ((!poGeomFieldDefn || poGeomFieldDefn->GetSpatialRef() == nullptr) && + !_bUpdate) { CPLError(CE_Failure, CPLE_NotSupported, "SRS is mandatory of creating a Geoconcept Layer."); @@ -349,6 +350,7 @@ OGRLayer *OGRGeoconceptDataSource::ICreateLayer( GCTypeKind gcioFeaType; GCDim gcioDim = v2D_GCIO; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; if (eType == wkbUnknown) gcioFeaType = vUnknownItemType_GCIO; else if (eType == wkbPoint) @@ -516,6 +518,8 @@ OGRLayer *OGRGeoconceptDataSource::ICreateLayer( /* -------------------------------------------------------------------- */ /* Assign the coordinate system (if provided) */ /* -------------------------------------------------------------------- */ + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; if (poSRS != nullptr) { auto poSRSClone = poSRS->Clone(); diff --git a/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.h b/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.h index 67a10f4a3672..8902abd1cc60 100644 --- a/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.h +++ b/ogr/ogrsf_frmts/geoconcept/ogrgeoconceptdatasource.h @@ -72,9 +72,8 @@ class OGRGeoconceptDataSource : public OGRDataSource int TestCapability(const char *pszCap) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; private: int LoadFile(const char *); diff --git a/ogr/ogrsf_frmts/geojson/ogr_geojson.h b/ogr/ogrsf_frmts/geojson/ogr_geojson.h index 910f1e500abc..2b0dab2b25df 100644 --- a/ogr/ogrsf_frmts/geojson/ogr_geojson.h +++ b/ogr/ogrsf_frmts/geojson/ogr_geojson.h @@ -116,6 +116,11 @@ class OGRGeoJSONLayer final : public OGRMemLayer nTotalFeatureCount_ = -1; } + void SetWriteOptions(const OGRGeoJSONWriteOptions &options) + { + oWriteOptions_ = options; + } + private: OGRGeoJSONDataSource *poDS_; OGRGeoJSONReader *poReader_; @@ -125,6 +130,9 @@ class OGRGeoJSONLayer final : public OGRMemLayer GIntBig nTotalFeatureCount_; GIntBig nFeatureReadSinceReset_ = 0; + //! Write options used by ICreateFeature() in append scenarios + OGRGeoJSONWriteOptions oWriteOptions_; + bool IngestAll(); void TerminateAppendSession(); @@ -139,7 +147,7 @@ class OGRGeoJSONWriteLayer final : public OGRLayer { public: OGRGeoJSONWriteLayer(const char *pszName, OGRwkbGeometryType eGType, - char **papszOptions, bool bWriteFC_BBOXIn, + CSLConstList papszOptions, bool bWriteFC_BBOXIn, OGRCoordinateTransformation *poCT, OGRGeoJSONDataSource *poDS); ~OGRGeoJSONWriteLayer(); @@ -190,7 +198,6 @@ class OGRGeoJSONWriteLayer final : public OGRLayer bool bWriteFC_BBOX; OGREnvelope3D sEnvelopeLayer; - int nCoordPrecision_; int nSignificantFigures_; bool bRFC7946_; @@ -224,9 +231,8 @@ class OGRGeoJSONDataSource final : public OGRDataSource int GetLayerCount() override; OGRLayer *GetLayer(int nLayer) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSRS = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *pszCap) override; void AddLayer(OGRGeoJSONLayer *poLayer); diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp index 4770c7117ce0..dda370005a46 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp @@ -30,6 +30,7 @@ #include "cpl_port.h" #include "ogr_geojson.h" +#include #include #include #include @@ -254,10 +255,10 @@ OGRLayer *OGRGeoJSONDataSource::GetLayer(int nLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, - char **papszOptions) +OGRLayer * +OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { if (nullptr == fpOut_) { @@ -274,6 +275,11 @@ OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, return nullptr; } + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + const char *pszForeignMembersCollection = CSLFetchNameValue(papszOptions, "FOREIGN_MEMBERS_COLLECTION"); if (pszForeignMembersCollection) @@ -407,6 +413,12 @@ OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, continue; } + if (strcmp(it.key, "xy_coordinate_resolution") == 0 || + strcmp(it.key, "z_coordinate_resolution") == 0) + { + continue; + } + json_object *poKey = json_object_new_string(it.key); VSIFPrintfL(fpOut_, "%s: ", json_object_to_json_string(poKey)); json_object_put(poKey); @@ -528,6 +540,60 @@ OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, CPLFree(pszOGCURN); } + CPLStringList aosOptions(papszOptions); + + double dfXYResolution = OGRGeomCoordinatePrecision::UNKNOWN; + double dfZResolution = OGRGeomCoordinatePrecision::UNKNOWN; + + if (const char *pszCoordPrecision = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION")) + { + dfXYResolution = std::pow(10.0, -CPLAtof(pszCoordPrecision)); + dfZResolution = dfXYResolution; + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n", + dfXYResolution); + if (poSRS && poSRS->GetAxesCount() == 3) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n", + dfZResolution); + } + } + else if (poSrcGeomFieldDefn) + { + const auto &oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + OGRSpatialReference oSRSWGS84; + oSRSWGS84.SetWellKnownGeogCS("WGS84"); + const auto oCoordPrecWGS84 = + oCoordPrec.ConvertToOtherSRS(poSRS, &oSRSWGS84); + + if (oCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfXYResolution = poSRS && bRFC7946 ? oCoordPrecWGS84.dfXYResolution + : oCoordPrec.dfXYResolution; + + aosOptions.SetNameValue( + "XY_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfXYResolution))); + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n", + dfXYResolution); + } + if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfZResolution = poSRS && bRFC7946 ? oCoordPrecWGS84.dfZResolution + : oCoordPrec.dfZResolution; + + aosOptions.SetNameValue( + "Z_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfZResolution))); + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n", + dfZResolution); + } + } + if (bFpOutputIsSeekable_ && bWriteFC_BBOX) { nBBOXInsertLocation_ = static_cast(VSIFTellL(fpOut_)); @@ -539,7 +605,27 @@ OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, VSIFPrintfL(fpOut_, "\"features\": [\n"); OGRGeoJSONWriteLayer *poLayer = new OGRGeoJSONWriteLayer( - pszNameIn, eGType, papszOptions, bWriteFC_BBOX, poCT, this); + pszNameIn, eGType, aosOptions.List(), bWriteFC_BBOX, poCT, this); + + if (eGType != wkbNone && + dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfXYResolution = dfXYResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + if (eGType != wkbNone && + dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfZResolution = dfZResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } /* -------------------------------------------------------------------- */ /* Add layer to data source layer list. */ diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp index 5e9e2acbd60a..efd662eeabbd 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp @@ -787,6 +787,7 @@ void RegisterOGRGeoJSON() poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE"); poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnOpen = OGRGeoJSONDriverOpen; poDriver->pfnIdentify = OGRGeoJSONDriverIdentify; diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp index 905d38b00617..df1512136b00 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp @@ -348,7 +348,7 @@ OGRErr OGRGeoJSONLayer::ICreateFeature(OGRFeature *poFeature) VSIFPrintfL(fp, ",\n"); } json_object *poObj = - OGRGeoJSONWriteFeature(poFeature, OGRGeoJSONWriteOptions()); + OGRGeoJSONWriteFeature(poFeature, oWriteOptions_); VSIFPrintfL(fp, "%s", json_object_to_json_string(poObj)); json_object_put(poObj); diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp index fdf58480c32d..bf437f4544e0 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp @@ -368,6 +368,53 @@ void OGRGeoJSONReaderStreamingParser::TooComplex() "for larger features, or 0 to remove any size limit."); } +/************************************************************************/ +/* SetCoordinatePrecision() */ +/************************************************************************/ + +static void SetCoordinatePrecision(json_object *poRootObj, + OGRGeoJSONLayer *poLayer) +{ + if (poLayer->GetLayerDefn()->GetGeomType() != wkbNone) + { + OGRGeoJSONWriteOptions options; + + json_object *poXYRes = + CPL_json_object_object_get(poRootObj, "xy_coordinate_resolution"); + if (poXYRes && (json_object_get_type(poXYRes) == json_type_double || + json_object_get_type(poXYRes) == json_type_int)) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfXYResolution = json_object_get_double(poXYRes); + whileUnsealing(poGeomFieldDefn)->SetCoordinatePrecision(oCoordPrec); + + options.nXYCoordPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + oCoordPrec.dfXYResolution); + } + + json_object *poZRes = + CPL_json_object_object_get(poRootObj, "z_coordinate_resolution"); + if (poZRes && (json_object_get_type(poZRes) == json_type_double || + json_object_get_type(poZRes) == json_type_int)) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfZResolution = json_object_get_double(poZRes); + whileUnsealing(poGeomFieldDefn)->SetCoordinatePrecision(oCoordPrec); + + options.nZCoordPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + oCoordPrec.dfZResolution); + } + + poLayer->SetWriteOptions(options); + } +} + /************************************************************************/ /* FirstPassReadLayer() */ /************************************************************************/ @@ -556,6 +603,8 @@ bool OGRGeoJSONReader::FirstPassReadLayer(OGRGeoJSONDataSource *poDS, if (poSRS) poSRS->Release(); + SetCoordinatePrecision(poRootObj, poLayer); + if (bStoreNativeData_) { CPLString osNativeData("NATIVE_DATA="); @@ -934,6 +983,8 @@ void OGRGeoJSONReader::ReadLayer(OGRGeoJSONDataSource *poDS, poLayer->SetMetadataItem("DESCRIPTION", json_object_get_string(poDescription)); } + + SetCoordinatePrecision(poObj, poLayer); } /* -------------------------------------------------------------------- */ @@ -2952,7 +3003,6 @@ OGRPolygon *OGRGeoJSONReadPolygon(json_object *poObj, bool bRaw) if (poObjPoints == nullptr) { poPolygon = new OGRPolygon(); - poPolygon->addRingDirectly(new OGRLinearRing()); } else { @@ -2968,11 +3018,7 @@ OGRPolygon *OGRGeoJSONReadPolygon(json_object *poObj, bool bRaw) i < nRings && nullptr != poPolygon; ++i) { poObjPoints = json_object_array_get_idx(poObjRings, i); - if (poObjPoints == nullptr) - { - poPolygon->addRingDirectly(new OGRLinearRing()); - } - else + if (poObjPoints != nullptr) { OGRLinearRing *poRing = OGRGeoJSONReadLinearRing(poObjPoints); @@ -2983,6 +3029,10 @@ OGRPolygon *OGRGeoJSONReadPolygon(json_object *poObj, bool bRaw) } } } + else + { + poPolygon = new OGRPolygon(); + } } return poPolygon; diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp index 40d9e6cf678c..7ae861e30957 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp @@ -65,9 +65,8 @@ class OGRGeoJSONSeqDataSource final : public GDALDataset } OGRLayer *GetLayer(int) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSRS = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *pszCap) override; bool Open(GDALOpenInfo *poOpenInfo, GeoJSONSourceType nSrcType); @@ -175,12 +174,15 @@ OGRLayer *OGRGeoJSONSeqDataSource::GetLayer(int nIndex) /************************************************************************/ OGRLayer *OGRGeoJSONSeqDataSource::ICreateLayer( - const char *pszNameIn, const OGRSpatialReference *poSRS, - OGRwkbGeometryType /*eGType*/, char **papszOptions) + const char *pszNameIn, const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { if (!TestCapability(ODsCCreateLayer)) return nullptr; + const auto poSRS = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + std::unique_ptr poCT; if (poSRS == nullptr) { @@ -216,9 +218,71 @@ OGRLayer *OGRGeoJSONSeqDataSource::ICreateLayer( m_bIsRSSeparated = CPLTestBool(pszRS); } + CPLStringList aosOptions(papszOptions); + + double dfXYResolution = OGRGeomCoordinatePrecision::UNKNOWN; + double dfZResolution = OGRGeomCoordinatePrecision::UNKNOWN; + if (const char *pszCoordPrecision = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION")) + { + dfXYResolution = std::pow(10.0, -CPLAtof(pszCoordPrecision)); + dfZResolution = dfXYResolution; + } + else if (poSrcGeomFieldDefn) + { + const auto &oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + OGRSpatialReference oSRSWGS84; + oSRSWGS84.SetWellKnownGeogCS("WGS84"); + const auto oCoordPrecWGS84 = + oCoordPrec.ConvertToOtherSRS(poSRS, &oSRSWGS84); + + if (oCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfXYResolution = oCoordPrecWGS84.dfXYResolution; + + aosOptions.SetNameValue( + "XY_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfXYResolution))); + } + if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfZResolution = oCoordPrecWGS84.dfZResolution; + + aosOptions.SetNameValue( + "Z_COORD_PRECISION", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfZResolution))); + } + } + m_apoLayers.emplace_back(std::make_unique( - this, pszNameIn, papszOptions, std::move(poCT))); - return m_apoLayers.back().get(); + this, pszNameIn, aosOptions.List(), std::move(poCT))); + + auto poLayer = m_apoLayers.back().get(); + if (poLayer->GetGeomType() != wkbNone && + dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfXYResolution = dfXYResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + if (poLayer->GetGeomType() != wkbNone && + dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfZResolution = dfZResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + return poLayer; } /************************************************************************/ @@ -278,8 +342,22 @@ OGRGeoJSONSeqLayer::OGRGeoJSONSeqLayer( m_oWriteOptions.SetRFC7946Settings(); m_oWriteOptions.SetIDOptions(papszOptions); - m_oWriteOptions.nCoordPrecision = - atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "7")); + + const char *pszCoordPrecision = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION"); + if (pszCoordPrecision) + { + m_oWriteOptions.nXYCoordPrecision = atoi(pszCoordPrecision); + m_oWriteOptions.nZCoordPrecision = atoi(pszCoordPrecision); + } + else + { + m_oWriteOptions.nXYCoordPrecision = + atoi(CSLFetchNameValueDef(papszOptions, "XY_COORD_PRECISION", "7")); + m_oWriteOptions.nZCoordPrecision = + atoi(CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION", "3")); + } + m_oWriteOptions.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); m_oWriteOptions.bAllowNonFiniteValues = CPLTestBool( @@ -973,6 +1051,7 @@ void RegisterOGRGeoJSONSeq() "Integer64List RealList StringList"); poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean"); poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnOpen = OGRGeoJSONSeqDriverOpen; poDriver->pfnIdentify = OGRGeoJSONSeqDriverIdentify; diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp index bfa50270f8d1..f866bcd40167 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp @@ -40,7 +40,7 @@ OGRGeoJSONWriteLayer::OGRGeoJSONWriteLayer(const char *pszName, OGRwkbGeometryType eGType, - char **papszOptions, + CSLConstList papszOptions, bool bWriteFC_BBOXIn, OGRCoordinateTransformation *poCT, OGRGeoJSONDataSource *poDS) @@ -48,8 +48,6 @@ OGRGeoJSONWriteLayer::OGRGeoJSONWriteLayer(const char *pszName, bWriteBBOX(CPLTestBool( CSLFetchNameValueDef(papszOptions, "WRITE_BBOX", "FALSE"))), bBBOX3D(false), bWriteFC_BBOX(bWriteFC_BBOXIn), - nCoordPrecision_(atoi( - CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1"))), nSignificantFigures_(atoi( CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1"))), bRFC7946_( @@ -71,10 +69,21 @@ OGRGeoJSONWriteLayer::OGRGeoJSONWriteLayer(const char *pszName, poFeatureDefn_->Reference(); poFeatureDefn_->SetGeomType(eGType); SetDescription(poFeatureDefn_->GetName()); - if (bRFC7946_ && nCoordPrecision_ < 0) - nCoordPrecision_ = 7; + const char *pszCoordPrecision = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION"); + if (pszCoordPrecision) + { + oWriteOptions_.nXYCoordPrecision = atoi(pszCoordPrecision); + oWriteOptions_.nZCoordPrecision = atoi(pszCoordPrecision); + } + else + { + oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef( + papszOptions, "XY_COORD_PRECISION", bRFC7946_ ? "7" : "-1")); + oWriteOptions_.nZCoordPrecision = atoi(CSLFetchNameValueDef( + papszOptions, "Z_COORD_PRECISION", bRFC7946_ ? "3" : "-1")); + } oWriteOptions_.bWriteBBOX = bWriteBBOX; - oWriteOptions_.nCoordPrecision = nCoordPrecision_; oWriteOptions_.nSignificantFigures = nSignificantFigures_; if (bRFC7946_) { @@ -121,9 +130,9 @@ void OGRGeoJSONWriteLayer::FinishWriting() { CPLString osBBOX = "[ "; char szFormat[32]; - if (nCoordPrecision_ >= 0) + if (oWriteOptions_.nXYCoordPrecision >= 0) snprintf(szFormat, sizeof(szFormat), "%%.%df", - nCoordPrecision_); + oWriteOptions_.nXYCoordPrecision); else snprintf(szFormat, sizeof(szFormat), "%s", "%.15g"); @@ -234,41 +243,32 @@ OGRErr OGRGeoJSONWriteLayer::ICreateFeature(OGRFeature *poFeature) // Special processing to detect and repair invalid geometries due to // coordinate precision. + // Normally drivers shouldn't do that as similar code is triggered by + // setting the OGR_APPLY_GEOM_SET_PRECISION=YES configuration option by + // the generic OGRLayer::CreateFeature() code path. But this code predates + // its introduction and RFC99, and can be useful in RFC7946 mode due to + // coordinate reprojection. OGRGeometry *poOrigGeom = poFeature->GetGeometryRef(); - if (OGRGeometryFactory::haveGEOS() && nCoordPrecision_ >= 0 && poOrigGeom && + if (OGRGeometryFactory::haveGEOS() && + oWriteOptions_.nXYCoordPrecision >= 0 && poOrigGeom && wkbFlatten(poOrigGeom->getGeometryType()) != wkbPoint && IsValid(poOrigGeom)) { - struct CoordinateRoundingVisitor : public OGRDefaultGeometryVisitor - { - const double dfFactor_; - const double dfInvFactor_; - - explicit CoordinateRoundingVisitor(int nCoordPrecision) - : dfFactor_(std::pow(10.0, double(nCoordPrecision))), - dfInvFactor_(std::pow(10.0, double(-nCoordPrecision))) - { - } - - using OGRDefaultGeometryVisitor::visit; - void visit(OGRPoint *p) override - { - p->setX(std::round(p->getX() * dfFactor_) * dfInvFactor_); - p->setY(std::round(p->getY() * dfFactor_) * dfInvFactor_); - } - }; - - CoordinateRoundingVisitor oVisitor(nCoordPrecision_); + const double dfXYResolution = + std::pow(10.0, double(-oWriteOptions_.nXYCoordPrecision)); auto poNewGeom = poFeature == poFeatureToWrite ? poOrigGeom->clone() : poFeatureToWrite->GetGeometryRef(); bool bDeleteNewGeom = (poFeature == poFeatureToWrite); - poNewGeom->accept(&oVisitor); + OGRGeomCoordinatePrecision sPrecision; + sPrecision.dfXYResolution = dfXYResolution; + poNewGeom->roundCoordinates(sPrecision); if (!IsValid(poNewGeom)) { - CPLDebug("GeoJSON", "Running MakeValid() to correct an invalid " + CPLDebug("GeoJSON", "Running SetPrecision() to correct an invalid " "geometry due to reduced precision output"); - auto poValidGeom = poNewGeom->MakeValid(); + auto poValidGeom = + poOrigGeom->SetPrecision(dfXYResolution, /* nFlags = */ 0); if (poValidGeom) { if (poFeature == poFeatureToWrite) @@ -278,25 +278,6 @@ OGRErr OGRGeoJSONWriteLayer::ICreateFeature(OGRFeature *poFeature) poFeatureToWrite->SetFID(poFeature->GetFID()); } - // It may happen that after MakeValid(), and rounding again, - // we end up with an invalid result. Run MakeValid() again... - poValidGeom->accept(&oVisitor); - if (!IsValid(poValidGeom)) - { - auto poValidGeom2 = poValidGeom->MakeValid(); - if (poValidGeom2) - { - delete poValidGeom; - poValidGeom = poValidGeom2; - if (!IsValid(poValidGeom)) - { - // hopefully should not happen - CPLDebug("GeoJSON", - "... still not valid! Giving up"); - } - } - } - poFeatureToWrite->SetGeometryDirectly(poValidGeom); } } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.cpp index 274236e45429..b1d1fdedf13d 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.cpp @@ -59,8 +59,10 @@ json_object_new_float_with_significant_figures(float fVal, void OGRGeoJSONWriteOptions::SetRFC7946Settings() { bBBOXRFC7946 = true; - if (nCoordPrecision < 0) - nCoordPrecision = 7; + if (nXYCoordPrecision < 0) + nXYCoordPrecision = 7; + if (nZCoordPrecision < 0) + nZCoordPrecision = 3; bPolygonRightHandRule = true; bCanPatchCoordinatesWithNativeData = false; bHonourReservedRFC7946Members = true; @@ -95,13 +97,23 @@ void OGRGeoJSONWriteOptions::SetIDOptions(CSLConstList papszOptions) /************************************************************************/ static json_object * -json_object_new_coord(double dfVal, const OGRGeoJSONWriteOptions &oOptions) +json_object_new_coord(double dfVal, int nDimIdx, + const OGRGeoJSONWriteOptions &oOptions) { // If coordinate precision is specified, or significant figures is not // then use the '%f' formatting. - if (oOptions.nCoordPrecision >= 0 || oOptions.nSignificantFigures < 0) - return json_object_new_double_with_precision(dfVal, - oOptions.nCoordPrecision); + if (nDimIdx <= 2) + { + if (oOptions.nXYCoordPrecision >= 0 || oOptions.nSignificantFigures < 0) + return json_object_new_double_with_precision( + dfVal, oOptions.nXYCoordPrecision); + } + else + { + if (oOptions.nZCoordPrecision >= 0 || oOptions.nSignificantFigures < 0) + return json_object_new_double_with_precision( + dfVal, oOptions.nZCoordPrecision); + } return json_object_new_double_with_significant_figures( dfVal, oOptions.nSignificantFigures); @@ -734,19 +746,21 @@ json_object *OGRGeoJSONWriteFeature(OGRFeature *poFeature, json_object *poObjBBOX = json_object_new_array(); json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MinX, oOptions)); + poObjBBOX, json_object_new_coord(sEnvelope.MinX, 1, oOptions)); json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MinY, oOptions)); + poObjBBOX, json_object_new_coord(sEnvelope.MinY, 2, oOptions)); if (wkbHasZ(poGeometry->getGeometryType())) json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MinZ, oOptions)); + poObjBBOX, + json_object_new_coord(sEnvelope.MinZ, 3, oOptions)); json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MaxX, oOptions)); + poObjBBOX, json_object_new_coord(sEnvelope.MaxX, 1, oOptions)); json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MaxY, oOptions)); + poObjBBOX, json_object_new_coord(sEnvelope.MaxY, 2, oOptions)); if (wkbHasZ(poGeometry->getGeometryType())) json_object_array_add( - poObjBBOX, json_object_new_coord(sEnvelope.MaxZ, oOptions)); + poObjBBOX, + json_object_new_coord(sEnvelope.MaxZ, 3, oOptions)); json_object_object_add(poObj, "bbox", poObjBBOX); } @@ -1085,16 +1099,6 @@ json_object *OGRGeoJSONWriteAttributes(OGRFeature *poFeature, /* OGRGeoJSONWriteGeometry */ /************************************************************************/ -json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry, - int nCoordPrecision, - int nSignificantFigures) -{ - OGRGeoJSONWriteOptions oOptions; - oOptions.nCoordPrecision = nCoordPrecision; - oOptions.nSignificantFigures = nSignificantFigures; - return OGRGeoJSONWriteGeometry(poGeometry, oOptions); -} - json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry, const OGRGeoJSONWriteOptions &oOptions) { @@ -1402,8 +1406,8 @@ json_object *OGRGeoJSONWriteCoords(double const &fX, double const &fY, return nullptr; } poObjCoords = json_object_new_array(); - json_object_array_add(poObjCoords, json_object_new_coord(fX, oOptions)); - json_object_array_add(poObjCoords, json_object_new_coord(fY, oOptions)); + json_object_array_add(poObjCoords, json_object_new_coord(fX, 1, oOptions)); + json_object_array_add(poObjCoords, json_object_new_coord(fY, 2, oOptions)); return poObjCoords; } @@ -1420,9 +1424,9 @@ json_object *OGRGeoJSONWriteCoords(double const &fX, double const &fY, return nullptr; } json_object *poObjCoords = json_object_new_array(); - json_object_array_add(poObjCoords, json_object_new_coord(fX, oOptions)); - json_object_array_add(poObjCoords, json_object_new_coord(fY, oOptions)); - json_object_array_add(poObjCoords, json_object_new_coord(fZ, oOptions)); + json_object_array_add(poObjCoords, json_object_new_coord(fX, 1, oOptions)); + json_object_array_add(poObjCoords, json_object_new_coord(fY, 2, oOptions)); + json_object_array_add(poObjCoords, json_object_new_coord(fZ, 3, oOptions)); return poObjCoords; } @@ -1528,10 +1532,17 @@ char *OGR_G_ExportToJson(OGRGeometryH hGeometry) * The following options are supported : *
      *
    • COORDINATE_PRECISION=number: maximum number of figures after decimal - * separator to write in coordinates.
    • SIGNIFICANT_FIGURES=number: + * separator to write in coordinates.
    • + *
    • XY_COORD_PRECISION=integer: number of decimal figures for X,Y coordinates + * (added in GDAL 3.9)
    • + *
    • Z_COORD_PRECISION=integer: number of decimal figures for Z coordinates + * (added in GDAL 3.9)
    • + *
    • SIGNIFICANT_FIGURES=number: * maximum number of significant figures (GDAL >= 2.1).
    • *
    * + * If XY_COORD_PRECISION or Z_COORD_PRECISION is specified, COORDINATE_PRECISION + * or SIGNIFICANT_FIGURES will be ignored if specified. * If COORDINATE_PRECISION is defined, SIGNIFICANT_FIGURES will be ignored if * specified. * When none are defined, the default is COORDINATE_PRECISION=15. @@ -1551,14 +1562,17 @@ char *OGR_G_ExportToJsonEx(OGRGeometryH hGeometry, char **papszOptions) OGRGeometry *poGeometry = OGRGeometry::FromHandle(hGeometry); - const int nCoordPrecision = - atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1")); + const char *pszCoordPrecision = + CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "-1"); const int nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); OGRGeoJSONWriteOptions oOptions; - oOptions.nCoordPrecision = nCoordPrecision; + oOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef( + papszOptions, "XY_COORD_PRECISION", pszCoordPrecision)); + oOptions.nZCoordPrecision = atoi(CSLFetchNameValueDef( + papszOptions, "Z_COORD_PRECISION", pszCoordPrecision)); oOptions.nSignificantFigures = nSignificantFigures; // If the CRS has latitude, longitude (or northing, easting) axis order, diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.h b/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.h index 6ef2f6ee0f65..0a33665ba669 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.h +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.h @@ -75,7 +75,8 @@ class OGRGeoJSONWriteOptions public: bool bWriteBBOX = false; bool bBBOXRFC7946 = false; - int nCoordPrecision = -1; + int nXYCoordPrecision = -1; + int nZCoordPrecision = -1; int nSignificantFigures = -1; bool bPolygonRightHandRule = false; bool bCanPatchCoordinatesWithNativeData = true; @@ -102,9 +103,6 @@ void OGRGeoJSONWriteId(const OGRFeature *poFeature, json_object *poObj, json_object *OGRGeoJSONWriteAttributes( OGRFeature *poFeature, bool bWriteIdIfFoundInAttributes = true, const OGRGeoJSONWriteOptions &oOptions = OGRGeoJSONWriteOptions()); -json_object *OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry, - int nCoordPrecision, - int nSignificantFigures); json_object CPL_DLL * OGRGeoJSONWriteGeometry(const OGRGeometry *poGeometry, const OGRGeoJSONWriteOptions &oOptions); diff --git a/ogr/ogrsf_frmts/georss/ogr_georss.h b/ogr/ogrsf_frmts/georss/ogr_georss.h index 90af671358b6..3b285dd01af6 100644 --- a/ogr/ogrsf_frmts/georss/ogr_georss.h +++ b/ogr/ogrsf_frmts/georss/ogr_georss.h @@ -210,11 +210,9 @@ class OGRGeoRSSDataSource final : public OGRDataSource } OGRLayer *GetLayer(int) override; - OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) override; - + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *) override; VSILFILE *GetOutputFP() diff --git a/ogr/ogrsf_frmts/georss/ogrgeorssdatasource.cpp b/ogr/ogrsf_frmts/georss/ogrgeorssdatasource.cpp index 8d8ce12d0e15..40d0264578a9 100644 --- a/ogr/ogrsf_frmts/georss/ogrgeorssdatasource.cpp +++ b/ogr/ogrsf_frmts/georss/ogrgeorssdatasource.cpp @@ -127,14 +127,16 @@ OGRLayer *OGRGeoRSSDataSource::GetLayer(int iLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRGeoRSSDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType /* eType */, - char ** /* papszOptions */) +OGRLayer * +OGRGeoRSSDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { if (fpOutput == nullptr) return nullptr; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; if (poSRS != nullptr && eGeomDialect != GEORSS_GML) { OGRSpatialReference oSRS; diff --git a/ogr/ogrsf_frmts/gml/gmlfeatureclass.cpp b/ogr/ogrsf_frmts/gml/gmlfeatureclass.cpp index 80bd5a8efb58..86871ce29e30 100644 --- a/ogr/ogrsf_frmts/gml/gmlfeatureclass.cpp +++ b/ogr/ogrsf_frmts/gml/gmlfeatureclass.cpp @@ -480,7 +480,7 @@ bool GMLFeatureClass::InitializeFromXML(CPLXMLNode *psRoot) bool bHasFoundGeomElements = false; const char *pszGName = ""; const char *pszGPath = ""; - int nGeomType = wkbUnknown; + OGRwkbGeometryType nGeomType = wkbUnknown; const auto FlattenGeomTypeFromInt = [](int eType) { @@ -509,20 +509,24 @@ bool GMLFeatureClass::InitializeFromXML(CPLXMLNode *psRoot) nGeomType = wkbUnknown; if (pszType != nullptr && !EQUAL(pszType, "0")) { - nGeomType = atoi(pszType); - const int nFlattenGeomType = FlattenGeomTypeFromInt(nGeomType); - if (nGeomType != 0 && - !(nFlattenGeomType >= static_cast(wkbPoint) && - nFlattenGeomType <= static_cast(wkbTIN))) + int nGeomTypeInt = atoi(pszType); + const int nFlattenGeomTypeInt = + FlattenGeomTypeFromInt(nGeomTypeInt); + if (nGeomTypeInt != 0 && + !(nFlattenGeomTypeInt >= static_cast(wkbPoint) && + nFlattenGeomTypeInt <= static_cast(wkbTIN))) { - nGeomType = wkbUnknown; CPLError(CE_Warning, CPLE_AppDefined, "Unrecognized geometry type : %s", pszType); } - else if (nGeomType == 0) + else if (nGeomTypeInt == 0) { nGeomType = OGRFromOGCGeomType(pszType); } + else + { + nGeomType = static_cast(nGeomTypeInt); + } } bHasFoundGeomElements = true; auto poDefn = new GMLGeometryPropertyDefn(pszName, pszElementPath, @@ -595,27 +599,31 @@ bool GMLFeatureClass::InitializeFromXML(CPLXMLNode *psRoot) nGeomType = wkbUnknown; if (pszGeometryType != nullptr && !EQUAL(pszGeometryType, "0")) { - nGeomType = atoi(pszGeometryType); - const int nFlattenGeomType = FlattenGeomTypeFromInt(nGeomType); - if (nGeomType == 100 || EQUAL(pszGeometryType, "NONE")) + const int nGeomTypeInt = atoi(pszGeometryType); + const int nFlattenGeomTypeInt = + FlattenGeomTypeFromInt(nGeomTypeInt); + if (nGeomTypeInt == 100 || EQUAL(pszGeometryType, "NONE")) { bHasValidGeometryElementPath = false; bHasFoundGeomType = false; break; } - else if (nGeomType != 0 && - !(nFlattenGeomType >= static_cast(wkbPoint) && - nFlattenGeomType <= static_cast(wkbTIN))) + else if (nGeomTypeInt != 0 && + !(nFlattenGeomTypeInt >= static_cast(wkbPoint) && + nFlattenGeomTypeInt <= static_cast(wkbTIN))) { - nGeomType = wkbUnknown; CPLError(CE_Warning, CPLE_AppDefined, "Unrecognized geometry type : %s", pszGeometryType); } - else if (nGeomType == 0) + else if (nGeomTypeInt == 0) { nGeomType = OGRFromOGCGeomType(pszGeometryType); } + else + { + nGeomType = static_cast(nGeomTypeInt); + } } bHasFoundGeomType = true; } diff --git a/ogr/ogrsf_frmts/gml/gmlpropertydefn.cpp b/ogr/ogrsf_frmts/gml/gmlpropertydefn.cpp index b44687053a01..10b5cdb119bd 100644 --- a/ogr/ogrsf_frmts/gml/gmlpropertydefn.cpp +++ b/ogr/ogrsf_frmts/gml/gmlpropertydefn.cpp @@ -217,15 +217,16 @@ void GMLPropertyDefn::AnalysePropertyValue(const GMLProperty *psGMLProperty, /* GMLGeometryPropertyDefn */ /************************************************************************/ -GMLGeometryPropertyDefn::GMLGeometryPropertyDefn(const char *pszName, - const char *pszSrcElement, - int nType, int nAttributeIndex, - bool bNullable) +GMLGeometryPropertyDefn::GMLGeometryPropertyDefn( + const char *pszName, const char *pszSrcElement, OGRwkbGeometryType nType, + int nAttributeIndex, bool bNullable, + const OGRGeomCoordinatePrecision &oCoordPrec) : m_pszName((pszName == nullptr || pszName[0] == '\0') ? CPLStrdup(pszSrcElement) : CPLStrdup(pszName)), m_pszSrcElement(CPLStrdup(pszSrcElement)), m_nGeometryType(nType), - m_nAttributeIndex(nAttributeIndex), m_bNullable(bNullable) + m_nAttributeIndex(nAttributeIndex), m_bNullable(bNullable), + m_oCoordPrecision(oCoordPrec) { } diff --git a/ogr/ogrsf_frmts/gml/gmlreader.cpp b/ogr/ogrsf_frmts/gml/gmlreader.cpp index fce461094cbf..747f788d45ca 100644 --- a/ogr/ogrsf_frmts/gml/gmlreader.cpp +++ b/ogr/ogrsf_frmts/gml/gmlreader.cpp @@ -1429,9 +1429,8 @@ bool GMLReader::PrescanForSchema(bool bGetExtents, bool bOnlyDetectSRS) } else { - poGeomProperty->SetType( - static_cast(OGRMergeGeometryTypesEx( - eGType, poGeometry->getGeometryType(), true))); + poGeomProperty->SetType(OGRMergeGeometryTypesEx( + eGType, poGeometry->getGeometryType(), true)); } // Merge extents. diff --git a/ogr/ogrsf_frmts/gml/gmlreader.h b/ogr/ogrsf_frmts/gml/gmlreader.h index 93dc4deedca7..8fdb2704ad9c 100644 --- a/ogr/ogrsf_frmts/gml/gmlreader.h +++ b/ogr/ogrsf_frmts/gml/gmlreader.h @@ -186,15 +186,19 @@ class CPL_DLL GMLGeometryPropertyDefn { char *m_pszName; char *m_pszSrcElement; - int m_nGeometryType; + OGRwkbGeometryType m_nGeometryType = wkbUnknown; int m_nAttributeIndex; bool m_bNullable; bool m_bSRSNameConsistent = true; std::string m_osSRSName{}; + OGRGeomCoordinatePrecision m_oCoordPrecision{}; public: GMLGeometryPropertyDefn(const char *pszName, const char *pszSrcElement, - int nType, int nAttributeIndex, bool bNullable); + OGRwkbGeometryType nType, int nAttributeIndex, + bool bNullable, + const OGRGeomCoordinatePrecision &oCoordPrec = + OGRGeomCoordinatePrecision()); ~GMLGeometryPropertyDefn(); const char *GetName() const @@ -202,11 +206,11 @@ class CPL_DLL GMLGeometryPropertyDefn return m_pszName; } - int GetType() const + OGRwkbGeometryType GetType() const { return m_nGeometryType; } - void SetType(int nType) + void SetType(OGRwkbGeometryType nType) { m_nGeometryType = nType; } @@ -225,6 +229,11 @@ class CPL_DLL GMLGeometryPropertyDefn return m_bNullable; } + const OGRGeomCoordinatePrecision &GetCoordinatePrecision() const + { + return m_oCoordPrecision; + } + void SetSRSName(const std::string &srsName) { m_bSRSNameConsistent = true; diff --git a/ogr/ogrsf_frmts/gml/ogr_gml.h b/ogr/ogrsf_frmts/gml/ogr_gml.h index b6f0a2b1bc5c..82fc3fd3c88f 100644 --- a/ogr/ogrsf_frmts/gml/ogr_gml.h +++ b/ogr/ogrsf_frmts/gml/ogr_gml.h @@ -192,12 +192,9 @@ class OGRGMLDataSource final : public OGRDataSource return nLayers; } OGRLayer *GetLayer(int) override; - - virtual OGRLayer *ICreateLayer(const char *, - const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; - + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *) override; VSILFILE *GetOutputFP() const diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 317c37ae3f25..dc7cda9e009d 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -136,23 +136,59 @@ OGRGMLDataSource::~OGRGMLDataSource() : CPLStrdup(""); char szLowerCorner[75] = {}; char szUpperCorner[75] = {}; + + OGRWktOptions coordOpts; + + if (OGRGMLDataSource::GetLayerCount() == 1) + { + OGRLayer *poLayer = OGRGMLDataSource::GetLayer(0); + if (poLayer->GetLayerDefn()->GetGeomFieldCount() == 1) + { + const auto &oCoordPrec = poLayer->GetLayerDefn() + ->GetGeomFieldDefn(0) + ->GetCoordinatePrecision(); + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.xyPrecision = OGRGeomCoordinatePrecision:: + ResolutionToPrecision( + oCoordPrec.dfXYResolution); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.zPrecision = OGRGeomCoordinatePrecision:: + ResolutionToPrecision(oCoordPrec.dfZResolution); + } + } + } + + std::string wkt; if (bCoordSwap) { - OGRMakeWktCoordinate(szLowerCorner, sBoundingRect.MinY, - sBoundingRect.MinX, sBoundingRect.MinZ, - bBBOX3D ? 3 : 2); - OGRMakeWktCoordinate(szUpperCorner, sBoundingRect.MaxY, - sBoundingRect.MaxX, sBoundingRect.MaxZ, - bBBOX3D ? 3 : 2); + wkt = OGRMakeWktCoordinate( + sBoundingRect.MinY, sBoundingRect.MinX, + sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts); + memcpy(szLowerCorner, wkt.data(), wkt.size() + 1); + + wkt = OGRMakeWktCoordinate( + sBoundingRect.MaxY, sBoundingRect.MaxX, + sBoundingRect.MaxZ, bBBOX3D ? 3 : 2, coordOpts); + memcpy(szUpperCorner, wkt.data(), wkt.size() + 1); } else { - OGRMakeWktCoordinate(szLowerCorner, sBoundingRect.MinX, - sBoundingRect.MinY, sBoundingRect.MinZ, - bBBOX3D ? 3 : 2); - OGRMakeWktCoordinate(szUpperCorner, sBoundingRect.MaxX, - sBoundingRect.MaxY, sBoundingRect.MaxZ, - (bBBOX3D) ? 3 : 2); + wkt = OGRMakeWktCoordinate( + sBoundingRect.MinX, sBoundingRect.MinY, + sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts); + memcpy(szLowerCorner, wkt.data(), wkt.size() + 1); + + wkt = OGRMakeWktCoordinate( + sBoundingRect.MaxX, sBoundingRect.MaxY, + sBoundingRect.MaxZ, (bBBOX3D) ? 3 : 2, coordOpts); + memcpy(szUpperCorner, wkt.data(), wkt.size() + 1); } if (bWriteSpaceIndentation) VSIFPrintfL(fpOutput, " "); @@ -1663,8 +1699,7 @@ OGRGMLLayer *OGRGMLDataSource::TranslateGMLSchema(GMLFeatureClass *poClass) poProperty->SetType(wkbPolyhedralSurfaceZ); } - OGRGeomFieldDefn oField(poProperty->GetName(), - (OGRwkbGeometryType)poProperty->GetType()); + OGRGeomFieldDefn oField(poProperty->GetName(), poProperty->GetType()); if (poClass->GetGeometryPropertyCount() == 1 && poClass->GetFeatureCount() == 0) { @@ -1692,6 +1727,7 @@ OGRGMLLayer *OGRGMLDataSource::TranslateGMLSchema(GMLFeatureClass *poClass) oField.SetSpatialRef(poSRS); } oField.SetNullable(poProperty->IsNullable()); + oField.SetCoordinatePrecision(poProperty->GetCoordinatePrecision()); poLayer->GetLayerDefn()->AddGeomFieldDefn(&oField); } @@ -1968,10 +2004,10 @@ void OGRGMLDataSource::WriteTopElements() /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRGMLDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - CPL_UNUSED char **papszOptions) +OGRLayer * +OGRGMLDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList /*papszOptions*/) { // Verify we are in update mode. if (fpOutput == nullptr) @@ -1984,6 +2020,11 @@ OGRLayer *OGRGMLDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + // Ensure name is safe as an element name. char *pszCleanLayerName = CPLStrdup(pszLayerName); @@ -2033,16 +2074,21 @@ OGRLayer *OGRGMLDataSource::ICreateLayer(const char *pszLayerName, poLayer->GetLayerDefn()->SetGeomType(eType); if (eType != wkbNone) { - poLayer->GetLayerDefn()->GetGeomFieldDefn(0)->SetName( - "geometryProperty"); + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + const char *pszGeomFieldName = poSrcGeomFieldDefn->GetNameRef(); + if (!pszGeomFieldName || pszGeomFieldName[0] == 0) + pszGeomFieldName = "geometryProperty"; + poGeomFieldDefn->SetName(pszGeomFieldName); + poGeomFieldDefn->SetNullable(poSrcGeomFieldDefn->IsNullable()); if (poSRS != nullptr) { auto poSRSClone = poSRS->Clone(); poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - poLayer->GetLayerDefn()->GetGeomFieldDefn(0)->SetSpatialRef( - poSRSClone); + poGeomFieldDefn->SetSpatialRef(poSRSClone); poSRSClone->Dereference(); } + poGeomFieldDefn->SetCoordinatePrecision( + poSrcGeomFieldDefn->GetCoordinatePrecision()); } CPLFree(pszCleanLayerName); @@ -2592,12 +2638,51 @@ void OGRGMLDataSource::InsertHeader() } int nMinOccurs = poFieldDefn->IsNullable() ? 0 : 1; - PrintLine( - fpSchema, - " %s%s", - poFieldDefn->GetNameRef(), pszGeometryTypeName, nMinOccurs, - pszGeomTypeComment, osSRSNameComment.c_str()); + const auto &oCoordPrec = poFieldDefn->GetCoordinatePrecision(); + if (oCoordPrec.dfXYResolution == + OGRGeomCoordinatePrecision::UNKNOWN && + oCoordPrec.dfZResolution == OGRGeomCoordinatePrecision::UNKNOWN) + { + PrintLine( + fpSchema, + " %s%s", + poFieldDefn->GetNameRef(), pszGeometryTypeName, nMinOccurs, + pszGeomTypeComment, osSRSNameComment.c_str()); + } + else + { + PrintLine(fpSchema, + " ", + poFieldDefn->GetNameRef(), pszGeometryTypeName, + nMinOccurs); + PrintLine(fpSchema, " "); + PrintLine(fpSchema, " "); + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + PrintLine(fpSchema, + " " + "%g", + oCoordPrec.dfXYResolution); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + PrintLine(fpSchema, + " " + "%g", + oCoordPrec.dfZResolution); + } + PrintLine(fpSchema, " "); + PrintLine(fpSchema, " "); + PrintLine(fpSchema, " %s%s", + pszGeomTypeComment, osSRSNameComment.c_str()); + } } // Emit each of the attributes. diff --git a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp index 9e0054cd1e6d..8a016dbe20cc 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp @@ -294,6 +294,7 @@ void RegisterOGRGML() poDriver->SetMetadataItem(GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnOpen = OGRGMLDriverOpen; poDriver->pfnIdentify = OGRGMLDriverIdentify; diff --git a/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp b/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp index 2918d92d58a6..7abc3ceebfa8 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp @@ -781,7 +781,7 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) for (int iGeomField = 0; iGeomField < poFeatureDefn->GetGeomFieldCount(); iGeomField++) { - OGRGeomFieldDefn *poFieldDefn = + const OGRGeomFieldDefn *poFieldDefn = poFeatureDefn->GetGeomFieldDefn(iGeomField); // Write out Geometry - for now it isn't indented properly. @@ -801,6 +801,8 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) poFieldDefn->GetSpatialRef() != nullptr) poGeom->assignSpatialReference(poFieldDefn->GetSpatialRef()); + const auto &oCoordPrec = poFieldDefn->GetCoordinatePrecision(); + if (bIsGML3Output && poDS->WriteFeatureBoundedBy()) { bool bCoordSwap = false; @@ -810,23 +812,50 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) poDS->GetSRSNameFormat(), &bCoordSwap); char szLowerCorner[75] = {}; char szUpperCorner[75] = {}; + + OGRWktOptions coordOpts; + + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.xyPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + oCoordPrec.dfXYResolution); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + coordOpts.format = OGRWktFormat::F; + coordOpts.zPrecision = + OGRGeomCoordinatePrecision::ResolutionToPrecision( + oCoordPrec.dfZResolution); + } + + std::string wkt; if (bCoordSwap) { - OGRMakeWktCoordinate(szLowerCorner, sGeomBounds.MinY, - sGeomBounds.MinX, sGeomBounds.MinZ, - nCoordDimension); - OGRMakeWktCoordinate(szUpperCorner, sGeomBounds.MaxY, - sGeomBounds.MaxX, sGeomBounds.MaxZ, - nCoordDimension); + wkt = OGRMakeWktCoordinate( + sGeomBounds.MinY, sGeomBounds.MinX, sGeomBounds.MinZ, + nCoordDimension, coordOpts); + memcpy(szLowerCorner, wkt.data(), wkt.size() + 1); + + wkt = OGRMakeWktCoordinate( + sGeomBounds.MaxY, sGeomBounds.MaxX, sGeomBounds.MaxZ, + nCoordDimension, coordOpts); + memcpy(szUpperCorner, wkt.data(), wkt.size() + 1); } else { - OGRMakeWktCoordinate(szLowerCorner, sGeomBounds.MinX, - sGeomBounds.MinY, sGeomBounds.MinZ, - nCoordDimension); - OGRMakeWktCoordinate(szUpperCorner, sGeomBounds.MaxX, - sGeomBounds.MaxY, sGeomBounds.MaxZ, - nCoordDimension); + wkt = OGRMakeWktCoordinate( + sGeomBounds.MinX, sGeomBounds.MinY, sGeomBounds.MinZ, + nCoordDimension, coordOpts); + memcpy(szLowerCorner, wkt.data(), wkt.size() + 1); + + wkt = OGRMakeWktCoordinate( + sGeomBounds.MaxX, sGeomBounds.MaxY, sGeomBounds.MaxZ, + nCoordDimension, coordOpts); + memcpy(szUpperCorner, wkt.data(), wkt.size() + 1); } if (bWriteSpaceIndentation) VSIFPrintfL(fp, " "); @@ -873,6 +902,20 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) poFeature->GetFID())); } + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + papszOptions = CSLAddString( + papszOptions, CPLSPrintf("XY_COORD_RESOLUTION=%g", + oCoordPrec.dfXYResolution)); + } + if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + papszOptions = CSLAddString( + papszOptions, CPLSPrintf("Z_COORD_RESOLUTION=%g", + oCoordPrec.dfZResolution)); + } + char *pszGeometry = nullptr; if (!bIsGML3Output && OGR_GT_IsNonLinear(poGeom->getGeometryType())) { diff --git a/ogr/ogrsf_frmts/gml/parsexsd.cpp b/ogr/ogrsf_frmts/gml/parsexsd.cpp index 3e9a6c466dbf..8ae243fe3886 100644 --- a/ogr/ogrsf_frmts/gml/parsexsd.cpp +++ b/ogr/ogrsf_frmts/gml/parsexsd.cpp @@ -30,6 +30,7 @@ #include "cpl_port.h" #include "parsexsd.h" +#include #include #include #include @@ -481,8 +482,8 @@ static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, std::string osSRSName; // Look if there's a comment restricting to subclasses. - const CPLXMLNode *psIter2 = psAttrDef->psNext; - while (psIter2 != nullptr) + for (const CPLXMLNode *psIter2 = psAttrDef->psNext; + psIter2 != nullptr; psIter2 = psIter2->psNext) { if (psIter2->eType == CXT_Comment) { @@ -515,14 +516,64 @@ static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, } } } + } - psIter2 = psIter2->psNext; + // Try to get coordinate precision from a construct like: + /* + + + + 8.9e-9 + 1e-3 + 1e-3 + + + + */ + OGRGeomCoordinatePrecision oGeomCoordPrec; + const auto psAnnotation = + CPLGetXMLNode(psAttrDef, "annotation"); + if (psAnnotation) + { + for (const CPLXMLNode *psIterAppinfo = + psAnnotation->psChild; + psIterAppinfo; + psIterAppinfo = psIterAppinfo->psNext) + { + if (psIterAppinfo->eType == CXT_Element && + strcmp(psIterAppinfo->pszValue, + "appinfo") == 0 && + strcmp(CPLGetXMLValue(psIterAppinfo, + "source", ""), + "http://ogr.maptools.org/") == 0) + { + if (const char *pszXYRes = CPLGetXMLValue( + psIterAppinfo, + "xy_coordinate_resolution", + nullptr)) + { + const double dfVal = CPLAtof(pszXYRes); + if (dfVal > 0 && std::isfinite(dfVal)) + oGeomCoordPrec.dfXYResolution = + dfVal; + } + if (const char *pszZRes = CPLGetXMLValue( + psIterAppinfo, + "z_coordinate_resolution", nullptr)) + { + const double dfVal = CPLAtof(pszZRes); + if (dfVal > 0 && std::isfinite(dfVal)) + oGeomCoordPrec.dfZResolution = + dfVal; + } + } + } } GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn( pszElementName, pszElementName, eType, - nAttributeIndex, bNullable); + nAttributeIndex, bNullable, oGeomCoordPrec); poDefn->SetSRSName(osSRSName); if (poClass->AddGeometryProperty(poDefn) < 0) diff --git a/ogr/ogrsf_frmts/gmt/ogr_gmt.h b/ogr/ogrsf_frmts/gmt/ogr_gmt.h index b929379004a0..9efa2c6a5c1e 100644 --- a/ogr/ogrsf_frmts/gmt/ogr_gmt.h +++ b/ogr/ogrsf_frmts/gmt/ogr_gmt.h @@ -128,10 +128,9 @@ class OGRGmtDataSource final : public OGRDataSource } OGRLayer *GetLayer(int) override; - virtual OGRLayer *ICreateLayer(const char *, - const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *) override; }; diff --git a/ogr/ogrsf_frmts/gmt/ogrgmtdatasource.cpp b/ogr/ogrsf_frmts/gmt/ogrgmtdatasource.cpp index 032366e92780..d9f85ce53950 100644 --- a/ogr/ogrsf_frmts/gmt/ogrgmtdatasource.cpp +++ b/ogr/ogrsf_frmts/gmt/ogrgmtdatasource.cpp @@ -99,14 +99,18 @@ int OGRGmtDataSource::Create(const char *pszDSName, char ** /* papszOptions */) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRGmtDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - CPL_UNUSED char **papszOptions) +OGRLayer * +OGRGmtDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { if (nLayers != 0) return nullptr; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Establish the geometry type. Note this logic */ /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h index d93290259a84..f8c7688201aa 100644 --- a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h +++ b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h @@ -332,10 +332,9 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource, GDALDataType eDT, char **papszOptions); OGRLayer *GetLayer(int iLayer) override; OGRErr DeleteLayer(int iLayer) override; - OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *) override; std::vector @@ -575,6 +574,9 @@ class OGRGeoPackageLayer CPL_NON_FINAL : public OGRLayer, int m_iGeomCol = -1; std::vector m_anFieldOrdinals{}; + //! Whether to call OGRGeometry::SetPrecision() when reading back geometries from the database + bool m_bUndoDiscardCoordLSBOnReading = false; + void ClearStatement(); virtual OGRErr ResetStatement() = 0; @@ -647,6 +649,7 @@ class OGRGeoPackageTableLayer final : public OGRGeoPackageLayer int m_iSrs = 0; int m_nZFlag = 0; int m_nMFlag = 0; + OGRGeomCoordinateBinaryPrecision m_sBinaryPrecision{}; OGREnvelope *m_poExtent = nullptr; #ifdef ENABLE_GPKG_OGR_CONTENTS GIntBig m_nTotalFeatureCount = -1; @@ -884,12 +887,12 @@ class OGRGeoPackageTableLayer final : public OGRGeoPackageLayer const char *pszObjectType, bool bIsInGpkgContents, bool bIsSpatial, const char *pszGeomColName, const char *pszGeomType, bool bHasZ, bool bHasM); - void SetCreationParameters(OGRwkbGeometryType eGType, - const char *pszGeomColumnName, int bGeomNullable, - OGRSpatialReference *poSRS, - const char *pszFIDColumnName, - const char *pszIdentifier, - const char *pszDescription); + void SetCreationParameters( + OGRwkbGeometryType eGType, const char *pszGeomColumnName, + int bGeomNullable, OGRSpatialReference *poSRS, + const OGRGeomCoordinatePrecision &oCoordPrec, bool bDiscardCoordLSB, + bool bUndoDiscardCoordLSBOnReading, const char *pszFIDColumnName, + const char *pszIdentifier, const char *pszDescription); void SetDeferredSpatialIndexCreation(bool bFlag); void SetASpatialVariant(GPKGASpatialVariant eASpatialVariant) { diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index 3a62f416ce89..7b122f5e090a 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -6602,9 +6602,10 @@ OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *GDALGeoPackageDataset::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ /* Verify we are in update mode. */ @@ -6619,6 +6620,11 @@ OGRLayer *GDALGeoPackageDataset::ICreateLayer( return nullptr; } + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + if (!m_bHasGPKGGeometryColumns) { if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE) @@ -6675,6 +6681,12 @@ OGRLayer *GDALGeoPackageDataset::ICreateLayer( CSLFetchNameValue(papszOptions, "GEOMETRY_NAME"); if (pszGeomColumnName == nullptr) /* deprecated name */ pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN"); + if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn) + { + pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef(); + if (pszGeomColumnName && pszGeomColumnName[0] == 0) + pszGeomColumnName = nullptr; + } if (pszGeomColumnName == nullptr) pszGeomColumnName = "geom"; const bool bGeomNullable = @@ -6759,8 +6771,15 @@ OGRLayer *GDALGeoPackageDataset::ICreateLayer( poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); } poLayer->SetCreationParameters( - eGType, pszGeomColumnName, bGeomNullable, poSRS, pszFIDColumnName, - pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION")); + eGType, pszGeomColumnName, bGeomNullable, poSRS, + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision() + : OGRGeomCoordinatePrecision(), + CPLTestBool( + CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")), + CPLTestBool(CSLFetchNameValueDef( + papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")), + pszFIDColumnName, pszIdentifier, + CSLFetchNameValue(papszOptions, "DESCRIPTION")); if (poSRS) { poSRS->Release(); @@ -8143,7 +8162,7 @@ static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */, } size_t nBLOBDestLen = 0; GByte *pabyDestBLOB = - GPkgGeometryFromOGR(poGeom, nDestSRID, &nBLOBDestLen); + GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen); if (!pabyDestBLOB) { sqlite3_result_null(pContext); @@ -8208,8 +8227,8 @@ static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc, } size_t nBLOBDestLen = 0; - GByte *pabyDestBLOB = - GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId, &nBLOBDestLen); + GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId, + nullptr, &nBLOBDestLen); if (!pabyDestBLOB) { sqlite3_result_null(pContext); @@ -8416,7 +8435,7 @@ void OGRGeoPackageTransform(sqlite3_context *pContext, int argc, size_t nBLOBDestLen = 0; GByte *pabyDestBLOB = - GPkgGeometryFromOGR(poGeom.get(), nDestSRID, &nBLOBDestLen); + GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen); if (!pabyDestBLOB) { sqlite3_result_null(pContext); @@ -8970,7 +8989,8 @@ static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/, const auto poSRS = poLayer->GetSpatialRef(); const int nSRID = poSRS ? poDS->GetSrsId(*poSRS) : 0; size_t nBLOBDestLen = 0; - GByte *pabyDestBLOB = GPkgGeometryFromOGR(&oPoly, nSRID, &nBLOBDestLen); + GByte *pabyDestBLOB = + GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen); if (!pabyDestBLOB) { sqlite3_result_null(pContext); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp index 94db259ae453..e5e3043bad72 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp @@ -675,6 +675,13 @@ void RegisterOGRGeoPackage() "