From 7123048957321d89c0a1d0256fa4f03039f09be4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 14:35:29 +0100 Subject: [PATCH 001/163] CMake: add a GDAL_FIND_PACKAGE_OPENJPEG_METHOD cache variable to potentially use 'find_package(OpenJPEG CONFIG)' with newer versions --- cmake/helpers/CheckDependentLibraries.cmake | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 () From 97c1f7f0071b2560d7107e680e9a44b5c5b46855 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 22 Feb 2024 21:24:55 +0100 Subject: [PATCH 002/163] Add gdal::GCP class --- gcore/gdal_misc.cpp | 112 ++++++++++++++++++++++++++++++++++++++++++ gcore/gdal_priv.h | 116 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index b219a9437154..a199f3a8c17d 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -1345,6 +1345,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() */ /************************************************************************/ diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index 0368de11c838..2f2fa7b9a1d9 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 */ /* ******************************************************************** */ From 733c199a1b8818e7c99eb6f76305c9c7eee39ded Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 16:38:16 +0100 Subject: [PATCH 003/163] Add unit tests for gdal::GCP class --- autotest/cpp/test_gdal.cpp | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 5828da330ef5..76f33c59acf1 100644 --- a/autotest/cpp/test_gdal.cpp +++ b/autotest/cpp/test_gdal.cpp @@ -3597,4 +3597,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 From 638ea3a90e0b08ae8b0742b4c6e454ad8335176c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 22 Feb 2024 21:25:14 +0100 Subject: [PATCH 004/163] GTiff: use gdal::GCP --- frmts/gtiff/gtiffdataset.cpp | 8 +- frmts/gtiff/gtiffdataset.h | 3 +- frmts/gtiff/gtiffdataset_read.cpp | 118 ++++++++++++----------------- frmts/gtiff/gtiffdataset_write.cpp | 30 +++----- 4 files changed, 61 insertions(+), 98 deletions(-) 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..4dbdcb031191 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) { @@ -8252,16 +8252,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 +8316,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 +8374,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; From 57a0a223f39514e258e64ccf804c3e5221bbd6c9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 22 Feb 2024 21:25:30 +0100 Subject: [PATCH 005/163] HFA: use gdal::GCP --- frmts/hfa/hfadataset.cpp | 31 ++++++++++--------------------- frmts/hfa/hfadataset.h | 3 +-- 2 files changed, 11 insertions(+), 23 deletions(-) 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); From 8bc5b2b3c43675bf2a35e9b9a153b171204eab20 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 22 Feb 2024 21:54:38 +0100 Subject: [PATCH 006/163] Make GDALSerializeGCPListToXML() / GDALDeserializeGCPListFromXML() use gdal::GCP --- alg/gdal_crs.cpp | 133 +++++++++++++++++---------------------- alg/gdal_tps.cpp | 69 ++++++++------------ frmts/vrt/vrtdataset.cpp | 27 ++------ frmts/vrt/vrtdataset.h | 3 +- gcore/gdal_misc.cpp | 84 ++++++++----------------- gcore/gdal_pam.h | 3 +- gcore/gdal_priv.h | 6 +- gcore/gdalpamdataset.cpp | 70 +++++++-------------- 8 files changed, 141 insertions(+), 254 deletions(-) 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/frmts/vrt/vrtdataset.cpp b/frmts/vrt/vrtdataset.cpp index 5e99d7437ed6..0bebede6e916 100644 --- a/frmts/vrt/vrtdataset.cpp +++ b/frmts/vrt/vrtdataset.cpp @@ -99,11 +99,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; @@ -313,10 +308,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); } /* -------------------------------------------------------------------- */ @@ -503,8 +497,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); } /* -------------------------------------------------------------------- */ @@ -619,7 +612,7 @@ CPLErr VRTDataset::XMLInit(const CPLXMLNode *psTree, const char *pszVRTPathIn) int VRTDataset::GetGCPCount() { - return m_nGCPCount; + return static_cast(m_asGCPs.size()); } /************************************************************************/ @@ -629,7 +622,7 @@ int VRTDataset::GetGCPCount() const GDAL_GCP *VRTDataset::GetGCPs() { - return m_pasGCPList; + return gdal::GCP::c_ptr(m_asGCPs); } /************************************************************************/ @@ -642,17 +635,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..43f15da5596c 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; diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index a199f3a8c17d..ca2f6c0a3a07 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -4168,8 +4168,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; @@ -4199,10 +4199,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) @@ -4211,25 +4209,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())); } } @@ -4238,7 +4234,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) @@ -4277,42 +4273,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) @@ -4337,13 +4307,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) @@ -4353,7 +4323,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, @@ -4361,13 +4331,9 @@ void GDALDeserializeGCPListFromXML(const CPLXMLNode *psGCPList, bOK = false; } - if (!bOK) - { - GDALDeinitGCPs(1, psGCP); - } - else + if (bOK) { - (*pnGCPCount)++; + asGCPs.emplace_back(std::move(gcp)); } } } 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 2f2fa7b9a1d9..dfe671a759d4 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -4169,11 +4169,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/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()); } /* -------------------------------------------------------------------- */ From a29abc73a4088dbd30615e28cccf904b000a1954 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 16:50:19 +0100 Subject: [PATCH 007/163] PDF: use gdal::GCP --- frmts/pdf/pdfcreatecopy.cpp | 37 +++++++++--------- frmts/pdf/pdfcreatefromcomposition.cpp | 53 ++++++++++---------------- frmts/pdf/pdfcreatefromcomposition.h | 4 +- 3 files changed, 41 insertions(+), 53 deletions(-) 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); From 0f85594b61756e5b30548b31b395ff5722bbb6aa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 16:55:40 +0100 Subject: [PATCH 008/163] HDF4: use gdal::GCP --- frmts/hdf4/hdf4imagedataset.cpp | 95 ++++++++++----------------------- 1 file changed, 28 insertions(+), 67 deletions(-) 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); } } } From 22ad089bd6e333d0c0a65c392267d90ad8130a20 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 17:00:09 +0100 Subject: [PATCH 009/163] HDF5: use gdal::GCP --- frmts/hdf5/hdf5imagedataset.cpp | 85 ++++++++------------------------- 1 file changed, 21 insertions(+), 64 deletions(-) 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); From 376f55db81ad746a60a004ef64cdee176fce7736 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 17:02:25 +0100 Subject: [PATCH 010/163] MEM: use gdal::GCP --- frmts/mem/memdataset.cpp | 18 +++++------------- frmts/mem/memdataset.h | 3 +-- 2 files changed, 6 insertions(+), 15 deletions(-) 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; From 1e7aed6784915e69b44c58b42b8ffe06840c3c18 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 17:06:50 +0100 Subject: [PATCH 011/163] JPEG: use gdal::GCP --- frmts/jpeg/jpgdataset.cpp | 23 +++++++++++------------ frmts/jpeg/jpgdataset.h | 3 +-- 2 files changed, 12 insertions(+), 14 deletions(-) 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; From 059b2671ef37453ff517f3d53aed52dd858baa42 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 5 Mar 2024 16:22:50 +0100 Subject: [PATCH 012/163] Add OSRSetFromUserInputEx() and map it to SWIG (fixes #9358) --- autotest/osr/osr_url.py | 9 +++++++++ ogr/ogr_srs_api.h | 2 ++ ogr/ogrspatialreference.cpp | 27 +++++++++++++++++++++++++-- swig/csharp/apps/OSRTransform.cs | 2 +- swig/include/osr.i | 16 ++++++++++++++-- 5 files changed, 51 insertions(+), 5 deletions(-) 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/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/ogrspatialreference.cpp b/ogr/ogrspatialreference.cpp index fef6875c044f..0957a3efb929 100644 --- a/ogr/ogrspatialreference.cpp +++ b/ogr/ogrspatialreference.cpp @@ -3699,7 +3699,8 @@ OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition) * possible applications should call the specific method appropriate if the * input is known to be in a particular format. * - * This method does the same thing as the OSRSetFromUserInput() function. + * This method does the same thing as the OSRSetFromUserInput() and + * OSRSetFromUserInputEx() functions. * * @param pszDefinition text definition to try to deduce SRS from. * @@ -3709,7 +3710,7 @@ OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition) * Whether http:// or https:// access is allowed. Defaults to YES. *
  • ALLOW_FILE_ACCESS=YES/NO. * Whether reading a file using the Virtual File System layer is allowed - * (can also involve network access). Defaults to YES. + * (can also involve network access). Defaults to YES. * * * @return OGRERR_NONE on success, or an error code if the name isn't @@ -4034,6 +4035,8 @@ OGRErr OGRSpatialReference::SetFromUserInput(const char *pszDefinition, * \brief Set spatial reference from various text formats. * * This function is the same as OGRSpatialReference::SetFromUserInput() + * + * \see OSRSetFromUserInputEx() for a variant allowing to pass options. */ OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS, const char *pszDef) @@ -4044,6 +4047,26 @@ OGRErr CPL_STDCALL OSRSetFromUserInput(OGRSpatialReferenceH hSRS, return ToPointer(hSRS)->SetFromUserInput(pszDef); } +/************************************************************************/ +/* OSRSetFromUserInputEx() */ +/************************************************************************/ + +/** + * \brief Set spatial reference from various text formats. + * + * This function is the same as OGRSpatialReference::SetFromUserInput(). + * + * @since GDAL 3.9 + */ +OGRErr OSRSetFromUserInputEx(OGRSpatialReferenceH hSRS, const char *pszDef, + CSLConstList papszOptions) + +{ + VALIDATE_POINTER1(hSRS, "OSRSetFromUserInputEx", OGRERR_FAILURE); + + return ToPointer(hSRS)->SetFromUserInput(pszDef, papszOptions); +} + /************************************************************************/ /* ImportFromUrl() */ /************************************************************************/ diff --git a/swig/csharp/apps/OSRTransform.cs b/swig/csharp/apps/OSRTransform.cs index 46da01626fe5..6d32970c039a 100644 --- a/swig/csharp/apps/OSRTransform.cs +++ b/swig/csharp/apps/OSRTransform.cs @@ -71,4 +71,4 @@ public static void Main(string[] args) { System.Environment.Exit(-1); } } -} \ No newline at end of file +} diff --git a/swig/include/osr.i b/swig/include/osr.i index 37851cd9aa9f..1aef03664297 100644 --- a/swig/include/osr.i +++ b/swig/include/osr.i @@ -927,9 +927,21 @@ public: return OSRSetWellKnownGeogCS( self, name ); } - OGRErr SetFromUserInput( const char *name ) { - return OSRSetFromUserInput( self, name ); +#ifdef SWIGCSHARP + OGRErr SetFromUserInput( const char *name) { + return OSRSetFromUserInputEx( self, name, NULL ); + } + OGRErr SetFromUserInput( const char *name, char** options ) { + return OSRSetFromUserInputEx( self, name, options ); } +#else +#ifndef SWIGJAVA + %feature( "kwargs" ) SetFromUserInput; +#endif + OGRErr SetFromUserInput( const char *name, char** options = NULL ) { + return OSRSetFromUserInputEx( self, name, options ); + } +#endif OGRErr CopyGeogCSFrom( OSRSpatialReferenceShadow *rhs ) { return OSRCopyGeogCSFrom( self, rhs ); From 02b2eb8b46a65a19caa9b932dc0c6659ce8ee1c2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 6 Mar 2024 21:28:54 +0100 Subject: [PATCH 013/163] gdalinfo -json/gdal.Info(format='json'): avoid error/exception on engineering CRS (fixes #9396) --- apps/gdalinfo_lib.cpp | 23 ++++++++++++-------- autotest/utilities/test_gdalinfo_lib.py | 29 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) 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/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 From a8134aa71098c17943049c710f465b2c7fc7edbc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 6 Mar 2024 23:37:26 +0100 Subject: [PATCH 014/163] gdal_translate -expand rgb[a]: automatically select Byte output data type if not specified and color table compatible of it (fixes #9402) --- apps/gdal_translate_lib.cpp | 37 ++++++++++++++++++- autotest/utilities/test_gdal_translate_lib.py | 31 ++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) 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/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 From 6a80a692dbd509b77f8fbdc941c546f4adbd418f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Mar 2024 15:30:04 +0100 Subject: [PATCH 015/163] gdal_grid: error out on in invalid layer name, SQL statement or failed where condition (fixes #9406) --- apps/gdal_grid_lib.cpp | 36 ++++++++++------- autotest/utilities/test_gdal_grid_lib.py | 51 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 14 deletions(-) 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/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", + ) From 2e3f4f4632d43166281f30f94f84ad4bd1fa330e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Mar 2024 16:23:26 +0100 Subject: [PATCH 016/163] Selafin: avoid emitting additional error message on file that can't be opened (fixes #9410) --- autotest/utilities/test_gdalinfo.py | 26 ++++++++++++++++++ autotest/utilities/test_ogrinfo.py | 28 ++++++++++++++++++++ ogr/ogrsf_frmts/selafin/ogrselafindriver.cpp | 5 ++++ 3 files changed, 59 insertions(+) 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_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/ogr/ogrsf_frmts/selafin/ogrselafindriver.cpp b/ogr/ogrsf_frmts/selafin/ogrselafindriver.cpp index c5f498e0e0f7..821cf15b7a0d 100644 --- a/ogr/ogrsf_frmts/selafin/ogrselafindriver.cpp +++ b/ogr/ogrsf_frmts/selafin/ogrselafindriver.cpp @@ -57,6 +57,11 @@ static int OGRSelafinDriverIdentify(GDALOpenInfo *poOpenInfo) return TRUE; } + // We can stat() the file but it is not a regular file or we did not + // get access to its content + if (poOpenInfo->bStatOK) + return FALSE; + return -1; } From b971f4714513f790752824d5baebd3fc30e3bd49 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Mar 2024 19:56:01 +0100 Subject: [PATCH 017/163] gdaltransform: make it output extra content at end of input line (unless -ignore_extra_input), and add -E echo mode, and -field_sep option (fixes #9411) --- apps/gdaltransform.cpp | 106 +++++++++++++++++++---- autotest/utilities/test_gdaltransform.py | 57 ++++++++++++ doc/source/programs/gdaltransform.rst | 33 ++++++- 3 files changed, 175 insertions(+), 21 deletions(-) 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/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/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 From db5dc206a6c36c058fe4748505f25486e4ce69e6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 7 Mar 2024 21:47:45 +0100 Subject: [PATCH 018/163] gdallocationinfo: make it output extra content at end of input line (unless -ignore_extra_input), and add -E echo mode, and -field_sep option (fixes #9411) --- apps/gdallocationinfo.cpp | 158 ++++++++++++++++++-- autotest/utilities/test_gdallocationinfo.py | 93 ++++++++++++ doc/source/programs/gdallocationinfo.rst | 35 ++++- 3 files changed, 276 insertions(+), 10 deletions(-) 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/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/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 From e351b46803334e64c8beed88c458f7b9c29c157f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 8 Mar 2024 00:53:16 +0100 Subject: [PATCH 019/163] gdalwarp: fix performance issue when warping to COG (fixes #9416) --- apps/gdalwarp_lib.cpp | 7 +++++-- autotest/utilities/test_gdalwarp_lib.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) 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/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index b85d54dce0f7..dd9089b8ee40 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 From 3b6e04ae73499ab9469e7c10fcb5f089dcc4a38a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 8 Mar 2024 14:51:20 +0100 Subject: [PATCH 020/163] VRTWarpedDataset: add an optimized IRasterIO() implementation This specialized implementation of IRasterIO() 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). e.g. with a 3909x3154 4-band UInt16 input JPEG2000 source dataset: ``` $ gdalwarp -overwrite -t_srs EPSG:4326 -r cubic -wo NUM_THREADS=12 input.jp2 test.vrt Creating output file that is 3909P x 3154L. ``` Now: ``` $ time GDAL_SKIP=JP2KAK,JP2ECW gdal_translate test.vrt out.tif -co tiled=yes Input file size is 3909, 3154 0...10...20...30...40...50...60...70...80...90...100 - done. real 0m1,624s user 0m11,910s sys 0m0,270s ``` Before (or disabling the optimization): ``` $ time GDAL_VRT_WARP_USE_DATASET_RASTERIO=NO GDAL_SKIP=JP2KAK,JP2ECW gdal_translate test.vrt out.tif -co tiled=yes Input file size is 3909, 3154 0...10...20...30...40...50...60...70...80...90...100 - done. real 0m4,115s user 0m9,934s sys 0m0,218s ``` --- alg/gdalwarper.h | 19 +-- alg/gdalwarpoperation.cpp | 128 +++++++++++-------- autotest/gdrivers/vrtwarp.py | 62 ++++++++++ frmts/vrt/vrtdataset.h | 18 +++ frmts/vrt/vrtwarped.cpp | 231 +++++++++++++++++++++++++++++++++++ 5 files changed, 403 insertions(+), 55 deletions(-) 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/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/frmts/vrt/vrtdataset.h b/frmts/vrt/vrtdataset.h index 6c123027779a..2328f5cd4ef6 100644 --- a/frmts/vrt/vrtdataset.h +++ b/frmts/vrt/vrtdataset.h @@ -426,6 +426,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 +827,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() */ /************************************************************************/ From 44ea86785ccc19e71740f0e9a84d5633c031f6a3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 9 Mar 2024 16:47:54 +0100 Subject: [PATCH 021/163] Doc: adjust declared driver capabilities with reality --- doc/source/drivers/raster/ceos.rst | 2 -- doc/source/drivers/raster/grib.rst | 2 ++ doc/source/drivers/raster/lan.rst | 2 ++ doc/source/drivers/raster/pnm.rst | 2 ++ doc/source/drivers/raster/srtmhgt.rst | 2 ++ doc/source/drivers/raster/usgsdem.rst | 2 +- doc/source/drivers/vector/gmlas.rst | 2 ++ doc/source/drivers/vector/mongodbv3.rst | 2 -- doc/source/drivers/vector/s57.rst | 2 ++ doc/source/drivers/vector/vrt.rst | 2 -- 10 files changed, 13 insertions(+), 7 deletions(-) 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/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/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/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/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/vrt.rst b/doc/source/drivers/vector/vrt.rst index b1de74ac1050..b8149bdef518 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:: From 52e003f8d06dfc2e33c47c1eb3143f6814e9ec9f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 9 Mar 2024 16:48:25 +0100 Subject: [PATCH 022/163] Add scripts/check_doc.py to check that driver documentation pages are consistent with driver capabilities --- scripts/check_doc.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 scripts/check_doc.py diff --git a/scripts/check_doc.py b/scripts/check_doc.py new file mode 100755 index 000000000000..c4e1608cc67a --- /dev/null +++ b/scripts/check_doc.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# Check that driver documentation pages are consistent with driver capabilities + +import os + +from osgeo import gdal + +doc_source = os.path.join(os.path.dirname(os.path.dirname(__file__)), "doc", "source") + +map_doc_caps = {} + +for subdir in ("raster", "vector"): + dirname = os.path.join(doc_source, "drivers", subdir) + for f in os.listdir(dirname): + filename = os.path.join(dirname, f) + shortnames = [] + supports_create = False + supports_createcopy = False + for l in open(filename, "rt").readlines(): + if l.startswith(".. shortname:: "): + shortnames.append(l[len(".. shortname:: ") : -1]) + elif "supports_create::" in l: + supports_create = True + elif "supports_createcopy::" in l: + supports_createcopy = True + for shortname in shortnames: + d = {} + if supports_create: + d["supports_create"] = True + if supports_createcopy: + d["supports_createcopy"] = True + if shortname.upper() in map_doc_caps: + assert subdir == "vector" + map_doc_caps["OGR_" + shortname.upper()] = d + else: + map_doc_caps[shortname.upper()] = d + +for i in range(gdal.GetDriverCount()): + drv = gdal.GetDriver(i) + shortname = drv.ShortName + if shortname in ("BIGGIF", "GTX", "NULL", "GNMFile", "GNMDatabase", "HTTP"): + continue + if shortname == "OGR_GMT": + shortname = "GMT" + if shortname == "OGR_OGDI": + shortname = "OGDI" + if shortname == "NWT_GRC": + # mixed in same page as NWT_GRD, which confuses our logic + continue + if shortname == "SQLite": + # sqlite.rst and rasterlite2.rst declare both SQLite driver name, + # which confuses our logic + continue + if shortname in ("OpenFileGDB", "NGW"): + # one page in vector and another one in raster, which confuses our logic + continue + + assert shortname.upper() in map_doc_caps, drv.ShortName + doc_caps = map_doc_caps[shortname.upper()] + + if drv.GetMetadataItem(gdal.DCAP_CREATE): + if shortname == "PDF": + continue # Supports Create() but in a very specific mode, hence better not advertizing it + assert ( + "supports_create" in doc_caps + ), f"Driver {shortname} declares DCAP_CREATE but doc does not!" + else: + if shortname == "HDF4": + continue # This is actually the HDF4Image + assert ( + "supports_create" not in doc_caps + ), f"Driver {shortname} does not declare DCAP_CREATE but doc does!" + + if drv.GetMetadataItem(gdal.DCAP_CREATECOPY): + if shortname == "WMTS": + continue # Supports CreateCopy() but in a very specific mode, hence better not advertizing it + assert ( + "supports_createcopy" in doc_caps + ), f"Driver {shortname} declares DCAP_CREATECOPY but doc does not!" + else: + if not drv.GetMetadataItem(gdal.DCAP_CREATE): + if shortname == "JP2MrSID": + continue # build dependent + assert ( + "supports_createcopy" not in doc_caps + ), f"Driver {shortname} does not declare DCAP_CREATECOPY but doc does!" From 84faace62505c545963b3a8e803f80312f0089ab Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 9 Mar 2024 16:48:43 +0100 Subject: [PATCH 023/163] CI: run scripts/check_doc.py --- .github/workflows/cmake_builds.yml | 1 + 1 file changed, 1 insertion(+) 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. From bd9f5258173be38122b1d6956e3f417cde01e9f6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 10 Mar 2024 00:54:12 +0100 Subject: [PATCH 024/163] netCDF vector write: make it more robust to empty geometries --- frmts/netcdf/netcdfsgwriterutil.cpp | 42 +++++++++++------------------ frmts/netcdf/netcdfsgwriterutil.h | 21 +++------------ 2 files changed, 18 insertions(+), 45 deletions(-) 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; From c7e2ca8330a2a9fa158b3760b49f2c78275f784a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 10 Mar 2024 00:33:17 +0100 Subject: [PATCH 025/163] GeoJSON reader: accept a {"type": "Polygon","coordinates": []} as a representation of POLYGON EMPTY (fixes duckdb/duckdb_spatial#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was what we exported already, which also happens to validate against http://geojson.org/schema/Polygon.json but on import, we insisted on havin a [[]] coordinates array (which does not validate the schema) --- autotest/ogr/ogr_geojson.py | 12 +++++++++--- ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp | 11 +++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 8fad3db57da7..07eaafa2b5ad 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 diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp index fdf58480c32d..ffacdcdaf83f 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp @@ -2952,7 +2952,6 @@ OGRPolygon *OGRGeoJSONReadPolygon(json_object *poObj, bool bRaw) if (poObjPoints == nullptr) { poPolygon = new OGRPolygon(); - poPolygon->addRingDirectly(new OGRLinearRing()); } else { @@ -2968,11 +2967,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 +2978,10 @@ OGRPolygon *OGRGeoJSONReadPolygon(json_object *poObj, bool bRaw) } } } + else + { + poPolygon = new OGRPolygon(); + } } return poPolygon; From 8fb53c9b2d122e88a1eef8318b0524b725bd97bd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 11 Mar 2024 00:49:46 +0100 Subject: [PATCH 026/163] Internal shapelib: SBNOpenDiskTree(): make it work with node descriptors with non-increasing nBinStart Fixes https://github.com/OSGeo/shapelib/issues/106 / https://github.com/OSGeo/gdal/issues/9430 --- autotest/ogr/ogr_shape_sbn.py | 19 +++ ogr/ogrsf_frmts/shape/sbnsearch.c | 205 +++++++++++++++++++++++++----- 2 files changed, 193 insertions(+), 31 deletions(-) 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/ogr/ogrsf_frmts/shape/sbnsearch.c b/ogr/ogrsf_frmts/shape/sbnsearch.c index be080210d7a8..34ea56350d2e 100644 --- a/ogr/ogrsf_frmts/shape/sbnsearch.c +++ b/ogr/ogrsf_frmts/shape/sbnsearch.c @@ -97,6 +97,24 @@ typedef struct #endif } SearchStruct; +/* Associates a node id with the index of its first bin */ +typedef struct +{ + int nNodeId; + int nBinStart; +} SBNNodeIdBinStartPair; + +/************************************************************************/ +/* SBNCompareNodeIdBinStartPairs() */ +/************************************************************************/ + +/* helper for qsort, to sort SBNNodeIdBinStartPair by increasing nBinStart */ +static int SBNCompareNodeIdBinStartPairs(const void *a, const void *b) +{ + return STATIC_CAST(const SBNNodeIdBinStartPair *, a)->nBinStart - + STATIC_CAST(const SBNNodeIdBinStartPair *, b)->nBinStart; +} + /************************************************************************/ /* SBNOpenDiskTree() */ /************************************************************************/ @@ -254,6 +272,21 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, hSBN->pasNodeDescriptor = pasNodeDescriptor; + SBNNodeIdBinStartPair *pasNodeIdBinStartPairs = + STATIC_CAST(SBNNodeIdBinStartPair *, + malloc(nNodeDescCount * sizeof(SBNNodeIdBinStartPair))); + if (pasNodeIdBinStartPairs == SHPLIB_NULLPTR) + { + free(pabyData); + hSBN->sHooks.Error("Out of memory error"); + SBNCloseDiskTree(hSBN); + return SHPLIB_NULLPTR; + } + +#ifdef ENABLE_SBN_SANITY_CHECKS + int nShapeCountAcc = 0; +#endif + int nEntriesInNodeIdBinStartPairs = 0; for (int i = 0; i < nNodeDescCount; i++) { /* -------------------------------------------------------------------- */ @@ -261,45 +294,121 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, /* described it, and the number of shapes in this first bin and */ /* the following bins (in the relevant case). */ /* -------------------------------------------------------------------- */ - int nBinStart = READ_MSB_INT(pabyData + 8 * i); - int nNodeShapeCount = READ_MSB_INT(pabyData + 8 * i + 4); + const int nBinStart = READ_MSB_INT(pabyData + 8 * i); + const int nNodeShapeCount = READ_MSB_INT(pabyData + 8 * i + 4); pasNodeDescriptor[i].nBinStart = nBinStart > 0 ? nBinStart : 0; pasNodeDescriptor[i].nShapeCount = nNodeShapeCount; +#ifdef DEBUG_SBN + fprintf(stderr, "node[%d], nBinStart=%d, nShapeCount=%d\n", i, + nBinStart, nNodeShapeCount); +#endif + if ((nBinStart > 0 && nNodeShapeCount == 0) || nNodeShapeCount < 0 || nNodeShapeCount > nShapeCount) { hSBN->sHooks.Error("Inconsistent shape count in bin"); + free(pabyData); + free(pasNodeIdBinStartPairs); SBNCloseDiskTree(hSBN); return SHPLIB_NULLPTR; } + +#ifdef ENABLE_SBN_SANITY_CHECKS + if (nShapeCountAcc > nShapeCount - nNodeShapeCount) + { + hSBN->sHooks.Error("Inconsistent shape count in bin"); + free(pabyData); + free(pasNodeIdBinStartPairs); + SBNCloseDiskTree(hSBN); + return SHPLIB_NULLPTR; + } + nShapeCountAcc += nNodeShapeCount; +#endif + + if (nBinStart > 0) + { + pasNodeIdBinStartPairs[nEntriesInNodeIdBinStartPairs].nNodeId = i; + pasNodeIdBinStartPairs[nEntriesInNodeIdBinStartPairs].nBinStart = + nBinStart; + ++nEntriesInNodeIdBinStartPairs; + } } free(pabyData); /* pabyData = SHPLIB_NULLPTR; */ - /* Locate first non-empty node */ - int nCurNode = 0; - while (nCurNode < nMaxNodes && pasNodeDescriptor[nCurNode].nBinStart <= 0) - nCurNode++; - - if (nCurNode >= nMaxNodes) + if (nEntriesInNodeIdBinStartPairs == 0) { + free(pasNodeIdBinStartPairs); hSBN->sHooks.Error("All nodes are empty"); SBNCloseDiskTree(hSBN); return SHPLIB_NULLPTR; } - pasNodeDescriptor[nCurNode].nBinOffset = - STATIC_CAST(int, hSBN->sHooks.FTell(hSBN->fpSBN)); +#ifdef ENABLE_SBN_SANITY_CHECKS + if (nShapeCountAcc != nShapeCount) + { + /* Not totally sure if the above condition is always true */ + /* Not enabled by default, as non-needed for the good working */ + /* of our code. */ + free(pasNodeIdBinStartPairs); + char szMessage[128]; + snprintf(szMessage, sizeof(szMessage), + "Inconsistent shape count read in .sbn header (%d) vs total " + "of shapes over nodes (%d)", + nShapeCount, nShapeCountAcc); + hSBN->sHooks.Error(szMessage); + SBNCloseDiskTree(hSBN); + return SHPLIB_NULLPTR; + } +#endif - /* Compute the index of the next non empty node. */ - int nNextNonEmptyNode = nCurNode + 1; - while (nNextNonEmptyNode < nMaxNodes && - pasNodeDescriptor[nNextNonEmptyNode].nBinStart <= 0) - nNextNonEmptyNode++; + /* Sort node descriptors by increasing nBinStart */ + /* In most cases, the node descriptors have already an increasing nBinStart, + * but not for https://github.com/OSGeo/gdal/issues/9430 */ + qsort(pasNodeIdBinStartPairs, nEntriesInNodeIdBinStartPairs, + sizeof(SBNNodeIdBinStartPair), SBNCompareNodeIdBinStartPairs); + + /* Consistency check: the first referenced nBinStart should be 2. */ + if (pasNodeIdBinStartPairs[0].nBinStart != 2) + { + char szMessage[128]; + snprintf(szMessage, sizeof(szMessage), + "First referenced bin (by node %d) should be 2, but %d found", + pasNodeIdBinStartPairs[0].nNodeId, + pasNodeIdBinStartPairs[0].nBinStart); + hSBN->sHooks.Error(szMessage); + SBNCloseDiskTree(hSBN); + free(pasNodeIdBinStartPairs); + return SHPLIB_NULLPTR; + } + + /* And referenced nBinStart should be all distinct. */ + for (int i = 1; i < nEntriesInNodeIdBinStartPairs; ++i) + { + if (pasNodeIdBinStartPairs[i].nBinStart == + pasNodeIdBinStartPairs[i - 1].nBinStart) + { + char szMessage[128]; + snprintf(szMessage, sizeof(szMessage), + "Node %d and %d have the same nBinStart=%d", + pasNodeIdBinStartPairs[i - 1].nNodeId, + pasNodeIdBinStartPairs[i].nNodeId, + pasNodeIdBinStartPairs[i].nBinStart); + hSBN->sHooks.Error(szMessage); + SBNCloseDiskTree(hSBN); + free(pasNodeIdBinStartPairs); + return SHPLIB_NULLPTR; + } + } int nExpectedBinId = 1; + int nIdxInNodeBinPair = 0; + int nCurNode = pasNodeIdBinStartPairs[nIdxInNodeBinPair].nNodeId; + + pasNodeDescriptor[nCurNode].nBinOffset = + STATIC_CAST(int, hSBN->sHooks.FTell(hSBN->fpSBN)); /* -------------------------------------------------------------------- */ /* Traverse bins to compute the offset of the first bin of each */ @@ -316,10 +425,22 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, int nBinSize = READ_MSB_INT(abyBinHeader + 4); nBinSize *= 2; /* 16-bit words */ +#ifdef DEBUG_SBN + fprintf(stderr, "bin id=%d, bin size (in features) = %d\n", nBinId, + nBinSize / 8); +#endif + if (nBinId != nExpectedBinId) { - hSBN->sHooks.Error("Unexpected bin id"); + char szMessage[128]; + snprintf(szMessage, sizeof(szMessage), + "Unexpected bin id at bin starting at offset %d. Got %d, " + "expected %d", + STATIC_CAST(int, hSBN->sHooks.FTell(hSBN->fpSBN)) - 8, + nBinId, nExpectedBinId); + hSBN->sHooks.Error(szMessage); SBNCloseDiskTree(hSBN); + free(pasNodeIdBinStartPairs); return SHPLIB_NULLPTR; } @@ -327,23 +448,24 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, /* If there are more, then they are located in continuous bins */ if ((nBinSize % 8) != 0 || nBinSize <= 0 || nBinSize > 100 * 8) { - hSBN->sHooks.Error("Unexpected bin size"); + char szMessage[128]; + snprintf(szMessage, sizeof(szMessage), + "Unexpected bin size at bin starting at offset %d. Got %d", + STATIC_CAST(int, hSBN->sHooks.FTell(hSBN->fpSBN)) - 8, + nBinSize); + hSBN->sHooks.Error(szMessage); SBNCloseDiskTree(hSBN); + free(pasNodeIdBinStartPairs); return SHPLIB_NULLPTR; } - if (nNextNonEmptyNode < nMaxNodes && - nBinId == pasNodeDescriptor[nNextNonEmptyNode].nBinStart) + if (nIdxInNodeBinPair + 1 < nEntriesInNodeIdBinStartPairs && + nBinId == pasNodeIdBinStartPairs[nIdxInNodeBinPair + 1].nBinStart) { - nCurNode = nNextNonEmptyNode; + ++nIdxInNodeBinPair; + nCurNode = pasNodeIdBinStartPairs[nIdxInNodeBinPair].nNodeId; pasNodeDescriptor[nCurNode].nBinOffset = STATIC_CAST(int, hSBN->sHooks.FTell(hSBN->fpSBN)) - 8; - - /* Compute the index of the next non empty node. */ - nNextNonEmptyNode = nCurNode + 1; - while (nNextNonEmptyNode < nMaxNodes && - pasNodeDescriptor[nNextNonEmptyNode].nBinStart <= 0) - nNextNonEmptyNode++; } pasNodeDescriptor[nCurNode].nBinCount++; @@ -352,6 +474,17 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, hSBN->sHooks.FSeek(hSBN->fpSBN, nBinSize, SEEK_CUR); } + if (nIdxInNodeBinPair + 1 != nEntriesInNodeIdBinStartPairs) + { + hSBN->sHooks.Error("Could not determine nBinOffset / nBinCount for all " + "non-empty nodes."); + SBNCloseDiskTree(hSBN); + free(pasNodeIdBinStartPairs); + return SHPLIB_NULLPTR; + } + + free(pasNodeIdBinStartPairs); + return hSBN; } @@ -537,7 +670,13 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, { free(psNode->pabyShapeDesc); psNode->pabyShapeDesc = SHPLIB_NULLPTR; - hSBN->sHooks.Error("Inconsistent shape count for bin"); + char szMessage[128]; + snprintf( + szMessage, sizeof(szMessage), + "Inconsistent shape count for bin idx=%d of node %d. " + "nShapeCountAcc=(%d) + nShapes=(%d) > nShapeCount(=%d)", + i, nNodeId, nShapeCountAcc, nShapes, psNode->nShapeCount); + hSBN->sHooks.Error(szMessage); return false; } @@ -583,7 +722,7 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, if (!psNode->bBBoxInit) { /* clang-format off */ -#ifdef sanity_checks +#ifdef ENABLE_SBN_SANITY_CHECKS /* -------------------------------------------------------------------- */ /* Those tests only check that the shape bounding box in the bin */ /* are consistent (self-consistent and consistent with the node */ @@ -639,7 +778,12 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, { free(psNode->pabyShapeDesc); psNode->pabyShapeDesc = SHPLIB_NULLPTR; - hSBN->sHooks.Error("Inconsistent shape count for bin"); + char szMessage[96]; + snprintf( + szMessage, sizeof(szMessage), + "Inconsistent shape count for node %d. Got %d, expected %d", + nNodeId, nShapeCountAcc, psNode->nShapeCount); + hSBN->sHooks.Error(szMessage); return false; } @@ -701,8 +845,7 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, /* helper for qsort */ static int compare_ints(const void *a, const void *b) { - return *REINTERPRET_CAST(const int *, a) - - *REINTERPRET_CAST(const int *, b); + return *STATIC_CAST(const int *, a) - *STATIC_CAST(const int *, b); } /************************************************************************/ From 167385ac58a2c92b01e18cc57c2fa817dee8d366 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 11 Mar 2024 00:49:22 +0100 Subject: [PATCH 027/163] sbnsearch.c: avoid potential integer overflow on corrupted bin size --- ogr/ogrsf_frmts/shape/sbnsearch.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ogr/ogrsf_frmts/shape/sbnsearch.c b/ogr/ogrsf_frmts/shape/sbnsearch.c index 34ea56350d2e..2fabe76963d6 100644 --- a/ogr/ogrsf_frmts/shape/sbnsearch.c +++ b/ogr/ogrsf_frmts/shape/sbnsearch.c @@ -422,12 +422,11 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, nExpectedBinId++; const int nBinId = READ_MSB_INT(abyBinHeader); - int nBinSize = READ_MSB_INT(abyBinHeader + 4); - nBinSize *= 2; /* 16-bit words */ + const int nBinSize = READ_MSB_INT(abyBinHeader + 4); /* 16-bit words */ #ifdef DEBUG_SBN fprintf(stderr, "bin id=%d, bin size (in features) = %d\n", nBinId, - nBinSize / 8); + nBinSize / 4); #endif if (nBinId != nExpectedBinId) @@ -446,7 +445,7 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, /* Bins are always limited to 100 features */ /* If there are more, then they are located in continuous bins */ - if ((nBinSize % 8) != 0 || nBinSize <= 0 || nBinSize > 100 * 8) + if ((nBinSize % 4) != 0 || nBinSize <= 0 || nBinSize > 100 * 4) { char szMessage[128]; snprintf(szMessage, sizeof(szMessage), @@ -471,7 +470,7 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, pasNodeDescriptor[nCurNode].nBinCount++; /* Skip shape description */ - hSBN->sHooks.FSeek(hSBN->fpSBN, nBinSize, SEEK_CUR); + hSBN->sHooks.FSeek(hSBN->fpSBN, nBinSize * sizeof(uint16_t), SEEK_CUR); } if (nIdxInNodeBinPair + 1 != nEntriesInNodeIdBinStartPairs) @@ -652,13 +651,12 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, return false; } - int nBinSize = READ_MSB_INT(abyBinHeader + 4); - nBinSize *= 2; /* 16-bit words */ - - int nShapes = nBinSize / 8; + /* 16-bit words */ + const int nBinSize = READ_MSB_INT(abyBinHeader + 4); + const int nShapes = nBinSize / 4; /* Bins are always limited to 100 features */ - if ((nBinSize % 8) != 0 || nShapes <= 0 || nShapes > 100) + if ((nBinSize % 4) != 0 || nShapes <= 0 || nShapes > 100) { hSBN->sHooks.Error("Unexpected bin size"); free(psNode->pabyShapeDesc); @@ -692,9 +690,10 @@ static bool SBNSearchDiskInternal(SearchStruct *psSearch, int nDepth, } #ifdef DEBUG_IO - psSearch->nBytesRead += nBinSize; + psSearch->nBytesRead += nBinSize * sizeof(uint16_t); #endif - if (hSBN->sHooks.FRead(pabyBinShape, nBinSize, 1, hSBN->fpSBN) != 1) + if (hSBN->sHooks.FRead(pabyBinShape, nBinSize * sizeof(uint16_t), 1, + hSBN->fpSBN) != 1) { hSBN->sHooks.Error("I/O error"); free(psNode->pabyShapeDesc); From 0d3e5fda6dcf50211363281e629fd79681711bb7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 11 Mar 2024 02:36:49 +0100 Subject: [PATCH 028/163] sbnsearch.c: avoid potential integer overflow on corrupted nNodeDescSize --- ogr/ogrsf_frmts/shape/sbnsearch.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ogr/ogrsf_frmts/shape/sbnsearch.c b/ogr/ogrsf_frmts/shape/sbnsearch.c index 2fabe76963d6..5fdadeb88ca4 100644 --- a/ogr/ogrsf_frmts/shape/sbnsearch.c +++ b/ogr/ogrsf_frmts/shape/sbnsearch.c @@ -227,26 +227,27 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, /* There are at most (2^nMaxDepth) - 1, but all are not necessary */ /* described. Non described nodes are empty. */ /* -------------------------------------------------------------------- */ - int nNodeDescSize = READ_MSB_INT(abyHeader + 104); - nNodeDescSize *= 2; /* 16-bit words */ + const int nNodeDescSize = READ_MSB_INT(abyHeader + 104); /* 16-bit words */ /* each bin descriptor is made of 2 ints */ - const int nNodeDescCount = nNodeDescSize / 8; + const int nNodeDescCount = nNodeDescSize / 4; - if ((nNodeDescSize % 8) != 0 || nNodeDescCount < 0 || + if ((nNodeDescSize % 4) != 0 || nNodeDescCount < 0 || nNodeDescCount > nMaxNodes) { char szErrorMsg[64]; snprintf(szErrorMsg, sizeof(szErrorMsg), - "Invalid node descriptor size in .sbn : %d", nNodeDescSize); + "Invalid node descriptor size in .sbn : %d", + nNodeDescSize * STATIC_CAST(int, sizeof(uint16_t))); hSBN->sHooks.Error(szErrorMsg); SBNCloseDiskTree(hSBN); return SHPLIB_NULLPTR; } + const int nNodeDescSizeBytes = nNodeDescCount * 2 * 4; /* coverity[tainted_data] */ unsigned char *pabyData = - STATIC_CAST(unsigned char *, malloc(nNodeDescSize)); + STATIC_CAST(unsigned char *, malloc(nNodeDescSizeBytes)); SBNNodeDescriptor *pasNodeDescriptor = STATIC_CAST( SBNNodeDescriptor *, calloc(nMaxNodes, sizeof(SBNNodeDescriptor))); if (pabyData == SHPLIB_NULLPTR || pasNodeDescriptor == SHPLIB_NULLPTR) @@ -261,7 +262,7 @@ SBNSearchHandle SBNOpenDiskTree(const char *pszSBNFilename, /* -------------------------------------------------------------------- */ /* Read node descriptors. */ /* -------------------------------------------------------------------- */ - if (hSBN->sHooks.FRead(pabyData, nNodeDescSize, 1, hSBN->fpSBN) != 1) + if (hSBN->sHooks.FRead(pabyData, nNodeDescSizeBytes, 1, hSBN->fpSBN) != 1) { free(pabyData); free(pasNodeDescriptor); From 81083bd0b31bdd8900a5baf63f8c35b0b6645664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bergstr=C3=B8m=20Aarnseth?= Date: Tue, 12 Mar 2024 00:24:57 +0100 Subject: [PATCH 029/163] SWIG: Exposes additional Dataset methods for the csharp wrapper (fixes #918) (#9398) SWIG: Exposes additional Dataset methods for the csharp wrapper: - GetNextFeature (exposing _ppoBelongingLayer_ as a ref IntPtr and _pdfProgressPct_ as ref double), - GetLayerCount, - GetLayer, - GetLayerByName, - ResetReading --- swig/include/Dataset.i | 60 ++++++++++++++++++--------- swig/include/csharp/gdal_csharp.i | 2 + swig/include/csharp/typemaps_csharp.i | 16 +++++++ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/swig/include/Dataset.i b/swig/include/Dataset.i index fe4945f95f66..f7ddfbbdabbb 100644 --- a/swig/include/Dataset.i +++ b/swig/include/Dataset.i @@ -805,32 +805,12 @@ CPLErr AdviseRead( int xoff, int yoff, int xsize, int ysize, return GDALDatasetDeleteLayer(self, index); } - int GetLayerCount() { - return GDALDatasetGetLayerCount(self); - } - bool IsLayerPrivate( int index ) { return GDALDatasetIsLayerPrivate(self, index); } -#ifdef SWIGJAVA - OGRLayerShadow *GetLayerByIndex( int index ) { -#else - OGRLayerShadow *GetLayerByIndex( int index=0) { -#endif - OGRLayerShadow* layer = (OGRLayerShadow*) GDALDatasetGetLayer(self, index); - return layer; - } - OGRLayerShadow *GetLayerByName( const char* layer_name) { - OGRLayerShadow* layer = (OGRLayerShadow*) GDALDatasetGetLayerByName(self, layer_name); - return layer; - } - void ResetReading() - { - GDALDatasetResetReading( self ); - } #ifdef SWIGPYTHON %newobject GetNextFeature; @@ -890,6 +870,46 @@ CPLErr AdviseRead( int xoff, int yoff, int xsize, int ysize, #endif /* defined(SWIGPYTHON) || defined(SWIGJAVA) */ +#ifdef SWIGJAVA + OGRLayerShadow *GetLayerByIndex( int index ) { +#elif SWIGPYTHON + OGRLayerShadow *GetLayerByIndex( int index=0) { +#else + OGRLayerShadow *GetLayer( int index ) { +#endif + OGRLayerShadow* layer = (OGRLayerShadow*) GDALDatasetGetLayer(self, index); + return layer; + } + + OGRLayerShadow *GetLayerByName(const char* layer_name) { + OGRLayerShadow* layer = (OGRLayerShadow*) GDALDatasetGetLayerByName(self, layer_name); + return layer; + } + + void ResetReading() + { + GDALDatasetResetReading(self); + } + + int GetLayerCount() { + return GDALDatasetGetLayerCount(self); + } + +#ifdef SWIGCSHARP + + %newobject GetNextFeature; + OGRFeatureShadow *GetNextFeature( OGRLayerShadow** ppoBelongingLayer = NULL, + double* pdfProgressPct = NULL, + GDALProgressFunc callback = NULL, + void* callback_data=NULL ) + { + return GDALDatasetGetNextFeature( self, ppoBelongingLayer, pdfProgressPct, + callback, callback_data ); + } + + +#endif + OGRErr AbortSQL() { return GDALDatasetAbortSQL(self); } diff --git a/swig/include/csharp/gdal_csharp.i b/swig/include/csharp/gdal_csharp.i index 23b5abe76a98..39a4e0587662 100644 --- a/swig/include/csharp/gdal_csharp.i +++ b/swig/include/csharp/gdal_csharp.i @@ -86,6 +86,8 @@ typedef struct } GDALRasterIOExtraArg; DEFINE_EXTERNAL_CLASS(OGRLayerShadow, OSGeo.OGR.Layer) +DEFINE_EXTERNAL_CLASS(OGRFeatureShadow, OSGeo.OGR.Feature) + %define %rasterio_functions(GDALTYPE,CSTYPE) public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, CSTYPE[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace) { diff --git a/swig/include/csharp/typemaps_csharp.i b/swig/include/csharp/typemaps_csharp.i index ef706e6de018..509db618aee4 100644 --- a/swig/include/csharp/typemaps_csharp.i +++ b/swig/include/csharp/typemaps_csharp.i @@ -599,3 +599,19 @@ OPTIONAL_POD(int, int); %typemap(imtype) (void* callback_data) "string" %typemap(cstype) (void* callback_data) "string" %typemap(csin) (void* callback_data) "$csinput" + + +/****************************************************************************** + * GDALGetNextFeature typemaps * + *****************************************************************************/ + +%apply (double *defaultval) {double* pdfProgressPct}; + +%typemap(imtype) (OGRLayerShadow **ppoBelongingLayer) "ref IntPtr" +%typemap(cstype) (OGRLayerShadow **ppoBelongingLayer) "ref IntPtr" +%typemap(csin) (OGRLayerShadow **ppoBelongingLayer) "ref $csinput" + +/****************************************************************************** + * GDALGetLayerByName typemaps * + *****************************************************************************/ +%apply ( const char *utf8_path ) { const char* layer_name }; From a8846d10a87435b7a9966b784ed3ca10e8382274 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 12 Mar 2024 11:49:29 +0100 Subject: [PATCH 030/163] gdal_argparse.py: use the default ArgumentParser formatter_class if None is provided --- swig/python/gdal-utils/osgeo_utils/auxiliary/gdal_argparse.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swig/python/gdal-utils/osgeo_utils/auxiliary/gdal_argparse.py b/swig/python/gdal-utils/osgeo_utils/auxiliary/gdal_argparse.py index 15948ae27547..5e11f32b8652 100644 --- a/swig/python/gdal-utils/osgeo_utils/auxiliary/gdal_argparse.py +++ b/swig/python/gdal-utils/osgeo_utils/auxiliary/gdal_argparse.py @@ -62,6 +62,9 @@ def __init__( formatter_class = argparse.RawDescriptionHelpFormatter description = f'{title}\n{"-"*(2+len(title))}\n{description}' + if formatter_class is None: + formatter_class = argparse.HelpFormatter + super().__init__( fromfile_prefix_chars=fromfile_prefix_chars, description=description, From c3faafa94452e602745a83b49a4141112d4a6b70 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 12 Mar 2024 17:38:20 +0100 Subject: [PATCH 031/163] /vsis3/: include region to build s3.{region}.amazonaws.com host name (fixes #9449) --- autotest/gcore/vsis3.py | 2 +- port/cpl_aws.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/autotest/gcore/vsis3.py b/autotest/gcore/vsis3.py index cacec03b5b45..f799185fce48 100755 --- a/autotest/gcore/vsis3.py +++ b/autotest/gcore/vsis3.py @@ -143,7 +143,7 @@ def test_vsis3_no_sign_request(aws_test_config_as_config_options_or_credentials) bucket = "noaa-goes16" obj = "ABI-L1b-RadC/2022/001/00/OR_ABI-L1b-RadC-M6C01_G16_s20220010001173_e20220010003546_c20220010003587.nc" vsis3_path = "/vsis3/" + bucket + "/" + obj - url = "https://" + bucket + ".s3.amazonaws.com/" + obj + url = "https://" + bucket + ".s3.us-east-1.amazonaws.com/" + obj with gdaltest.config_options( options, thread_local=False diff --git a/port/cpl_aws.cpp b/port/cpl_aws.cpp index 0f4606a5c8ad..550a2b8df1c7 100644 --- a/port/cpl_aws.cpp +++ b/port/cpl_aws.cpp @@ -1730,8 +1730,12 @@ VSIS3HandleHelper *VSIS3HandleHelper::BuildFromURI(const char *pszURI, osRegion = osDefaultRegion; } - const std::string osEndpoint = VSIGetPathSpecificOption( + std::string osEndpoint = VSIGetPathSpecificOption( osPathForOption.c_str(), "AWS_S3_ENDPOINT", "s3.amazonaws.com"); + if (!osRegion.empty() && osEndpoint == "s3.amazonaws.com") + { + osEndpoint = "s3." + osRegion + ".amazonaws.com"; + } const std::string osRequestPayer = VSIGetPathSpecificOption( osPathForOption.c_str(), "AWS_REQUEST_PAYER", ""); std::string osBucket; From c24b95cc394f7f1ac929c055717d70d8599a120b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 12 Mar 2024 19:56:58 +0100 Subject: [PATCH 032/163] swq_select::expand_wildcard(): avoid unsigned integer overflow, and coverity warning about loop taken once --- ogr/swq_select.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ogr/swq_select.cpp b/ogr/swq_select.cpp index bec4a345b619..9d63d199b00c 100644 --- a/ogr/swq_select.cpp +++ b/ogr/swq_select.cpp @@ -866,13 +866,14 @@ CPLErr swq_select::expand_wildcard(swq_field_list *field_list, column_defs.insert(pos, expanded_columns.begin(), expanded_columns.end()); - columns_added += static_cast(expanded_columns.size() - 1); + columns_added += static_cast(expanded_columns.size()) - 1; - auto it = m_exclude_fields.find(isrc); + const auto it = m_exclude_fields.find(isrc); if (it != m_exclude_fields.end()) { - for (const auto &field : it->second) + if (!it->second.empty()) { + const auto &field = it->second.front(); CPLError( CE_Failure, CPLE_AppDefined, "Field %s specified in EXCEPT/EXCLUDE expression not found", From 6703d3071de7155d320a39a580f27230428dcaca Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 13 Mar 2024 13:42:24 +0100 Subject: [PATCH 033/163] requirements.txt: add pin for importlib-resources to avoid issue with pytest with jsonschema --- autotest/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) 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 From f7a5ab201f96176a756ebe8af43a81f2e709038f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 13 Mar 2024 22:21:40 +0100 Subject: [PATCH 034/163] OGRLayer::GetArrowSchema(): remove potential unaligned int32_t writes --- ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp | 62 ++++++++++++++--------- 1 file changed, 38 insertions(+), 24 deletions(-) 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); } } From 9a6b633d70b32de363f662d8bba23dd4b67a7492 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 00:30:52 +0100 Subject: [PATCH 035/163] OpenFileGDB: avoid issue with -fno-sanitize-recover=unsigned-integer-overflow with recent clang --- ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt | 21 +++ .../ogropenfilegdb_generate_uuid.cpp | 134 ++++++++++++++++++ .../ogropenfilegdbdatasource_write.cpp | 104 -------------- 3 files changed, 155 insertions(+), 104 deletions(-) create mode 100644 ogr/ogrsf_frmts/openfilegdb/ogropenfilegdb_generate_uuid.cpp diff --git a/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt b/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt index 49d1074dc103..e6f2be66e593 100644 --- a/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt +++ b/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt @@ -11,6 +11,7 @@ add_gdal_driver( ogr_openfilegdb.h ogropenfilegdbdatasource.cpp ogropenfilegdbdatasource_write.cpp + ogropenfilegdb_generate_uuid.cpp ogropenfilegdbdriver.cpp ogropenfilegdblayer.cpp ogropenfilegdblayer_write.cpp @@ -27,6 +28,26 @@ endif() # because of use of GDAL_RELEASE_NAME set_property(SOURCE filegdbtable_write.cpp PROPERTY SKIP_UNITY_BUILD_INCLUSION ON) +if (CMAKE_CXX_FLAGS MATCHES "-fno-sanitize-recover=unsigned-integer-overflow") + # Remove '-fno-sanitize-recover=unsigned-integer-overflow' from global flags + string(REPLACE "-fno-sanitize-recover=unsigned-integer-overflow" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + + define_property( + SOURCE + PROPERTY COMPILE_FLAGS + INHERITED + BRIEF_DOCS "brief-doc" + FULL_DOCS "full-doc" + ) + + # Add '-fno-sanitize-recover=unsigned-integer-overflow' for directory + set_directory_properties(PROPERTIES COMPILE_FLAGS "-fno-sanitize-recover=unsigned-integer-overflow") + + # Remove '-fno-sanitize-recover=unsigned-integer-overflow' from source file + set_source_files_properties("ogropenfilegdb_generate_uuid.cpp" PROPERTIES COMPILE_FLAGS "") +endif() + + gdal_standard_includes(ogr_OpenFileGDB) target_include_directories(ogr_OpenFileGDB PRIVATE $) diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdb_generate_uuid.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdb_generate_uuid.cpp new file mode 100644 index 000000000000..0c6a23f0a0ad --- /dev/null +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdb_generate_uuid.cpp @@ -0,0 +1,134 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: Implements Open FileGDB OGR driver. + * Author: Even Rouault, + * + ****************************************************************************** + * Copyright (c) 2022, 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 "cpl_port.h" +#include "ogr_openfilegdb.h" + +#include +#include + +/************************************************************************/ +/* CPLGettimeofday() */ +/************************************************************************/ + +#if defined(_WIN32) && !defined(__CYGWIN__) +#include + +namespace +{ +struct CPLTimeVal +{ + time_t tv_sec; /* seconds */ + long tv_usec; /* and microseconds */ +}; +} // namespace + +static int CPLGettimeofday(struct CPLTimeVal *tp, void * /* timezonep*/) +{ + struct _timeb theTime; + + _ftime(&theTime); + tp->tv_sec = static_cast(theTime.time); + tp->tv_usec = theTime.millitm * 1000; + return 0; +} +#else +#include /* for gettimeofday() */ +#define CPLTimeVal timeval +#define CPLGettimeofday(t, u) gettimeofday(t, u) +#endif + +/***********************************************************************/ +/* OFGDBGenerateUUID() */ +/***********************************************************************/ + +// Probably not the best UUID generator ever. One issue is that mt19937 +// uses only a 32-bit seed. +CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW +std::string OFGDBGenerateUUID() +{ + struct CPLTimeVal tv; + memset(&tv, 0, sizeof(tv)); + static uint32_t nCounter = 0; + const bool bReproducibleUUID = + CPLTestBool(CPLGetConfigOption("OPENFILEGDB_REPRODUCIBLE_UUID", "NO")); + + std::stringstream ss; + + { + if (!bReproducibleUUID) + CPLGettimeofday(&tv, nullptr); + std::mt19937 gen(++nCounter + + (bReproducibleUUID + ? 0 + : static_cast(tv.tv_sec ^ tv.tv_usec))); + std::uniform_int_distribution<> dis(0, 15); + + ss << "{"; + ss << std::hex; + for (int i = 0; i < 8; i++) + { + ss << dis(gen); + } + ss << "-"; + for (int i = 0; i < 4; i++) + { + ss << dis(gen); + } + ss << "-4"; + for (int i = 0; i < 3; i++) + { + ss << dis(gen); + } + } + + { + if (!bReproducibleUUID) + CPLGettimeofday(&tv, nullptr); + std::mt19937 gen(++nCounter + + (bReproducibleUUID + ? 0 + : static_cast(tv.tv_sec ^ tv.tv_usec))); + std::uniform_int_distribution<> dis(0, 15); + std::uniform_int_distribution<> dis2(8, 11); + + ss << "-"; + ss << dis2(gen); + for (int i = 0; i < 3; i++) + { + ss << dis(gen); + } + ss << "-"; + for (int i = 0; i < 12; i++) + { + ss << dis(gen); + }; + ss << "}"; + return ss.str(); + } +} diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index c7e7fc7eacf0..27370a22c1a1 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -51,110 +51,6 @@ #include "filegdb_fielddomain.h" #include "filegdb_relationship.h" -#include -#include - -/************************************************************************/ -/* CPLGettimeofday() */ -/************************************************************************/ - -#if defined(_WIN32) && !defined(__CYGWIN__) -#include - -namespace -{ -struct CPLTimeVal -{ - time_t tv_sec; /* seconds */ - long tv_usec; /* and microseconds */ -}; -} // namespace - -static int CPLGettimeofday(struct CPLTimeVal *tp, void * /* timezonep*/) -{ - struct _timeb theTime; - - _ftime(&theTime); - tp->tv_sec = static_cast(theTime.time); - tp->tv_usec = theTime.millitm * 1000; - return 0; -} -#else -#include /* for gettimeofday() */ -#define CPLTimeVal timeval -#define CPLGettimeofday(t, u) gettimeofday(t, u) -#endif - -/***********************************************************************/ -/* OFGDBGenerateUUID() */ -/***********************************************************************/ - -// Probably not the best UUID generator ever. One issue is that mt19937 -// uses only a 32-bit seed. -CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW -std::string OFGDBGenerateUUID() -{ - struct CPLTimeVal tv; - memset(&tv, 0, sizeof(tv)); - static uint32_t nCounter = 0; - const bool bReproducibleUUID = - CPLTestBool(CPLGetConfigOption("OPENFILEGDB_REPRODUCIBLE_UUID", "NO")); - - std::stringstream ss; - - { - if (!bReproducibleUUID) - CPLGettimeofday(&tv, nullptr); - std::mt19937 gen(++nCounter + - (bReproducibleUUID - ? 0 - : static_cast(tv.tv_sec ^ tv.tv_usec))); - std::uniform_int_distribution<> dis(0, 15); - - ss << "{"; - ss << std::hex; - for (int i = 0; i < 8; i++) - { - ss << dis(gen); - } - ss << "-"; - for (int i = 0; i < 4; i++) - { - ss << dis(gen); - } - ss << "-4"; - for (int i = 0; i < 3; i++) - { - ss << dis(gen); - } - } - - { - if (!bReproducibleUUID) - CPLGettimeofday(&tv, nullptr); - std::mt19937 gen(++nCounter + - (bReproducibleUUID - ? 0 - : static_cast(tv.tv_sec ^ tv.tv_usec))); - std::uniform_int_distribution<> dis(0, 15); - std::uniform_int_distribution<> dis2(8, 11); - - ss << "-"; - ss << dis2(gen); - for (int i = 0; i < 3; i++) - { - ss << dis(gen); - } - ss << "-"; - for (int i = 0; i < 12; i++) - { - ss << dis(gen); - }; - ss << "}"; - return ss.str(); - } -} - /***********************************************************************/ /* GetExistingSpatialRef() */ /***********************************************************************/ From 36858a629ee508464428e2e7474857f0f0cee568 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 01:38:39 +0100 Subject: [PATCH 036/163] OGCAPI: fix potential use-after-free --- frmts/ogcapi/gdalogcapidataset.cpp | 1 + 1 file changed, 1 insertion(+) 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(); } From 2e7f81e95ddb328dbc69f37ebc392a240abe7770 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 01:27:01 +0100 Subject: [PATCH 037/163] CI: update ASAN job to Ubuntu 22.04 --- .github/workflows/asan/build.sh | 5 +++-- .github/workflows/asan/test.sh | 6 ++++-- .github/workflows/linux_build.yml | 4 ++-- .github/workflows/ubuntu_22.04/Dockerfile.ci | 1 + autotest/gcore/tiff_write.py | 3 +++ autotest/lsan_suppressions.txt | 1 + autotest/pyscripts/test_gdal_calc.py | 7 +++++++ autotest/pyscripts/test_gdal_edit.py | 4 ++++ autotest/pyscripts/test_ogrmerge.py | 4 ++++ 9 files changed, 29 insertions(+), 6 deletions(-) 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..d8cd216564e4 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,7 @@ 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 \ -print \ -exec ./pytest_wrapper.sh {} \; \ | tee ./test-output.txt diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index 64a90f5c5db4..a3fc24a6d6d9 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -85,10 +85,10 @@ jobs: build_script: build.sh test_script: test.sh - - 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 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/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/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/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_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, From 79282308245b5014f6cd95d0aea7cfbae9256839 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 01:54:37 +0100 Subject: [PATCH 038/163] CI: disable ASAN config as too flaky --- .github/workflows/linux_build.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index a3fc24a6d6d9..c23dfd8aee19 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -85,12 +85,17 @@ jobs: build_script: build.sh test_script: test.sh - - name: Ubuntu 22.04, clang ASAN - id: asan - travis_branch: sanitize - container: ubuntu_22.04 - build_script: build.sh - test_script: test.sh + # Disabled because too many occurences of "AddressSanitizer:DEADLYSIGNAL" on CI + # whereas it works locally. Issue seems to have appears on March 13th or 14th + # Initially was on 20.04, but the upgrade to 22.04 does not improve + # OK build: https://github.com/OSGeo/gdal/actions/runs/8254308146/job/22578169924 + # KO build: https://github.com/OSGeo/gdal/actions/runs/8264809407/job/22609174350 + #- name: Ubuntu 22.04, clang ASAN + # id: asan + # travis_branch: sanitize + # container: ubuntu_22.04 + # build_script: build.sh + # test_script: test.sh - name: Ubuntu 20.04, gcc id: ubuntu_20.04 From 709169d6e8ec11ded4cbbbb9430be812b20f05e2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 02:14:19 +0100 Subject: [PATCH 039/163] GDALResampleChunk_AverageOrRMS_T(): avoid casting value to type that can't hold it (master only) --- gcore/overview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gcore/overview.cpp b/gcore/overview.cpp index 290acb24432e..130d569d904f 100644 --- a/gcore/overview.cpp +++ b/gcore/overview.cpp @@ -1131,10 +1131,10 @@ static CPLErr GDALResampleChunk_AverageOrRMS_T( tNoDataValue = 0; else tNoDataValue = static_cast(dfNoDataValue); - const T tReplacementVal = static_cast( - bHasNoData ? GDALGetNoDataReplacementValue( - poOverview->GetRasterDataType(), dfNoDataValue) - : dfNoDataValue); + const T tReplacementVal = + bHasNoData ? static_cast(GDALGetNoDataReplacementValue( + poOverview->GetRasterDataType(), dfNoDataValue)) + : 0; int nChunkRightXOff = nChunkXOff + nChunkXSize; int nChunkBottomYOff = nChunkYOff + nChunkYSize; From a594ef23f8ac03cbef3256970a4cca57e3ec33c0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 02:39:33 +0100 Subject: [PATCH 040/163] Revert "CI: disable ASAN config as too flaky" This reverts commit 79282308245b5014f6cd95d0aea7cfbae9256839. --- .github/workflows/linux_build.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index c23dfd8aee19..a3fc24a6d6d9 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -85,17 +85,12 @@ jobs: build_script: build.sh test_script: test.sh - # Disabled because too many occurences of "AddressSanitizer:DEADLYSIGNAL" on CI - # whereas it works locally. Issue seems to have appears on March 13th or 14th - # Initially was on 20.04, but the upgrade to 22.04 does not improve - # OK build: https://github.com/OSGeo/gdal/actions/runs/8254308146/job/22578169924 - # KO build: https://github.com/OSGeo/gdal/actions/runs/8264809407/job/22609174350 - #- name: Ubuntu 22.04, clang ASAN - # id: asan - # travis_branch: sanitize - # container: ubuntu_22.04 - # build_script: build.sh - # test_script: test.sh + - name: Ubuntu 22.04, clang ASAN + id: asan + travis_branch: sanitize + container: ubuntu_22.04 + build_script: build.sh + test_script: test.sh - name: Ubuntu 20.04, gcc id: ubuntu_20.04 From bdb9b13775b738ca18192a3747512cdc5d61d833 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 02:43:12 +0100 Subject: [PATCH 041/163] CI: force ASAN to run on 20.04 --- .github/workflows/linux_build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index a3fc24a6d6d9..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,6 +87,7 @@ jobs: before_test_script: services.sh build_script: build.sh test_script: test.sh + os: ubuntu-22.04 - name: Ubuntu 22.04, clang ASAN id: asan @@ -91,6 +95,8 @@ jobs: 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: From fa63fc09f6bcbdcc7f7271e0abe1a9ebeb0f2a2b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 08:45:10 +0100 Subject: [PATCH 042/163] CI: extra ASAN tweaks --- .github/workflows/asan/test.sh | 1 + autotest/osr/osr_basic.py | 6 ++++++ autotest/pyscripts/test_gdal_pansharpen.py | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/.github/workflows/asan/test.sh b/.github/workflows/asan/test.sh index d8cd216564e4..8ab93bb59724 100755 --- a/.github/workflows/asan/test.sh +++ b/.github/workflows/asan/test.sh @@ -46,6 +46,7 @@ find -L \ ! -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/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/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" ) From ec05413259cd44caf5fb50f459a6f52c3570c321 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 13:49:00 +0100 Subject: [PATCH 043/163] OpenFileGDB: avoid doing harmless unsigned-integer-overflow --- ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp index 284e0ec2980e..b97fb2c258ba 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp @@ -1666,10 +1666,11 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) std::vector abyTmp(nPageSize); uint64_t nOffset = TABLX_HEADER_SIZE + - static_cast(m_n1024BlocksPresent - 1) * nPageSize; + static_cast(m_n1024BlocksPresent) * nPageSize; for (int i = m_n1024BlocksPresent - 1; i >= static_cast(nCountBlocksBefore); --i) { + nOffset -= nPageSize; VSIFSeekL(m_fpTableX, nOffset, SEEK_SET); if (VSIFReadL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1) { @@ -1687,7 +1688,6 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) static_cast(nOffset)); return false; } - nOffset -= nPageSize; } abyTmp.clear(); abyTmp.resize(nPageSize); From 11fe8e4359e3d8ce7406af2e5e36053aff0349fc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 13:50:46 +0100 Subject: [PATCH 044/163] GTiff: avoid doing unsigned integer overflow --- frmts/gtiff/gtiffdataset_write.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index 4dbdcb031191..5e0e177ec7ff 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -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); From 210fbbfad1e2db53eda2df82546750bde019355a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 13:50:56 +0100 Subject: [PATCH 045/163] netCDF: avoid doing unsigned integer overflow --- frmts/netcdf/netcdfdrivercore.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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(); } } } From 5132de23b37545df1fe52209a37de152358c44c8 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 13:51:09 +0100 Subject: [PATCH 046/163] gdalmultidim: avoid doing (harmless) unsigned integer overflow --- gcore/gdalmultidim_subsetdimension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 59505b2a0db3436933e2b8e1849512c4e361395b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 13:28:26 +0100 Subject: [PATCH 047/163] ZMap: support reading variant of format where there is no newline character at end of column --- autotest/gdrivers/zmap.py | 23 ++ frmts/zmap/zmapdataset.cpp | 428 ++++++++++++++++++++----------------- 2 files changed, 258 insertions(+), 193 deletions(-) diff --git a/autotest/gdrivers/zmap.py b/autotest/gdrivers/zmap.py index 9d39f1994acc..e7d62f876cf8 100755 --- a/autotest/gdrivers/zmap.py +++ b/autotest/gdrivers/zmap.py @@ -65,3 +65,26 @@ def test_zmap_nodata(): ) ds = None gdal.GetDriverByName("ZMap").Delete(filename) + + +############################################################################### +# Test variant of the format where there is no flush at end of column + + +def test_zmap_no_flush_end_of_column(tmp_path): + + src_ds = gdal.GetDriverByName("MEM").Create("", 2, 5) + src_ds.WriteRaster(0, 0, 2, 5, b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09") + filename = str(tmp_path / "out.zmap") + with gdaltest.config_option("ZMAP_EMIT_EOL_AT_END_OF_COLUMN", "NO"): + gdal.GetDriverByName("ZMap").CreateCopy(filename, src_ds) + f = gdal.VSIFOpenL(filename, "rb") + assert f + data = gdal.VSIFReadL(1, 1000, f) + gdal.VSIFCloseL(f) + assert ( + data + == b"!\n! Created by GDAL.\n!\n@GRID FILE, GRID, 4\n 20, 1E+30, , 7, 1\n 5, 2, 0.0000000, 2.0000000, -5.0000000, 0.0000000\n0.0, 0.0, 0.0\n@\n 0.0000000 2.0000000 4.0000000 6.0000000\n 8.0000000 1.0000000 3.0000000 5.0000000\n 7.0000000 9.0000000\n" + ) + ds = gdal.Open(filename) + assert ds.ReadRaster(buf_type=gdal.GDT_Byte) == src_ds.ReadRaster() diff --git a/frmts/zmap/zmapdataset.cpp b/frmts/zmap/zmapdataset.cpp index f5c6887c5fd3..3cbb017b488e 100644 --- a/frmts/zmap/zmapdataset.cpp +++ b/frmts/zmap/zmapdataset.cpp @@ -31,7 +31,9 @@ #include "gdal_frmts.h" #include "gdal_pam.h" +#include #include +#include /************************************************************************/ /* ==================================================================== */ @@ -45,14 +47,17 @@ class ZMapDataset final : public GDALPamDataset { friend class ZMapRasterBand; - VSILFILE *fp; - int nValuesPerLine; - int nFieldSize; - int nDecimalCount; - int nColNum; - double dfNoDataValue; - vsi_l_offset nDataStartOff; - double adfGeoTransform[6]; + VSIVirtualHandleUniquePtr m_fp{}; + int m_nValuesPerLine = 0; + int m_nFieldSize = 0; + int m_nDecimalCount = 0; + int m_nColNum = -1; + double m_dfNoDataValue = 0; + vsi_l_offset m_nDataStartOff = 0; + std::array m_adfGeoTransform = {{0, 1, 0, 0, 0, 1}}; + int m_nFirstDataLine = 0; + int m_nCurLine = 0; + std::deque m_odfQueue{}; public: ZMapDataset(); @@ -98,6 +103,8 @@ ZMapRasterBand::ZMapRasterBand(ZMapDataset *poDSIn) eDataType = GDT_Float64; + // The format is column oriented! That is we have first the value of + // pixel (col=0, line=0), then the one of (col=0, line=1), etc. nBlockXSize = 1; nBlockYSize = poDSIn->GetRasterYSize(); } @@ -109,56 +116,97 @@ ZMapRasterBand::ZMapRasterBand(ZMapDataset *poDSIn) CPLErr ZMapRasterBand::IReadBlock(int nBlockXOff, CPL_UNUSED int nBlockYOff, void *pImage) { - ZMapDataset *poGDS = reinterpret_cast(poDS); - - if (poGDS->fp == nullptr) - return CE_Failure; + ZMapDataset *poGDS = cpl::down_cast(poDS); - if (nBlockXOff < poGDS->nColNum + 1) + // If seeking backwards in term of columns, reset reading to the first + // column + if (nBlockXOff < poGDS->m_nColNum + 1) { - VSIFSeekL(poGDS->fp, poGDS->nDataStartOff, SEEK_SET); - poGDS->nColNum = -1; + poGDS->m_fp->Seek(poGDS->m_nDataStartOff, SEEK_SET); + poGDS->m_nColNum = -1; + poGDS->m_nCurLine = poGDS->m_nFirstDataLine; + poGDS->m_odfQueue.clear(); } - if (nBlockXOff > poGDS->nColNum + 1) + if (nBlockXOff > poGDS->m_nColNum + 1) { - for (int i = poGDS->nColNum + 1; i < nBlockXOff; i++) + for (int i = poGDS->m_nColNum + 1; i < nBlockXOff; i++) { - if (IReadBlock(i, 0, pImage) != CE_None) + if (IReadBlock(i, 0, nullptr) != CE_None) return CE_Failure; } } - int i = 0; - const double dfExp = std::pow(10.0, poGDS->nDecimalCount); - while (i < nRasterYSize) + int iRow = 0; + const double dfExp = std::pow(10.0, poGDS->m_nDecimalCount); + double *padfImage = reinterpret_cast(pImage); + + // If we have previously read too many values, start by consuming the + // queue + while (iRow < nRasterYSize && !poGDS->m_odfQueue.empty()) + { + if (padfImage) + padfImage[iRow] = poGDS->m_odfQueue.front(); + ++iRow; + poGDS->m_odfQueue.pop_front(); + } + + // Now read as many lines as needed to finish filling the column buffer + while (iRow < nRasterYSize) { - char *pszLine = const_cast(CPLReadLineL(poGDS->fp)); + constexpr int MARGIN = 16; // Should be at least 2 for \r\n + char *pszLine = const_cast(CPLReadLine2L( + poGDS->m_fp.get(), + poGDS->m_nValuesPerLine * poGDS->m_nFieldSize + MARGIN, nullptr)); + ++poGDS->m_nCurLine; if (pszLine == nullptr) return CE_Failure; - int nExpected = nRasterYSize - i; - if (nExpected > poGDS->nValuesPerLine) - nExpected = poGDS->nValuesPerLine; - if (static_cast(strlen(pszLine)) != nExpected * poGDS->nFieldSize) + + // Each line should have at most m_nValuesPerLine values of size + // m_nFieldSize + const int nLineLen = static_cast(strlen(pszLine)); + if ((nLineLen % poGDS->m_nFieldSize) != 0) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Line %d has length %d, which is not a multiple of %d", + poGDS->m_nCurLine, nLineLen, poGDS->m_nFieldSize); return CE_Failure; + } - for (int j = 0; j < nExpected; j++) + const int nValuesThisLine = nLineLen / poGDS->m_nFieldSize; + if (nValuesThisLine > poGDS->m_nValuesPerLine) { - char *pszValue = pszLine + j * poGDS->nFieldSize; - const char chSaved = pszValue[poGDS->nFieldSize]; - pszValue[poGDS->nFieldSize] = 0; - if (strchr(pszValue, '.') != nullptr) - reinterpret_cast(pImage)[i + j] = CPLAtofM(pszValue); - else - reinterpret_cast(pImage)[i + j] = - atoi(pszValue) * dfExp; - pszValue[poGDS->nFieldSize] = chSaved; + CPLError( + CE_Failure, CPLE_AppDefined, + "Line %d has %d values, whereas the maximum expected is %d", + poGDS->m_nCurLine, nValuesThisLine, poGDS->m_nValuesPerLine); + return CE_Failure; } - i += nExpected; + for (int iValueThisLine = 0; iValueThisLine < nValuesThisLine; + iValueThisLine++) + { + char *pszValue = pszLine + iValueThisLine * poGDS->m_nFieldSize; + const char chSaved = pszValue[poGDS->m_nFieldSize]; + pszValue[poGDS->m_nFieldSize] = 0; + const double dfVal = strchr(pszValue, '.') != nullptr + ? CPLAtofM(pszValue) + : atoi(pszValue) * dfExp; + pszValue[poGDS->m_nFieldSize] = chSaved; + if (iRow < nRasterYSize) + { + if (padfImage) + padfImage[iRow] = dfVal; + ++iRow; + } + else + { + poGDS->m_odfQueue.push_back(dfVal); + } + } } - poGDS->nColNum++; + poGDS->m_nColNum++; return CE_None; } @@ -169,29 +217,19 @@ CPLErr ZMapRasterBand::IReadBlock(int nBlockXOff, CPL_UNUSED int nBlockYOff, double ZMapRasterBand::GetNoDataValue(int *pbSuccess) { - ZMapDataset *poGDS = reinterpret_cast(poDS); + ZMapDataset *poGDS = cpl::down_cast(poDS); if (pbSuccess) *pbSuccess = TRUE; - return poGDS->dfNoDataValue; + return poGDS->m_dfNoDataValue; } /************************************************************************/ /* ~ZMapDataset() */ /************************************************************************/ -ZMapDataset::ZMapDataset() - : fp(nullptr), nValuesPerLine(0), nFieldSize(0), nDecimalCount(0), - nColNum(-1), dfNoDataValue(0.0), nDataStartOff(0) -{ - adfGeoTransform[0] = 0; - adfGeoTransform[1] = 1; - adfGeoTransform[2] = 0; - adfGeoTransform[3] = 0; - adfGeoTransform[4] = 0; - adfGeoTransform[5] = 1; -} +ZMapDataset::ZMapDataset() = default; /************************************************************************/ /* ~ZMapDataset() */ @@ -201,8 +239,6 @@ ZMapDataset::~ZMapDataset() { FlushCache(true); - if (fp) - VSIFCloseL(fp); } /************************************************************************/ @@ -243,25 +279,17 @@ int ZMapDataset::Identify(GDALOpenInfo *poOpenInfo) return FALSE; i++; - char **papszTokens = CSLTokenizeString2(pszData + i, ",", 0); - if (CSLCount(papszTokens) < 3) + const CPLStringList aosTokens(CSLTokenizeString2(pszData + i, ",", 0)); + if (aosTokens.size() < 3) { - CSLDestroy(papszTokens); return FALSE; } - const char *pszToken = papszTokens[1]; + const char *pszToken = aosTokens[1]; while (*pszToken == ' ') pszToken++; - if (!STARTS_WITH(pszToken, "GRID")) - { - CSLDestroy(papszTokens); - return FALSE; - } - - CSLDestroy(papszTokens); - return TRUE; + return STARTS_WITH(pszToken, "GRID"); } /************************************************************************/ @@ -284,14 +312,22 @@ GDALDataset *ZMapDataset::Open(GDALOpenInfo *poOpenInfo) " datasets."); return nullptr; } + + auto poDS = std::make_unique(); + poDS->m_fp.reset(poOpenInfo->fpL); + poOpenInfo->fpL = nullptr; + /* -------------------------------------------------------------------- */ /* Find dataset characteristics */ /* -------------------------------------------------------------------- */ const char *pszLine; - - while ((pszLine = CPLReadLine2L(poOpenInfo->fpL, 100, nullptr)) != nullptr) + int nLine = 0; + constexpr int MAX_HEADER_LINE = 1024; + while ((pszLine = CPLReadLine2L(poDS->m_fp.get(), MAX_HEADER_LINE, + nullptr)) != nullptr) { + ++nLine; if (*pszLine == '!') { continue; @@ -302,160 +338,155 @@ GDALDataset *ZMapDataset::Open(GDALOpenInfo *poOpenInfo) // cppcheck-suppress knownConditionTrueFalse if (pszLine == nullptr) { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } /* Parse first header line */ - char **papszTokens = CSLTokenizeString2(pszLine, ",", 0); - if (CSLCount(papszTokens) != 3) + CPLStringList aosTokensFirstLine(CSLTokenizeString2(pszLine, ",", 0)); + if (aosTokensFirstLine.size() != 3) { - CSLDestroy(papszTokens); - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } - const int nValuesPerLine = atoi(papszTokens[2]); + const int nValuesPerLine = atoi(aosTokensFirstLine[2]); if (nValuesPerLine <= 0) { - CSLDestroy(papszTokens); - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid/unsupported value for nValuesPerLine = %d", + nValuesPerLine); return nullptr; } - CSLDestroy(papszTokens); - papszTokens = nullptr; - /* Parse second header line */ - pszLine = CPLReadLine2L(poOpenInfo->fpL, 100, nullptr); + pszLine = CPLReadLine2L(poDS->m_fp.get(), MAX_HEADER_LINE, nullptr); + ++nLine; if (pszLine == nullptr) { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } - papszTokens = CSLTokenizeString2(pszLine, ",", 0); - if (CSLCount(papszTokens) != 5) + const CPLStringList aosTokensSecondLine( + CSLTokenizeString2(pszLine, ",", 0)); + if (aosTokensSecondLine.size() != 5) + { + return nullptr; + } + + const int nFieldSize = atoi(aosTokensSecondLine[0]); + const double dfNoDataValue = CPLAtofM(aosTokensSecondLine[1]); + const int nDecimalCount = atoi(aosTokensSecondLine[3]); + const int nColumnNumber = atoi(aosTokensSecondLine[4]); + + if (nFieldSize <= 0 || nFieldSize >= 40) { - CSLDestroy(papszTokens); - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid/unsupported value for nFieldSize = %d", nFieldSize); return nullptr; } - const int nFieldSize = atoi(papszTokens[0]); - const double dfNoDataValue = CPLAtofM(papszTokens[1]); - const int nDecimalCount = atoi(papszTokens[3]); - const int nColumnNumber = atoi(papszTokens[4]); + if (nDecimalCount <= 0 || nDecimalCount >= nFieldSize) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid/unsupported value for nDecimalCount = %d", + nDecimalCount); + return nullptr; + } - CSLDestroy(papszTokens); - papszTokens = nullptr; + if (nColumnNumber != 1) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid/unsupported value for nColumnNumber = %d", + nColumnNumber); + return nullptr; + } - if (nFieldSize <= 0 || nFieldSize >= 40 || nDecimalCount <= 0 || - nDecimalCount >= nFieldSize || nColumnNumber != 1) + if (nValuesPerLine <= 0 || nFieldSize > 1024 * 1024 / nValuesPerLine) { - CPLDebug("ZMap", "nFieldSize=%d, nDecimalCount=%d, nColumnNumber=%d", - nFieldSize, nDecimalCount, nColumnNumber); - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid/unsupported value for nFieldSize = %d x " + "nValuesPerLine = %d", + nFieldSize, nValuesPerLine); return nullptr; } /* Parse third header line */ - pszLine = CPLReadLine2L(poOpenInfo->fpL, 100, nullptr); + pszLine = CPLReadLine2L(poDS->m_fp.get(), MAX_HEADER_LINE, nullptr); + ++nLine; if (pszLine == nullptr) { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } - papszTokens = CSLTokenizeString2(pszLine, ",", 0); - if (CSLCount(papszTokens) != 6) + const CPLStringList aosTokensThirdLine(CSLTokenizeString2(pszLine, ",", 0)); + if (aosTokensThirdLine.size() != 6) { - CSLDestroy(papszTokens); - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } - const int nRows = atoi(papszTokens[0]); - const int nCols = atoi(papszTokens[1]); - const double dfMinX = CPLAtofM(papszTokens[2]); - const double dfMaxX = CPLAtofM(papszTokens[3]); - const double dfMinY = CPLAtofM(papszTokens[4]); - const double dfMaxY = CPLAtofM(papszTokens[5]); - - CSLDestroy(papszTokens); - papszTokens = nullptr; + const int nRows = atoi(aosTokensThirdLine[0]); + const int nCols = atoi(aosTokensThirdLine[1]); + const double dfMinX = CPLAtofM(aosTokensThirdLine[2]); + const double dfMaxX = CPLAtofM(aosTokensThirdLine[3]); + const double dfMinY = CPLAtofM(aosTokensThirdLine[4]); + const double dfMaxY = CPLAtofM(aosTokensThirdLine[5]); if (!GDALCheckDatasetDimensions(nCols, nRows) || nCols == 1 || nRows == 1) { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } /* Ignore fourth header line */ - pszLine = CPLReadLine2L(poOpenInfo->fpL, 100, nullptr); + pszLine = CPLReadLine2L(poDS->m_fp.get(), MAX_HEADER_LINE, nullptr); + ++nLine; if (pszLine == nullptr) { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } /* Check fifth header line */ - pszLine = CPLReadLine2L(poOpenInfo->fpL, 100, nullptr); + pszLine = CPLReadLine2L(poDS->m_fp.get(), MAX_HEADER_LINE, nullptr); + ++nLine; if (pszLine == nullptr || pszLine[0] != '@') { - VSIFCloseL(poOpenInfo->fpL); - poOpenInfo->fpL = nullptr; return nullptr; } /* -------------------------------------------------------------------- */ /* Create a corresponding GDALDataset. */ /* -------------------------------------------------------------------- */ - ZMapDataset *poDS = new ZMapDataset(); - poDS->fp = poOpenInfo->fpL; - poOpenInfo->fpL = nullptr; - poDS->nDataStartOff = VSIFTellL(poDS->fp); - poDS->nValuesPerLine = nValuesPerLine; - poDS->nFieldSize = nFieldSize; - poDS->nDecimalCount = nDecimalCount; + poDS->m_nDataStartOff = VSIFTellL(poDS->m_fp.get()); + poDS->m_nValuesPerLine = nValuesPerLine; + poDS->m_nFieldSize = nFieldSize; + poDS->m_nDecimalCount = nDecimalCount; poDS->nRasterXSize = nCols; poDS->nRasterYSize = nRows; - poDS->dfNoDataValue = dfNoDataValue; + poDS->m_dfNoDataValue = dfNoDataValue; + poDS->m_nFirstDataLine = nLine; if (CPLTestBool(CPLGetConfigOption("ZMAP_PIXEL_IS_POINT", "FALSE"))) { const double dfStepX = (dfMaxX - dfMinX) / (nCols - 1); const double dfStepY = (dfMaxY - dfMinY) / (nRows - 1); - poDS->adfGeoTransform[0] = dfMinX - dfStepX / 2; - poDS->adfGeoTransform[1] = dfStepX; - poDS->adfGeoTransform[3] = dfMaxY + dfStepY / 2; - poDS->adfGeoTransform[5] = -dfStepY; + poDS->m_adfGeoTransform[0] = dfMinX - dfStepX / 2; + poDS->m_adfGeoTransform[1] = dfStepX; + poDS->m_adfGeoTransform[3] = dfMaxY + dfStepY / 2; + poDS->m_adfGeoTransform[5] = -dfStepY; } else { const double dfStepX = (dfMaxX - dfMinX) / nCols; const double dfStepY = (dfMaxY - dfMinY) / nRows; - poDS->adfGeoTransform[0] = dfMinX; - poDS->adfGeoTransform[1] = dfStepX; - poDS->adfGeoTransform[3] = dfMaxY; - poDS->adfGeoTransform[5] = -dfStepY; + poDS->m_adfGeoTransform[0] = dfMinX; + poDS->m_adfGeoTransform[1] = dfStepX; + poDS->m_adfGeoTransform[3] = dfMaxY; + poDS->m_adfGeoTransform[5] = -dfStepY; } /* -------------------------------------------------------------------- */ /* Create band information objects. */ /* -------------------------------------------------------------------- */ poDS->nBands = 1; - poDS->SetBand(1, new ZMapRasterBand(poDS)); + poDS->SetBand(1, std::make_unique(poDS.get())); /* -------------------------------------------------------------------- */ /* Initialize any PAM information. */ @@ -466,31 +497,33 @@ GDALDataset *ZMapDataset::Open(GDALOpenInfo *poOpenInfo) /* -------------------------------------------------------------------- */ /* Support overviews. */ /* -------------------------------------------------------------------- */ - poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename); - return poDS; + poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); + return poDS.release(); } /************************************************************************/ /* WriteRightJustified() */ /************************************************************************/ -static void WriteRightJustified(VSILFILE *fp, const char *pszValue, int nWidth) +static void WriteRightJustified(VSIVirtualHandleUniquePtr &fp, + const char *pszValue, int nWidth) { int nLen = (int)strlen(pszValue); CPLAssert(nLen <= nWidth); for (int i = 0; i < nWidth - nLen; i++) - VSIFWriteL(" ", 1, 1, fp); - VSIFWriteL(pszValue, 1, nLen, fp); + fp->Write(" ", 1, 1); + fp->Write(pszValue, 1, nLen); } -static void WriteRightJustified(VSILFILE *fp, int nValue, int nWidth) +static void WriteRightJustified(VSIVirtualHandleUniquePtr &fp, int nValue, + int nWidth) { CPLString osValue(CPLSPrintf("%d", nValue)); WriteRightJustified(fp, osValue.c_str(), nWidth); } -static void WriteRightJustified(VSILFILE *fp, double dfValue, int nWidth, - int nDecimals = -1) +static void WriteRightJustified(VSIVirtualHandleUniquePtr &fp, double dfValue, + int nWidth, int nDecimals = -1) { char szFormat[32]; if (nDecimals >= 0) @@ -574,7 +607,7 @@ GDALDataset *ZMapDataset::CreateCopy(const char *pszFilename, /* Create target file */ /* -------------------------------------------------------------------- */ - VSILFILE *fp = VSIFOpenL(pszFilename, "wb"); + auto fp = VSIVirtualHandleUniquePtr(VSIFOpenL(pszFilename, "wb")); if (fp == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s", pszFilename); @@ -591,108 +624,117 @@ GDALDataset *ZMapDataset::CreateCopy(const char *pszFilename, if (!bHasNoDataValue) dfNoDataValue = 1.e30; - VSIFPrintfL(fp, "!\n"); - VSIFPrintfL(fp, "! Created by GDAL.\n"); - VSIFPrintfL(fp, "!\n"); - VSIFPrintfL(fp, "@GRID FILE, GRID, %d\n", nValuesPerLine); + fp->Printf("!\n"); + fp->Printf("! Created by GDAL.\n"); + fp->Printf("!\n"); + fp->Printf("@GRID FILE, GRID, %d\n", nValuesPerLine); WriteRightJustified(fp, nFieldSize, 10); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, dfNoDataValue, nFieldSize, nDecimalCount); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, "", 10); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, nDecimalCount, 10); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, 1, 10); - VSIFPrintfL(fp, "\n"); + fp->Printf("\n"); WriteRightJustified(fp, nYSize, 10); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, nXSize, 10); - VSIFPrintfL(fp, ","); + fp->Printf(","); if (CPLTestBool(CPLGetConfigOption("ZMAP_PIXEL_IS_POINT", "FALSE"))) { WriteRightJustified(fp, adfGeoTransform[0] + adfGeoTransform[1] / 2, 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, adfGeoTransform[0] + adfGeoTransform[1] * nXSize - adfGeoTransform[1] / 2, 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, adfGeoTransform[3] + adfGeoTransform[5] * nYSize - adfGeoTransform[5] / 2, 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, adfGeoTransform[3] + adfGeoTransform[5] / 2, 14, 7); } else { WriteRightJustified(fp, adfGeoTransform[0], 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified( fp, adfGeoTransform[0] + adfGeoTransform[1] * nXSize, 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified( fp, adfGeoTransform[3] + adfGeoTransform[5] * nYSize, 14, 7); - VSIFPrintfL(fp, ","); + fp->Printf(","); WriteRightJustified(fp, adfGeoTransform[3], 14, 7); } - VSIFPrintfL(fp, "\n"); + fp->Printf("\n"); - VSIFPrintfL(fp, "0.0, 0.0, 0.0\n"); - VSIFPrintfL(fp, "@\n"); + fp->Printf("0.0, 0.0, 0.0\n"); + fp->Printf("@\n"); /* -------------------------------------------------------------------- */ /* Copy imagery */ /* -------------------------------------------------------------------- */ - double *padfLineBuffer = - reinterpret_cast(CPLMalloc(nYSize * sizeof(double))); + std::vector adfLineBuffer(nYSize); CPLErr eErr = CE_None; + const bool bEmitEOLAtEndOfColumn = CPLTestBool( + CPLGetConfigOption("ZMAP_EMIT_EOL_AT_END_OF_COLUMN", "YES")); + bool bEOLPrinted = false; + int nValuesThisLine = 0; for (int i = 0; i < nXSize && eErr == CE_None; i++) { - eErr = poSrcDS->GetRasterBand(1)->RasterIO(GF_Read, i, 0, 1, nYSize, - padfLineBuffer, 1, nYSize, - GDT_Float64, 0, 0, nullptr); + eErr = poSrcDS->GetRasterBand(1)->RasterIO( + GF_Read, i, 0, 1, nYSize, adfLineBuffer.data(), 1, nYSize, + GDT_Float64, 0, 0, nullptr); if (eErr != CE_None) break; - bool bEOLPrinted = false; - int j = 0; - for (; j < nYSize; j++) + for (int j = 0; j < nYSize; j++) { - WriteRightJustified(fp, padfLineBuffer[j], nFieldSize, + WriteRightJustified(fp, adfLineBuffer[j], nFieldSize, nDecimalCount); - if (((j + 1) % nValuesPerLine) == 0) + ++nValuesThisLine; + if (nValuesThisLine == nValuesPerLine) { bEOLPrinted = true; - VSIFPrintfL(fp, "\n"); + nValuesThisLine = 0; + fp->Printf("\n"); } else bEOLPrinted = false; } - if (!bEOLPrinted) - VSIFPrintfL(fp, "\n"); + if (bEmitEOLAtEndOfColumn && !bEOLPrinted) + { + bEOLPrinted = true; + nValuesThisLine = 0; + fp->Printf("\n"); + } if (pfnProgress != nullptr && - !pfnProgress((j + 1) * 1.0 / nYSize, nullptr, pProgressData)) + !pfnProgress((i + 1) * 1.0 / nXSize, nullptr, pProgressData)) { eErr = CE_Failure; break; } } - CPLFree(padfLineBuffer); - VSIFCloseL(fp); + if (!bEOLPrinted) + fp->Printf("\n"); - if (eErr != CE_None) + if (eErr != CE_None || fp->Close() != 0) return nullptr; - return GDALDataset::FromHandle(GDALOpen(pszFilename, GA_ReadOnly)); + fp.reset(); + GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly); + return ZMapDataset::Open(&oOpenInfo); } /************************************************************************/ @@ -702,7 +744,7 @@ GDALDataset *ZMapDataset::CreateCopy(const char *pszFilename, CPLErr ZMapDataset::GetGeoTransform(double *padfTransform) { - memcpy(padfTransform, adfGeoTransform, 6 * sizeof(double)); + memcpy(padfTransform, m_adfGeoTransform.data(), 6 * sizeof(double)); return CE_None; } @@ -717,7 +759,7 @@ void GDALRegister_ZMap() if (GDALGetDriverByName("ZMap") != nullptr) return; - GDALDriver *poDriver = new GDALDriver(); + auto poDriver = std::make_unique(); poDriver->SetDescription("ZMap"); poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); @@ -730,5 +772,5 @@ void GDALRegister_ZMap() poDriver->pfnIdentify = ZMapDataset::Identify; poDriver->pfnCreateCopy = ZMapDataset::CreateCopy; - GetGDALDriverManager()->RegisterDriver(poDriver); + GetGDALDriverManager()->RegisterDriver(poDriver.release()); } From 0bb2c1d094ae6d3d447ead800760e723de73e2f5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 14:54:00 +0100 Subject: [PATCH 048/163] ogrinfo: avoid emitting an error message twice (fixes #9459) --- apps/ogrinfo_bin.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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)) From f113caa05cc5d26e7a3de8fb82efbfd0817dd43f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 15:25:43 +0100 Subject: [PATCH 049/163] SWIG: fix memory leak in SpatialReference.ExportToCF1 --- swig/include/cpl.i | 2 +- swig/include/java/typemaps_java.i | 40 +++++++++++++++++++++++++------ swig/include/osr.i | 7 +++++- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/swig/include/cpl.i b/swig/include/cpl.i index 5573e135c876..dff33e913eda 100644 --- a/swig/include/cpl.i +++ b/swig/include/cpl.i @@ -507,7 +507,7 @@ const char *wrapper_CPLGetThreadLocalConfigOption( const char * pszKey, const ch %rename(GetConfigOptions) wrapper_GetConfigOptions; -#if defined(SWIGPYTHON) +#if defined(SWIGPYTHON) || defined(SWIGJAVA) %apply (char **dictAndCSLDestroy) { char ** }; #else %apply (char **) { char ** }; diff --git a/swig/include/java/typemaps_java.i b/swig/include/java/typemaps_java.i index baa4163d736c..de5c75f2d219 100644 --- a/swig/include/java/typemaps_java.i +++ b/swig/include/java/typemaps_java.i @@ -1035,16 +1035,16 @@ } } -%typemap(out) char **dict -{ - /* %typemap(out) char **dict */ - /* Convert a char array to a Hashtable */ - char **stringarray = $1; +%fragment("GetCSLStringAsHashTable","header") +%{ +/* Convert a char array to a Hashtable */ +static jobject +GetCSLStringAsHashTable(JNIEnv *jenv, char **stringarray, bool bFreeCSL ) { const jclass hashtable = jenv->FindClass("java/util/Hashtable"); const jmethodID constructor = jenv->GetMethodID(hashtable, "", "()V"); const jmethodID put = jenv->GetMethodID(hashtable, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - $result = jenv->NewObject(hashtable, constructor); + jobject jHashtable = jenv->NewObject(hashtable, constructor); if ( stringarray != NULL ) { while (*stringarray != NULL ) { char const *valptr; @@ -1056,7 +1056,7 @@ valptr = pszSep + 1; jstring name = jenv->NewStringUTF(keyptr); jstring value = jenv->NewStringUTF(valptr); - jenv->CallObjectMethod($result, put, name, value); + jenv->CallObjectMethod(jHashtable, put, name, value); jenv->DeleteLocalRef(name); jenv->DeleteLocalRef(value); CPLFree(keyptr); @@ -1064,6 +1064,16 @@ stringarray++; } } + if( bFreeCSL ) + CSLDestroy(stringarray); + return jHashtable; +} +%} + +%typemap(out,fragment="GetCSLStringAsHashTable") char **dict +{ + /* %typemap(out) char **dict */ + $result = GetCSLStringAsHashTable(jenv, $1, false); } %typemap(freearg) char **dict @@ -1080,6 +1090,22 @@ return $jnicall; } + +/* + * Typemap char ** -> dict and CSLDestroy() + */ +%typemap(out,fragment="GetCSLStringAsHashTable") char **dictAndCSLDestroy +{ + /* %typemap(out) char **dictAndCSLDestroy */ + $result = GetCSLStringAsHashTable(jenv, $1, true); +} +%typemap(jni) (char **dictAndCSLDestroy) "jobject" +%typemap(jtype) (char **dictAndCSLDestroy) "java.util.Hashtable" +%typemap(jstype) (char **dictAndCSLDestroy) "java.util.Hashtable" +%typemap(javaout) (char **dictAndCSLDestroy) { + return $jnicall; + } + /*************************************************** * Typemaps maps char** arguments from a Vector ***************************************************/ diff --git a/swig/include/osr.i b/swig/include/osr.i index 1aef03664297..43b49b3a76b1 100644 --- a/swig/include/osr.i +++ b/swig/include/osr.i @@ -1130,7 +1130,12 @@ public: return OSRExportToMICoordSys( self, argout ); } -%apply (char **dict) { char ** }; +#if defined(SWIGPYTHON) || defined(SWIGJAVA) +%apply (char **dictAndCSLDestroy) { char ** }; +#else +// We'd also need a dictAndCSLDestroy for other languages! +%apply (char **) { char ** }; +#endif %apply (char **options) { char **options }; char** ExportToCF1( char **options = NULL ) { char** ret = NULL; From 77e6c1cb4bccd3c83d904a2fab523ed02be93e3b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 14 Mar 2024 15:51:16 +0100 Subject: [PATCH 050/163] Add support for --config = syntax --- autotest/utilities/test_gdalinfo.py | 2 +- autotest/utilities/test_ogr2ogr.py | 2 +- autotest/utilities/test_ogrinfo.py | 16 +++++++++-- doc/source/user/configoptions.rst | 8 ++++++ gcore/gdal_misc.cpp | 42 ++++++++++++++++++++++------- 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/autotest/utilities/test_gdalinfo.py b/autotest/utilities/test_gdalinfo.py index 84792d39e806..6343e1d19fd7 100755 --- a/autotest/utilities/test_gdalinfo.py +++ b/autotest/utilities/test_gdalinfo.py @@ -254,7 +254,7 @@ def test_gdalinfo_14(gdalinfo_path): (_, err) = gdaltest.runexternal_out_and_err( gdalinfo_path + " --config", check_memleak=False ) - assert "--config option given without a key and value argument" in err + assert "--config option given without" in err ############################################################################### diff --git a/autotest/utilities/test_ogr2ogr.py b/autotest/utilities/test_ogr2ogr.py index ba4c1f78578f..cf598ceccb52 100755 --- a/autotest/utilities/test_ogr2ogr.py +++ b/autotest/utilities/test_ogr2ogr.py @@ -1850,7 +1850,7 @@ def test_ogr2ogr_56(ogr2ogr_path, tmp_path): f.close() gdaltest.runexternal( - f"{ogr2ogr_path} -f PGDump {dst_sql} {src_csv} -lco FID=myid --config PGDUMP_DEBUG_ALLOW_CREATION_FIELD_WITH_FID_NAME NO" + f"{ogr2ogr_path} -f PGDump {dst_sql} {src_csv} -lco FID=myid --config PGDUMP_DEBUG_ALLOW_CREATION_FIELD_WITH_FID_NAME=NO" ) f = open(dst_sql, "rt") diff --git a/autotest/utilities/test_ogrinfo.py b/autotest/utilities/test_ogrinfo.py index 4b69eec5531a..459c97fe3b08 100755 --- a/autotest/utilities/test_ogrinfo.py +++ b/autotest/utilities/test_ogrinfo.py @@ -197,12 +197,24 @@ def test_ogrinfo_12(ogrinfo_path): # Test erroneous use of --config -def test_ogrinfo_13(ogrinfo_path): +def test_ogrinfo_erroneous_config(ogrinfo_path): (_, err) = gdaltest.runexternal_out_and_err( ogrinfo_path + " --config", check_memleak=False ) - assert "--config option given without a key and value argument" in err + assert "--config option given without" in err + + +############################################################################### +# Test erroneous use of --config + + +def test_ogrinfo_erroneous_config_2(ogrinfo_path): + + (_, err) = gdaltest.runexternal_out_and_err( + ogrinfo_path + " --config foo", check_memleak=False + ) + assert "--config option given without" in err ############################################################################### diff --git a/doc/source/user/configoptions.rst b/doc/source/user/configoptions.rst index d64b0371c5e0..ed2d19eed91c 100644 --- a/doc/source/user/configoptions.rst +++ b/doc/source/user/configoptions.rst @@ -40,6 +40,14 @@ time to affect behavior. gdal_translate --config GDAL_CACHEMAX 64 in.tif out.tif +Since GDAL 3.9, it is also possible to set a config option in a more conventional +way by using a single ````=```` command line string instead of having ```` +and ```` as two space-separated strings. + +:: + + gdal_translate --config GDAL_CACHEMAX=64 in.tif out.tif + In C/C++ configuration switches can be set programmatically with :cpp:func:`CPLSetConfigOption`: diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index 6936383d676a..ba63d06cc62f 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -3147,6 +3147,7 @@ static void StripIrrelevantOptions(CPLXMLNode *psCOL, int nOptions) * --format [format]: report details of one format driver. * --optfile filename: expand an option file into the argument list. * --config key value: set system configuration option. + * --config key=value: set system configuration option (since GDAL 3.9) * --debug [on/off/value]: set debug level. * --mempreload dir: preload directory contents into /vsimem * --pause: Pause for user input (allows time to attach debugger) @@ -3235,17 +3236,39 @@ int CPL_STDCALL GDALGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv, */ else if (EQUAL(papszArgv[iArg], "--config")) { - if (iArg + 2 >= nArgc) + if (iArg + 1 >= nArgc) { - CPLError( - CE_Failure, CPLE_AppDefined, - "--config option given without a key and value argument."); + CPLError(CE_Failure, CPLE_AppDefined, + "--config option given without a key=value argument."); return -1; } - CPLSetConfigOption(papszArgv[iArg + 1], papszArgv[iArg + 2]); + const char *pszArg = papszArgv[iArg + 1]; + if (strchr(pszArg, '=') != nullptr) + { + char *pszKey = nullptr; + const char *pszValue = CPLParseNameValue(pszArg, &pszKey); + if (pszKey && pszValue) + { + CPLSetConfigOption(pszKey, pszValue); + } + CPLFree(pszKey); + ++iArg; + } + else + { + if (iArg + 2 >= nArgc) + { + CPLError(CE_Failure, CPLE_AppDefined, + "--config option given without a key and value " + "argument."); + return -1; + } + + CPLSetConfigOption(papszArgv[iArg + 1], papszArgv[iArg + 2]); - iArg += 2; + iArg += 2; + } } /* -------------------------------------------------------------------- @@ -3700,17 +3723,18 @@ int CPL_STDCALL GDALGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv, printf(" --license: report GDAL license info.\n"); /*ok*/ printf( /*ok*/ " --formats: report all configured format drivers.\n"); /*ok*/ - printf(" --format [format]: details of one format.\n"); /*ok*/ + printf(" --format []: details of one format.\n"); /*ok*/ /*ok*/ printf( " --optfile filename: expand an option file into the " "argument list.\n"); printf(/*ok*/ - " --config key value: set system configuration option.\n"); /*ok*/ + " --config or --config =: set " + "system configuration option.\n"); /*ok*/ printf(" --debug [on/off/value]: set debug level.\n"); /*ok*/ /*ok*/ printf( /*ok*/ " --pause: wait for user input, time to attach " "debugger\n"); - printf(" --locale [locale]: install locale for debugging " /*ok*/ + printf(" --locale []: install locale for debugging " /*ok*/ "(i.e. en_US.UTF-8)\n"); printf(" --help-general: report detailed help on general " /*ok*/ "options.\n"); From c34307cd72fc1c082e80ff0a8e0a0069c9db43c4 Mon Sep 17 00:00:00 2001 From: Kurt Schwehr Date: Thu, 14 Mar 2024 08:07:07 -0700 Subject: [PATCH 051/163] =?UTF-8?q?jp2kak=5Fheaders.h:=20Fix=20typo=20Kakd?= =?UTF-8?q?u=20=E2=86=92=20Kakadu=20[ci=20skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frmts/jp2kak/jp2kak_headers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b30262e32c1ec45ea4e4caa4bf808ecbd9e6512d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 15:12:18 +0100 Subject: [PATCH 052/163] Add C++ OGRGeomCoordinatePrecision class and corresponding C and SWIG API --- autotest/ogr/ogr_geomcoordinateprecision.py | 77 ++++ ogr/CMakeLists.txt | 2 + ogr/ogr_api.h | 33 ++ ogr/ogr_geomcoordinateprecision.h | 95 +++++ ogr/ogrgeomcoordinateprecision.cpp | 399 ++++++++++++++++++++ swig/include/java/ogr_java.i | 3 + swig/include/ogr.i | 72 ++++ 7 files changed, 681 insertions(+) create mode 100755 autotest/ogr/ogr_geomcoordinateprecision.py create mode 100644 ogr/ogr_geomcoordinateprecision.h create mode 100644 ogr/ogrgeomcoordinateprecision.cpp diff --git a/autotest/ogr/ogr_geomcoordinateprecision.py b/autotest/ogr/ogr_geomcoordinateprecision.py new file mode 100755 index 000000000000..1dbb4265f907 --- /dev/null +++ b/autotest/ogr/ogr_geomcoordinateprecision.py @@ -0,0 +1,77 @@ +#!/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.SetFromMetre(None, 0, 0, 0) + + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + prec.SetFromMetre(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.SetFromMetre(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.SetFromMetre(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"} 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/ogr_api.h b/ogr/ogr_api.h index f5874aaf5bd0..8645c21ce959 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 OGRGeomCoordinatePrecisionSetFromMetre(OGRGeomCoordinatePrecisionH, + OGRSpatialReferenceH hSRS, + double dfXYMetreResolution, + double dfZMetreResolution, + 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, diff --git a/ogr/ogr_geomcoordinateprecision.h b/ogr/ogr_geomcoordinateprecision.h new file mode 100644 index 000000000000..85d72135ede0 --- /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 metre, 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 SetFromMetre(const OGRSpatialReference *poSRS, + double dfXYMetreResolution, double dfZMetreResolution, + double dfMResolution); + + OGRGeomCoordinatePrecision + ConvertToOtherSRS(const OGRSpatialReference *poSRSSrc, + const OGRSpatialReference *poSRSDst) const; + + static int ResolutionToPrecision(double dfResolution); +}; + +#endif /* OGR_GEOMCOORDINATEPRECISION_H */ diff --git a/ogr/ogrgeomcoordinateprecision.cpp b/ogr/ogrgeomcoordinateprecision.cpp new file mode 100644 index 000000000000..ecdf85f6224d --- /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 OGRGeomCoordinatePrecisionSetFromMetre() 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; +} + +/************************************************************************/ +/* OGRGeomCoordinatePrecisionSetFromMetre() */ +/************************************************************************/ + +/** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z ordinates, the precision should be expressed in metre, + * 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 dfXYMetreResolution Resolution for for X and Y coordinates, in metre. + * @param dfZMetreResolution Resolution for for Z coordinates, in metre. + * @param dfMResolution Resolution for for M coordinates. + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecisionSetFromMetre( + OGRGeomCoordinatePrecisionH hGeomCoordPrec, OGRSpatialReferenceH hSRS, + double dfXYMetreResolution, double dfZMetreResolution, double dfMResolution) +{ + VALIDATE_POINTER0(hGeomCoordPrec, "OGRGeomCoordinatePrecisionSet"); + VALIDATE_POINTER0(hSRS, "OGRGeomCoordinatePrecisionSet"); + return hGeomCoordPrec->SetFromMetre(OGRSpatialReference::FromHandle(hSRS), + dfXYMetreResolution, dfZMetreResolution, + 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::SetFromMetre() */ +/************************************************************************/ + +/** + * \brief Set the resolution of the geometry coordinate components. + * + * For the X, Y and Z coordinates, the precision should be expressed in metre, + * 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 dfXYMetreResolution Resolution for for X and Y coordinates, in metre. + * @param dfZMetreResolution Resolution for for Z coordinates, in metre. + * @param dfMResolutionIn Resolution for for M coordinates. + * @since GDAL 3.9 + */ +void OGRGeomCoordinatePrecision::SetFromMetre(const OGRSpatialReference *poSRS, + double dfXYMetreResolution, + double dfZMetreResolution, + double dfMResolutionIn) +{ + double dfXYFactor = 1; + double dfZFactor = 1; + GetConversionFactors(poSRS, dfXYFactor, dfZFactor); + + dfXYResolution = dfXYMetreResolution / dfXYFactor; + dfZResolution = dfZMetreResolution / 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, metre unit is assumed) + * @param poSRSDst Spatial reference system of the returned instance + * (if null, metre 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/swig/include/java/ogr_java.i b/swig/include/java/ogr_java.i index df10f4ad3acb..5d12786fe14c 100644 --- a/swig/include/java/ogr_java.i +++ b/swig/include/java/ogr_java.i @@ -62,6 +62,9 @@ import org.gdal.osr.SpatialReference; %typemap(javaimports) OGRGeomTransformerShadow %{ import org.gdal.osr.CoordinateTransformation; %} +%typemap(javaimports) OGRGeomCoordinatePrecisionShadow %{ +import org.gdal.osr.SpatialReference; +%} %typemap(javacode) OGRDataSourceShadow %{ diff --git a/swig/include/ogr.i b/swig/include/ogr.i index f35c906a5688..c3942ff79c63 100644 --- a/swig/include/ogr.i +++ b/swig/include/ogr.i @@ -303,6 +303,7 @@ typedef struct OGRGeomFieldDefnHS OGRGeomFieldDefnShadow; typedef struct OGRGeomTransformer OGRGeomTransformerShadow; typedef struct _OGRPreparedGeometry OGRPreparedGeometryShadow; typedef struct OGRFieldDomainHS OGRFieldDomainShadow; +typedef struct OGRGeomCoordinatePrecision OGRGeomCoordinatePrecisionShadow; %} #ifdef SWIGJAVA @@ -4241,6 +4242,77 @@ OGRFieldDomainShadow* CreateGlobFieldDomain( const char *name, %clear const char* name; %clear const char* glob; +/************************************************************************/ +/* OGRGeomCoordinatePrecision */ +/************************************************************************/ + +%rename (GeomCoordinatePrecision) OGRGeomCoordinatePrecisionShadow; + +class OGRGeomCoordinatePrecisionShadow { + OGRGeomCoordinatePrecisionShadow(); +public: +%extend { + + ~OGRGeomCoordinatePrecisionShadow() { + OGRGeomCoordinatePrecisionDestroy(self); + } + + void Set(double xyResolution, double zResolution, double mResolution) { + OGRGeomCoordinatePrecisionSet(self, xyResolution, zResolution, mResolution); + } + + %apply Pointer NONNULL {OSRSpatialReferenceShadow* srs}; + void SetFromMetre(OSRSpatialReferenceShadow* srs, double xyResolutionMetre, double zResolutionMetre, double mResolution) { + OGRGeomCoordinatePrecisionSetFromMetre(self, srs, xyResolutionMetre, zResolutionMetre, mResolution); + } + %clear OSRSpatialReferenceShadow* srs; + + double GetXYResolution() { + return OGRGeomCoordinatePrecisionGetXYResolution(self); + } + + double GetZResolution() { + return OGRGeomCoordinatePrecisionGetZResolution(self); + } + + double GetMResolution() { + return OGRGeomCoordinatePrecisionGetMResolution(self); + } + +%apply (char **CSL) {(char **)}; + char **GetFormats() { + return OGRGeomCoordinatePrecisionGetFormats(self); + } +%clear char **; + +%apply (char **dict) {char **}; +%apply Pointer NONNULL {const char* formatName}; + char ** GetFormatSpecificOptions(const char* formatName) { + return OGRGeomCoordinatePrecisionGetFormatSpecificOptions(self, formatName); + } +%clear char **; +%clear const char* formatName; + +%apply Pointer NONNULL {const char* formatName}; +%apply (char **dict) { char ** formatSpecificOptions }; + void SetFormatSpecificOptions(const char* formatName, char **formatSpecificOptions) { + OGRGeomCoordinatePrecisionSetFormatSpecificOptions(self, formatName, formatSpecificOptions); + } +%clear const char* formatName; +%clear char **formatSpecificOptions; + +} /* %extend */ + +}; /* class OGRGeomCoordinatePrecisionShadow */ + +%newobject CreateGeomCoordinatePrecision; +%inline %{ +static +OGRGeomCoordinatePrecisionShadow* CreateGeomCoordinatePrecision() { + return OGRGeomCoordinatePrecisionCreate(); +} +%} + /************************************************************************/ /* Other misc functions. */ /************************************************************************/ From a281455ba5b1aa7400ac20937581dacad131d670 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 20:38:53 +0100 Subject: [PATCH 053/163] ogrfeature.cpp: silence gcc -Wnull-dereference false positives --- ogr/ogrfeature.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ogr/ogrfeature.cpp b/ogr/ogrfeature.cpp index 3ecafe900546..0d4a454ecbd9 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() */ /************************************************************************/ @@ -7814,3 +7820,7 @@ OGRFeature::FieldValue::operator CSLConstList() const const_cast(m_poPrivate->m_poSelf) ->GetFieldAsStringList(GetIndex())); } + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif From 20e21679f99dfac31362836bb411d409871f2009 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 20:22:59 +0100 Subject: [PATCH 054/163] OGRGeomFieldDefn: add a OGRGeomCoordinatePrecision member and getter and setter --- autotest/ogr/ogr_geomcoordinateprecision.py | 30 +++++++ ogr/ogr_api.h | 5 ++ ogr/ogr_feature.h | 9 ++ ogr/ogrgeomfielddefn.cpp | 99 ++++++++++++++++++++- swig/include/ogr.i | 11 +++ 5 files changed, 153 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_geomcoordinateprecision.py b/autotest/ogr/ogr_geomcoordinateprecision.py index 1dbb4265f907..1f9b424267c4 100755 --- a/autotest/ogr/ogr_geomcoordinateprecision.py +++ b/autotest/ogr/ogr_geomcoordinateprecision.py @@ -75,3 +75,33 @@ def test_ogr_geomcoordinate_precision(): 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/ogr/ogr_api.h b/ogr/ogr_api.h index 8645c21ce959..83b0c8f34e5f 100644 --- a/ogr/ogr_api.h +++ b/ogr/ogr_api.h @@ -468,6 +468,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/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/swig/include/ogr.i b/swig/include/ogr.i index c3942ff79c63..1bc57a73dda9 100644 --- a/swig/include/ogr.i +++ b/swig/include/ogr.i @@ -2987,6 +2987,17 @@ public: void SetNullable(int bNullable ) { return OGR_GFld_SetNullable( self, bNullable ); } + + OGRGeomCoordinatePrecisionShadow* GetCoordinatePrecision() { + return OGR_GFld_GetCoordinatePrecision(self); + } + + %apply Pointer NONNULL {OGRGeomCoordinatePrecisionShadow* srs}; + void SetCoordinatePrecision(OGRGeomCoordinatePrecisionShadow* coordPrec) { + OGR_GFld_SetCoordinatePrecision(self, coordPrec); + } + %clear OGRGeomCoordinatePrecisionShadow* srs; + } /* %extend */ From 48dd401390c3486a7ba25228949e04abcc69eafc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 22:09:06 +0100 Subject: [PATCH 055/163] Add GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION --- gcore/gdal.h | 11 +++++++++++ gcore/gdal_misc.cpp | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/gcore/gdal.h b/gcore/gdal.h index 736f3ad9f1ad..d9c26ab7b50f 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. * diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index 6936383d676a..01a22cb7e403 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -3603,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)) From 0c92a7fc29b90bd0658e92b9bd5cc206358cf9f7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 20:23:20 +0100 Subject: [PATCH 056/163] Change prototype of GDALDataset::ICreateLayer() to take a const OGRGeomCoordinatePrecision* --- autotest/cpp/test_ogr.cpp | 2 +- frmts/fits/fitsdataset.cpp | 13 +- frmts/netcdf/netcdfdataset.cpp | 9 +- frmts/netcdf/netcdfdataset.h | 7 +- frmts/null/nulldataset.cpp | 15 +- frmts/pcidsk/pcidskdataset2.cpp | 9 +- frmts/pcidsk/pcidskdataset2.h | 5 +- frmts/pdf/gdal_pdf.h | 7 +- frmts/pdf/pdfwritabledataset.cpp | 8 +- frmts/pds/pds4dataset.cpp | 9 +- frmts/pds/pds4dataset.h | 6 +- frmts/tiledb/tiledbheaders.h | 7 +- frmts/tiledb/tiledbsparse.cpp | 8 +- gcore/gdal.h | 4 + gcore/gdal_priv.h | 24 ++- gcore/gdaldataset.cpp | 172 +++++++++++++++--- gnm/gnm_frmts/db/gnmdb.h | 9 +- gnm/gnm_frmts/db/gnmdbnetwork.cpp | 5 +- gnm/gnm_frmts/file/gnmfile.h | 9 +- gnm/gnm_frmts/file/gnmfilenetwork.cpp | 9 +- ogr/ogrsf_frmts/amigocloud/ogr_amigocloud.h | 8 +- .../amigocloud/ogramigoclouddatasource.cpp | 11 +- ogr/ogrsf_frmts/arrow/ogr_feather.h | 5 +- .../arrow/ogrfeatherwriterdataset.cpp | 12 +- ogr/ogrsf_frmts/carto/ogr_carto.h | 8 +- ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp | 8 +- ogr/ogrsf_frmts/csv/ogr_csv.h | 8 +- ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp | 8 +- ogr/ogrsf_frmts/dgn/ogr_dgn.h | 7 +- ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp | 13 +- ogr/ogrsf_frmts/dwg/ogr_dgnv8.h | 6 +- ogr/ogrsf_frmts/dwg/ogrdgnv8datasource.cpp | 7 +- ogr/ogrsf_frmts/dxf/ogr_dxf.h | 5 +- ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp | 7 +- ogr/ogrsf_frmts/elastic/ogr_elastic.h | 10 +- .../elastic/ogrelasticdatasource.cpp | 12 +- ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp | 3 +- ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp | 9 +- ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 6 +- ogr/ogrsf_frmts/filegdb/ogr_fgdb.h | 11 +- ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h | 11 +- .../flatgeobuf/ogrflatgeobufdataset.cpp | 11 +- .../flatgeobuf/ogrflatgeobuflayer.cpp | 2 +- .../generic/ogremulatedtransaction.cpp | 17 +- .../generic/ogrmutexeddatasource.cpp | 9 +- .../generic/ogrmutexeddatasource.h | 8 +- .../geoconcept/ogrgeoconceptdatasource.cpp | 14 +- .../geoconcept/ogrgeoconceptdatasource.h | 5 +- ogr/ogrsf_frmts/geojson/ogr_geojson.h | 7 +- .../geojson/ogrgeojsondatasource.cpp | 12 +- .../geojson/ogrgeojsonseqdriver.cpp | 15 +- .../geojson/ogrgeojsonwritelayer.cpp | 2 +- ogr/ogrsf_frmts/georss/ogr_georss.h | 8 +- .../georss/ogrgeorssdatasource.cpp | 10 +- ogr/ogrsf_frmts/gml/ogr_gml.h | 9 +- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 12 +- ogr/ogrsf_frmts/gmt/ogr_gmt.h | 7 +- ogr/ogrsf_frmts/gmt/ogrgmtdatasource.cpp | 12 +- ogr/ogrsf_frmts/gpkg/ogr_geopackage.h | 7 +- .../gpkg/ogrgeopackagedatasource.cpp | 11 +- ogr/ogrsf_frmts/gpsbabel/ogr_gpsbabel.h | 7 +- .../gpsbabel/ogrgpsbabelwritedatasource.cpp | 7 +- ogr/ogrsf_frmts/gpx/ogr_gpx.h | 7 +- ogr/ogrsf_frmts/gpx/ogrgpxdatasource.cpp | 5 +- ogr/ogrsf_frmts/hana/ogr_hana.h | 7 +- ogr/ogrsf_frmts/hana/ogrhanadatasource.cpp | 13 +- ogr/ogrsf_frmts/ili/ogr_ili1.h | 7 +- ogr/ogrsf_frmts/ili/ogr_ili2.h | 7 +- ogr/ogrsf_frmts/ili/ogrili1datasource.cpp | 10 +- ogr/ogrsf_frmts/ili/ogrili2datasource.cpp | 9 +- ogr/ogrsf_frmts/jml/ogr_jml.h | 7 +- ogr/ogrsf_frmts/jml/ogrjmldataset.cpp | 7 +- ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h | 6 +- ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp | 12 +- ogr/ogrsf_frmts/kml/ogr_kml.h | 5 +- ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp | 12 +- ogr/ogrsf_frmts/libkml/ogr_libkml.h | 11 +- .../libkml/ogrlibkmldatasource.cpp | 18 +- ogr/ogrsf_frmts/mapml/ogrmapmldataset.cpp | 15 +- ogr/ogrsf_frmts/mem/ogr_mem.h | 7 +- ogr/ogrsf_frmts/mem/ogrmemdatasource.cpp | 13 +- .../mitab/mitab_ogr_datasource.cpp | 11 +- ogr/ogrsf_frmts/mitab/mitab_ogr_driver.h | 6 +- .../mongodbv3/ogrmongodbv3driver.cpp | 23 +-- .../mssqlspatial/ogr_mssqlspatial.h | 7 +- .../ogrmssqlspatialdatasource.cpp | 11 +- ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp | 16 +- ogr/ogrsf_frmts/mysql/ogr_mysql.h | 7 +- ogr/ogrsf_frmts/mysql/ogrmysqldatasource.cpp | 12 +- ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp | 9 +- ogr/ogrsf_frmts/ngw/ogr_ngw.h | 8 +- ogr/ogrsf_frmts/oci/ogr_oci.h | 10 +- ogr/ogrsf_frmts/oci/ogrocidatasource.cpp | 12 +- ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp | 2 +- ogr/ogrsf_frmts/ods/ogr_ods.h | 8 +- ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp | 7 +- ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h | 8 +- .../ogropenfilegdbdatasource_write.cpp | 13 +- ogr/ogrsf_frmts/parquet/ogr_parquet.h | 5 +- .../parquet/ogrparquetwriterdataset.cpp | 12 +- ogr/ogrsf_frmts/pg/ogr_pg.h | 7 +- ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp | 9 +- ogr/ogrsf_frmts/pgdump/ogr_pgdump.h | 7 +- .../pgdump/ogrpgdumpdatasource.cpp | 16 +- ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h | 5 +- .../pmtiles/ogrpmtileswriterdataset.cpp | 9 +- ogr/ogrsf_frmts/selafin/ogr_selafin.h | 10 +- .../selafin/ogrselafindatasource.cpp | 13 +- ogr/ogrsf_frmts/shape/ogrshape.h | 6 +- ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp | 12 +- ogr/ogrsf_frmts/sqlite/ogr_sqlite.h | 7 +- .../sqlite/ogrsqlitedatasource.cpp | 12 +- ogr/ogrsf_frmts/vdv/ogr_vdv.h | 9 +- ogr/ogrsf_frmts/vdv/ogrvdvdatasource.cpp | 9 +- ogr/ogrsf_frmts/wasp/ogrwasp.h | 8 +- ogr/ogrsf_frmts/wasp/ogrwaspdatasource.cpp | 7 +- ogr/ogrsf_frmts/xlsx/ogr_xlsx.h | 8 +- ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp | 8 +- swig/include/Dataset.i | 14 ++ swig/include/gdal.i | 1 + swig/include/java/gdal_java.i | 2 + swig/include/java/ogr_java.i | 16 ++ 122 files changed, 789 insertions(+), 475 deletions(-) diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index 0d3044633a28..498abf8370ee 100644 --- a/autotest/cpp/test_ogr.cpp +++ b/autotest/cpp/test_ogr.cpp @@ -1406,7 +1406,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) 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/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/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/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/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/gcore/gdal.h b/gcore/gdal.h index d9c26ab7b50f..0c6f05f25c63 100644 --- a/gcore/gdal.h +++ b/gcore/gdal.h @@ -1249,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); diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index dfe671a759d4..809f3cd88c63 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -1007,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); @@ -1048,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 *); diff --git a/gcore/gdaldataset.cpp b/gcore/gdaldataset.cpp index 38503ec8d697..c407bb78048c 100644 --- a/gcore/gdaldataset.cpp +++ b/gcore/gdaldataset.cpp @@ -4944,7 +4944,59 @@ 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. + +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. 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. +@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 +5005,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 +5035,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 +5140,57 @@ 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. + +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 +5515,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 +5593,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 +5686,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/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/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/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..fcea67fe433f 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 *poGeomFieldDefn, + CSLConstList papszOptions) { // Verify we are in update mode. if (!bUpdate) @@ -933,6 +933,10 @@ OGRCSVDataSource::ICreateLayer(const char *pszLayerName, 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/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..f06b6884b7ed 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp @@ -696,13 +696,16 @@ OGRLayer *FGdbDataSource::GetLayer(int iLayer) /************************************************************************/ OGRLayer *FGdbDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!m_bUpdate || m_pGeodatabase == nullptr) return nullptr; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + FGdbLayer *pLayer = new FGdbLayer(); if (!pLayer->Create(this, pszLayerName, poSRS, eType, papszOptions)) { diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index 01d47fba5181..6e6e5bafa70a 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -2074,7 +2074,7 @@ OGRErr FGdbLayer::AlterFieldDefn(int iFieldToAlter, /************************************************************************/ static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, - char **papszOptions) + CSLConstList papszOptions) { /* We always need a SpatialReference */ CPLXMLNode *srs_xml = @@ -2344,7 +2344,7 @@ static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, const OGRSpatialReference *poSRS, - char **papszOptions) + CSLConstList papszOptions) { /* XML node */ CPLXMLNode *xml_xml = CPLCreateXMLNode(nullptr, CXT_Element, "?xml"); @@ -2432,7 +2432,7 @@ bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, const char *pszLayerNameIn, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **papszOptions) + OGRwkbGeometryType eType, CSLConstList papszOptions) { std::string parent_path = ""; std::wstring wtable_path, wparent_path; diff --git a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h index d5661beb2fa2..bda0905bad36 100644 --- a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h +++ b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h @@ -166,11 +166,11 @@ class FGdbLayer final : public FGdbBaseLayer const std::wstring &wstrType); bool Create(FGdbDataSource *pParentDataSource, const char *pszLayerName, const OGRSpatialReference *poSRS, OGRwkbGeometryType eType, - char **papszOptions); + CSLConstList papszOptions); static bool CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, const OGRSpatialReference *poSRS, - char **papszOptions); + CSLConstList papszOptions); // virtual const char *GetName(); virtual const char *GetFIDColumn() override @@ -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/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..f7b065beb543 100644 --- a/ogr/ogrsf_frmts/geojson/ogr_geojson.h +++ b/ogr/ogrsf_frmts/geojson/ogr_geojson.h @@ -139,7 +139,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(); @@ -224,9 +224,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..6e6a73de72fb 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp @@ -254,10 +254,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 *poGeomFieldDefn, + CSLConstList papszOptions) { if (nullptr == fpOut_) { @@ -274,6 +274,10 @@ OGRLayer *OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + const char *pszForeignMembersCollection = CSLFetchNameValue(papszOptions, "FOREIGN_MEMBERS_COLLECTION"); if (pszForeignMembersCollection) diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp index 40d9e6cf678c..3aec64188e8e 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); @@ -174,13 +173,17 @@ OGRLayer *OGRGeoJSONSeqDataSource::GetLayer(int nIndex) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRGeoJSONSeqDataSource::ICreateLayer( - const char *pszNameIn, const OGRSpatialReference *poSRS, - OGRwkbGeometryType /*eGType*/, char **papszOptions) +OGRLayer * +OGRGeoJSONSeqDataSource::ICreateLayer(const char *pszNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!TestCapability(ODsCCreateLayer)) return nullptr; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + std::unique_ptr poCT; if (poSRS == nullptr) { diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp index bfa50270f8d1..2dae4d434161 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) 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/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..9f4a9af45aff 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -1968,10 +1968,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 *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { // Verify we are in update mode. if (fpOutput == nullptr) @@ -1984,6 +1984,10 @@ OGRLayer *OGRGMLDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + // Ensure name is safe as an element name. char *pszCleanLayerName = CPLStrdup(pszLayerName); 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..74a0ab3d627b 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 diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index 3a62f416ce89..ed0c70ebdf11 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 *poGeomFieldDefn, + CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ /* Verify we are in update mode. */ @@ -6619,6 +6620,10 @@ OGRLayer *GDALGeoPackageDataset::ICreateLayer( return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + if (!m_bHasGPKGGeometryColumns) { if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE) diff --git a/ogr/ogrsf_frmts/gpsbabel/ogr_gpsbabel.h b/ogr/ogrsf_frmts/gpsbabel/ogr_gpsbabel.h index 3453de99a791..e65608bb2796 100644 --- a/ogr/ogrsf_frmts/gpsbabel/ogr_gpsbabel.h +++ b/ogr/ogrsf_frmts/gpsbabel/ogr_gpsbabel.h @@ -101,10 +101,9 @@ class OGRGPSBabelWriteDataSource final : public OGRDataSource virtual int TestCapability(const char *) 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; int Create(const char *pszFilename, char **papszOptions); }; diff --git a/ogr/ogrsf_frmts/gpsbabel/ogrgpsbabelwritedatasource.cpp b/ogr/ogrsf_frmts/gpsbabel/ogrgpsbabelwritedatasource.cpp index 54689ab52680..f9bae27b9fc8 100644 --- a/ogr/ogrsf_frmts/gpsbabel/ogrgpsbabelwritedatasource.cpp +++ b/ogr/ogrsf_frmts/gpsbabel/ogrgpsbabelwritedatasource.cpp @@ -210,11 +210,12 @@ int OGRGPSBabelWriteDataSource::Create(const char *pszNameIn, /************************************************************************/ OGRLayer *OGRGPSBabelWriteDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **papszOptions) + const char *pszLayerName, const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (poGPXDS) - return poGPXDS->CreateLayer(pszLayerName, poSRS, eType, papszOptions); + return poGPXDS->CreateLayer(pszLayerName, poGeomFieldDefn, + papszOptions); return nullptr; } diff --git a/ogr/ogrsf_frmts/gpx/ogr_gpx.h b/ogr/ogrsf_frmts/gpx/ogr_gpx.h index 44b751a455c5..ca1aa299743a 100644 --- a/ogr/ogrsf_frmts/gpx/ogr_gpx.h +++ b/ogr/ogrsf_frmts/gpx/ogr_gpx.h @@ -235,10 +235,9 @@ class OGRGPXDataSource final : public GDALDataset } 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; diff --git a/ogr/ogrsf_frmts/gpx/ogrgpxdatasource.cpp b/ogr/ogrsf_frmts/gpx/ogrgpxdatasource.cpp index bc5fd3c25e6a..d4987e17a565 100644 --- a/ogr/ogrsf_frmts/gpx/ogrgpxdatasource.cpp +++ b/ogr/ogrsf_frmts/gpx/ogrgpxdatasource.cpp @@ -124,10 +124,11 @@ OGRLayer *OGRGPXDataSource::GetLayer(int iLayer) OGRLayer * OGRGPXDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference * /* poSRS */, - OGRwkbGeometryType eType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { GPXGeometryType gpxGeomType; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; if (eType == wkbPoint || eType == wkbPoint25D) { if (EQUAL(pszLayerName, "track_points")) diff --git a/ogr/ogrsf_frmts/hana/ogr_hana.h b/ogr/ogrsf_frmts/hana/ogr_hana.h index 706b7fc4222e..032364a3d2bb 100644 --- a/ogr/ogrsf_frmts/hana/ogr_hana.h +++ b/ogr/ogrsf_frmts/hana/ogr_hana.h @@ -402,10 +402,9 @@ class OGRHanaDataSource final : public GDALDataset } OGRLayer *GetLayer(int index) override; OGRLayer *GetLayerByName(const char *) override; - OGRLayer *ICreateLayer(const char *layerName, - const OGRSpatialReference *srs = nullptr, - OGRwkbGeometryType geomType = wkbUnknown, - char **options = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *capabilities) override; OGRLayer *ExecuteSQL(const char *sqlCommand, OGRGeometry *spatialFilter, diff --git a/ogr/ogrsf_frmts/hana/ogrhanadatasource.cpp b/ogr/ogrsf_frmts/hana/ogrhanadatasource.cpp index 09a3fdbc4283..8527bbebfc34 100644 --- a/ogr/ogrsf_frmts/hana/ogrhanadatasource.cpp +++ b/ogr/ogrsf_frmts/hana/ogrhanadatasource.cpp @@ -1542,14 +1542,19 @@ OGRLayer *OGRHanaDataSource::GetLayerByName(const char *name) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRHanaDataSource::ICreateLayer(const char *layerNameIn, - const OGRSpatialReference *srs, - OGRwkbGeometryType geomType, - char **options) +OGRLayer * +OGRHanaDataSource::ICreateLayer(const char *layerNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList options) { if (layerNameIn == nullptr) return nullptr; + const auto geomType = + poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto srs = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + // Check if we are allowed to create new objects in the database odbc::DatabaseMetaDataRef dmd = conn_->getDatabaseMetaData(); if (dmd->isReadOnly()) diff --git a/ogr/ogrsf_frmts/ili/ogr_ili1.h b/ogr/ogrsf_frmts/ili/ogr_ili1.h index baf73e24884e..2c29a825cb7f 100644 --- a/ogr/ogrsf_frmts/ili/ogr_ili1.h +++ b/ogr/ogrsf_frmts/ili/ogr_ili1.h @@ -140,10 +140,9 @@ class OGRILI1DataSource final : public OGRDataSource return fpTransfer; } - 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/ili/ogr_ili2.h b/ogr/ogrsf_frmts/ili/ogr_ili2.h index 5e18cda8e83e..619edc088342 100644 --- a/ogr/ogrsf_frmts/ili/ogr_ili2.h +++ b/ogr/ogrsf_frmts/ili/ogr_ili2.h @@ -121,10 +121,9 @@ class OGRILI2DataSource 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; VSILFILE *GetOutputFP() { diff --git a/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp b/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp index 241929928988..fe7d1cfa75ea 100644 --- a/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp +++ b/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp @@ -257,11 +257,13 @@ static char *ExtractTopic(const char *pszLayerName) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRILI1DataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference * /*poSRS*/, - OGRwkbGeometryType eType, - char ** /* papszOptions */) +OGRLayer * +OGRILI1DataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + FeatureDefnInfo featureDefnInfo = poImdReader->GetFeatureDefnInfo(pszLayerName); const char *table = pszLayerName; diff --git a/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp b/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp index ff6476c558d5..b1c36ce36f36 100644 --- a/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp +++ b/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp @@ -266,13 +266,16 @@ int OGRILI2DataSource::Create(const char *pszFilename, /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRILI2DataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference * /* poSRS */, - OGRwkbGeometryType eType, char ** /* papszOptions */) +OGRLayer * +OGRILI2DataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { if (fpOutput == nullptr) return nullptr; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + FeatureDefnInfo featureDefnInfo = poImdReader->GetFeatureDefnInfo(pszLayerName); OGRFeatureDefn *poFeatureDefn = featureDefnInfo.GetTableDefnRef(); diff --git a/ogr/ogrsf_frmts/jml/ogr_jml.h b/ogr/ogrsf_frmts/jml/ogr_jml.h index 8804d9d73590..ae7e54b646bd 100644 --- a/ogr/ogrsf_frmts/jml/ogr_jml.h +++ b/ogr/ogrsf_frmts/jml/ogr_jml.h @@ -207,10 +207,9 @@ class OGRJMLDataset final : public GDALDataset } 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; diff --git a/ogr/ogrsf_frmts/jml/ogrjmldataset.cpp b/ogr/ogrsf_frmts/jml/ogrjmldataset.cpp index d3ad7687081d..41bd89b9e32e 100644 --- a/ogr/ogrsf_frmts/jml/ogrjmldataset.cpp +++ b/ogr/ogrsf_frmts/jml/ogrjmldataset.cpp @@ -177,9 +177,8 @@ GDALDataset *OGRJMLDataset::Create(const char *pszFilename, int /* nXSize */, /************************************************************************/ OGRLayer *OGRJMLDataset::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType /* eType */, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!bWriteMode || poLayer != nullptr) return nullptr; @@ -191,6 +190,8 @@ OGRLayer *OGRJMLDataset::ICreateLayer(const char *pszLayerName, bool bClassicGML = CPLTestBool(CSLFetchNameValueDef(papszOptions, "CLASSIC_GML", "NO")); OGRSpatialReference *poSRSClone = nullptr; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; if (poSRS) { poSRSClone = poSRS->Clone(); diff --git a/ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h b/ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h index c170f348f281..70d233f69017 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h +++ b/ogr/ogrsf_frmts/jsonfg/ogr_jsonfg.h @@ -270,9 +270,9 @@ class OGRJSONFGDataset final : public GDALDataset void BeforeCreateFeature(); 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; OGRErr SyncToDiskInternal(); diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp index aedd15f44080..a875984dbf39 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp @@ -508,10 +508,10 @@ bool OGRJSONFGDataset::EmitStartFeaturesIfNeededAndReturnIfFirstFeature() /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, - char **papszOptions) +OGRLayer * +OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (nullptr == fpOut_) { @@ -529,6 +529,10 @@ OGRLayer *OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + std::string osCoordRefSys; std::unique_ptr poCTToWGS84; if (poSRS) diff --git a/ogr/ogrsf_frmts/kml/ogr_kml.h b/ogr/ogrsf_frmts/kml/ogr_kml.h index 45c59441dafc..584a9923e871 100644 --- a/ogr/ogrsf_frmts/kml/ogr_kml.h +++ b/ogr/ogrsf_frmts/kml/ogr_kml.h @@ -121,9 +121,8 @@ class OGRKMLDataSource final : public OGRDataSource } 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; // diff --git a/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp b/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp index 33b10e4a11ec..fa81a760714f 100644 --- a/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp +++ b/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp @@ -363,10 +363,10 @@ int OGRKMLDataSource::Create(const char *pszName, char **papszOptions) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRKMLDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char ** /* papszOptions */) +OGRLayer * +OGRKMLDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /* papszOptions*/) { CPLAssert(nullptr != pszLayerName); @@ -383,6 +383,10 @@ OGRLayer *OGRKMLDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Close the previous layer (if there is one open) */ /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/libkml/ogr_libkml.h b/ogr/ogrsf_frmts/libkml/ogr_libkml.h index 17cb7ce6af21..cfe8aff0405e 100644 --- a/ogr/ogrsf_frmts/libkml/ogr_libkml.h +++ b/ogr/ogrsf_frmts/libkml/ogr_libkml.h @@ -231,7 +231,7 @@ class OGRLIBKMLDataSource final : public OGRDataSource /***** style table pointer *****/ void SetCommonOptions(kmldom::ContainerPtr poKmlContainer, - char **papszOptions); + CSLConstList papszOptions); void ParseDocumentOptions(kmldom::KmlPtr poKml, kmldom::DocumentPtr poKmlDocument); @@ -254,9 +254,8 @@ class OGRLIBKMLDataSource final : public OGRDataSource OGRErr DeleteLayer(int) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; OGRStyleTable *GetStyleTable() override; void SetStyleTableDirectly(OGRStyleTable *poStyleTable) override; @@ -332,11 +331,11 @@ class OGRLIBKMLDataSource final : public OGRDataSource OGRLIBKMLLayer *CreateLayerKml(const char *pszLayerName, const OGRSpatialReference *poOgrSRS, OGRwkbGeometryType eGType, - char **papszOptions); + CSLConstList papszOptions); OGRLIBKMLLayer *CreateLayerKmz(const char *pszLayerName, const OGRSpatialReference *poOgrSRS, OGRwkbGeometryType eGType, - char **papszOptions); + CSLConstList papszOptions); /***** methods to delete layers on various datasource types *****/ OGRErr DeleteLayerKml(int); diff --git a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp index 88c124205002..aefca4171b14 100644 --- a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp +++ b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp @@ -1563,7 +1563,7 @@ static bool IsValidPhoneNumber(const char *pszPhoneNumber) /************************************************************************/ void OGRLIBKMLDataSource::SetCommonOptions(ContainerPtr poKmlContainer, - char **papszOptions) + CSLConstList papszOptions) { const char *l_pszName = CSLFetchNameValue(papszOptions, "NAME"); if (l_pszName != nullptr) @@ -2121,7 +2121,7 @@ OGRErr OGRLIBKMLDataSource::DeleteLayer(int iLayer) OGRLIBKMLLayer *OGRLIBKMLDataSource::CreateLayerKml( const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, char **papszOptions) + OGRwkbGeometryType eGType, CSLConstList papszOptions) { ContainerPtr poKmlLayerContainer = nullptr; @@ -2167,7 +2167,7 @@ OGRLIBKMLLayer *OGRLIBKMLDataSource::CreateLayerKml( OGRLIBKMLLayer *OGRLIBKMLDataSource::CreateLayerKmz( const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGType, char ** /* papszOptions */) + OGRwkbGeometryType eGType, CSLConstList /* papszOptions */) { DocumentPtr poKmlDocument = nullptr; @@ -2227,10 +2227,10 @@ OGRLIBKMLLayer *OGRLIBKMLDataSource::CreateLayerKmz( ******************************************************************************/ -OGRLayer *OGRLIBKMLDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poOgrSRS, - OGRwkbGeometryType eGType, - char **papszOptions) +OGRLayer * +OGRLIBKMLDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!bUpdate) return nullptr; @@ -2242,6 +2242,10 @@ OGRLayer *OGRLIBKMLDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poOgrSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + OGRLIBKMLLayer *poOgrLayer = nullptr; /***** kml DS *****/ diff --git a/ogr/ogrsf_frmts/mapml/ogrmapmldataset.cpp b/ogr/ogrsf_frmts/mapml/ogrmapmldataset.cpp index 58db1307eb2b..e312429d2f9d 100644 --- a/ogr/ogrsf_frmts/mapml/ogrmapmldataset.cpp +++ b/ogr/ogrsf_frmts/mapml/ogrmapmldataset.cpp @@ -141,9 +141,9 @@ class OGRMapMLWriterDataset final : public GDALPamDataset } OGRLayer *GetLayer(int idx) override; - 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; @@ -977,11 +977,14 @@ int OGRMapMLWriterDataset::TestCapability(const char *pszCap) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRMapMLWriterDataset::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRSIn, - OGRwkbGeometryType, char ** /* papszOptions */) +OGRLayer * +OGRMapMLWriterDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList /*papszOptions*/) { OGRSpatialReference oSRS_WGS84; + const auto poSRSIn = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; const OGRSpatialReference *poSRS = poSRSIn; if (poSRS == nullptr) { diff --git a/ogr/ogrsf_frmts/mem/ogr_mem.h b/ogr/ogrsf_frmts/mem/ogr_mem.h index f18b93ca4f2f..3f850a17bc85 100644 --- a/ogr/ogrsf_frmts/mem/ogr_mem.h +++ b/ogr/ogrsf_frmts/mem/ogr_mem.h @@ -196,10 +196,9 @@ class OGRMemDataSource CPL_NON_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; OGRErr DeleteLayer(int iLayer) override; int TestCapability(const char *) override; diff --git a/ogr/ogrsf_frmts/mem/ogrmemdatasource.cpp b/ogr/ogrsf_frmts/mem/ogrmemdatasource.cpp index d1f55d12274a..c4da9a8b0729 100644 --- a/ogr/ogrsf_frmts/mem/ogrmemdatasource.cpp +++ b/ogr/ogrsf_frmts/mem/ogrmemdatasource.cpp @@ -64,12 +64,17 @@ OGRMemDataSource::~OGRMemDataSource() /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRMemDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRSIn, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGRMemDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { // Create the layer object. + + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRSIn = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + OGRSpatialReference *poSRS = nullptr; if (poSRSIn) { diff --git a/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp b/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp index bc6245b9e24d..4677a7661a87 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp @@ -287,10 +287,10 @@ OGRLayer *OGRTABDataSource::GetLayer(int iLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRTABDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRSIn, - OGRwkbGeometryType /* eGeomTypeIn */, - char **papszOptions) +OGRLayer * +OGRTABDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefnIn, + CSLConstList papszOptions) { if (!GetUpdate()) @@ -300,6 +300,9 @@ OGRLayer *OGRTABDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } + const auto poSRSIn = + poGeomFieldDefnIn ? poGeomFieldDefnIn->GetSpatialRef() : nullptr; + // If it is a single file mode file, then we may have already // instantiated the low level layer. We would just need to // reset the coordinate system and (potentially) bounds. diff --git a/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.h b/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.h index c2c3160f1941..62fbf9b9fa4a 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.h +++ b/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.h @@ -81,9 +81,9 @@ class OGRTABDataSource : public OGRDataSource OGRLayer *GetLayer(int) override; int TestCapability(const char *) override; - OGRLayer *ICreateLayer(const char *, const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; char **GetFileList() override; diff --git a/ogr/ogrsf_frmts/mongodbv3/ogrmongodbv3driver.cpp b/ogr/ogrsf_frmts/mongodbv3/ogrmongodbv3driver.cpp index f4ceaa7e0059..0203b68d82db 100644 --- a/ogr/ogrsf_frmts/mongodbv3/ogrmongodbv3driver.cpp +++ b/ogr/ogrsf_frmts/mongodbv3/ogrmongodbv3driver.cpp @@ -108,9 +108,8 @@ class OGRMongoDBv3Dataset final : public GDALDataset void ReleaseResultSet(OGRLayer *poLayer) override; OGRLayer *ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) override; + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; OGRErr DeleteLayer(int iLayer) override; int TestCapability(const char *pszCap) override; @@ -2512,9 +2511,10 @@ bool OGRMongoDBv3Dataset::Open(GDALOpenInfo *poOpenInfo) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRMongoDBv3Dataset::ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRMongoDBv3Dataset::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (m_osDatabase.empty()) { @@ -2577,17 +2577,12 @@ OGRLayer *OGRMongoDBv3Dataset::ICreateLayer( poLayer->m_bCreateSpatialIndex = CPLFetchBool(papszOptions, "SPATIAL_INDEX", true); - if (eGType != wkbNone) + if (poGeomFieldDefn && poGeomFieldDefn->GetType() != wkbNone) { const char *pszGeometryName = CSLFetchNameValueDef(papszOptions, "GEOMETRY_NAME", "geometry"); - OGRGeomFieldDefn oFieldDefn(pszGeometryName, eGType); - OGRSpatialReference *poSRSClone = nullptr; - if (poSpatialRef) - poSRSClone = poSpatialRef->Clone(); - oFieldDefn.SetSpatialRef(poSRSClone); - if (poSRSClone) - poSRSClone->Release(); + OGRGeomFieldDefn oFieldDefn(poGeomFieldDefn); + oFieldDefn.SetName(pszGeometryName); poLayer->CreateGeomField(&oFieldDefn, FALSE); } diff --git a/ogr/ogrsf_frmts/mssqlspatial/ogr_mssqlspatial.h b/ogr/ogrsf_frmts/mssqlspatial/ogr_mssqlspatial.h index 85d290be1b99..5634f0f50ec5 100644 --- a/ogr/ogrsf_frmts/mssqlspatial/ogr_mssqlspatial.h +++ b/ogr/ogrsf_frmts/mssqlspatial/ogr_mssqlspatial.h @@ -653,10 +653,9 @@ class OGRMSSQLSpatialDataSource final : public OGRDataSource } virtual OGRErr DeleteLayer(int iLayer) 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/mssqlspatial/ogrmssqlspatialdatasource.cpp b/ogr/ogrsf_frmts/mssqlspatial/ogrmssqlspatialdatasource.cpp index c06cb7b2cf67..911ce94341ff 100644 --- a/ogr/ogrsf_frmts/mssqlspatial/ogrmssqlspatialdatasource.cpp +++ b/ogr/ogrsf_frmts/mssqlspatial/ogrmssqlspatialdatasource.cpp @@ -314,9 +314,10 @@ OGRErr OGRMSSQLSpatialDataSource::DeleteLayer(int iLayer) /* CreateLayer() */ /************************************************************************/ -OGRLayer *OGRMSSQLSpatialDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **papszOptions) +OGRLayer * +OGRMSSQLSpatialDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { char *pszTableName = nullptr; @@ -326,6 +327,10 @@ OGRLayer *OGRMSSQLSpatialDataSource::ICreateLayer( int nCoordDimension = 3; char *pszFIDColumnName = nullptr; + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + EndCopy(); /* determine the dimension */ diff --git a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp index d413002088ec..17acff0c908c 100644 --- a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp +++ b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp @@ -3366,9 +3366,9 @@ class OGRMVTWriterDataset final : public GDALDataset CPLErr Close() override; - 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; @@ -5824,12 +5824,14 @@ static bool ValidateMinMaxZoom(int nMinZoom, int nMaxZoom) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType, - char **papszOptions) +OGRLayer * +OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { OGRSpatialReference *poSRSClone = nullptr; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; if (poSRS) { poSRSClone = poSRS->Clone(); diff --git a/ogr/ogrsf_frmts/mysql/ogr_mysql.h b/ogr/ogrsf_frmts/mysql/ogr_mysql.h index 71fd55181490..44148857beae 100644 --- a/ogr/ogrsf_frmts/mysql/ogr_mysql.h +++ b/ogr/ogrsf_frmts/mysql/ogr_mysql.h @@ -303,10 +303,9 @@ class OGRMySQLDataSource 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/mysql/ogrmysqldatasource.cpp b/ogr/ogrsf_frmts/mysql/ogrmysqldatasource.cpp index 05fd14918c4f..09636fb357d3 100644 --- a/ogr/ogrsf_frmts/mysql/ogrmysqldatasource.cpp +++ b/ogr/ogrsf_frmts/mysql/ogrmysqldatasource.cpp @@ -1156,10 +1156,10 @@ OGRErr OGRMySQLDataSource::DeleteLayer(int iLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRMySQLDataSource::ICreateLayer(const char *pszLayerNameIn, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGRMySQLDataSource::ICreateLayer(const char *pszLayerNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { MYSQL_RES *hResult = nullptr; @@ -1174,6 +1174,10 @@ OGRLayer *OGRMySQLDataSource::ICreateLayer(const char *pszLayerNameIn, /* -------------------------------------------------------------------- */ InterruptLongResult(); + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + if (CPLFetchBool(papszOptions, "LAUNDER", true)) pszLayerName = LaunderName(pszLayerNameIn); else diff --git a/ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp b/ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp index 3820f41b725b..c7e8cf257575 100644 --- a/ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp +++ b/ogr/ogrsf_frmts/ngw/gdalngwdataset.cpp @@ -598,9 +598,8 @@ void OGRNGWDataset::AddRaster(const CPLJSONObject &oRasterJsonObj, * ICreateLayer */ OGRLayer *OGRNGWDataset::ICreateLayer(const char *pszNameIn, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!IsUpdateMode()) { @@ -609,6 +608,10 @@ OGRLayer *OGRNGWDataset::ICreateLayer(const char *pszNameIn, return nullptr; } + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + // Check permissions as we create new layer in memory and will create in // during SyncToDisk. FetchPermissions(); diff --git a/ogr/ogrsf_frmts/ngw/ogr_ngw.h b/ogr/ogrsf_frmts/ngw/ogr_ngw.h index 19777981ef04..bf4992eaff80 100644 --- a/ogr/ogrsf_frmts/ngw/ogr_ngw.h +++ b/ogr/ogrsf_frmts/ngw/ogr_ngw.h @@ -270,11 +270,9 @@ class OGRNGWDataset final : public GDALDataset } virtual OGRLayer *GetLayer(int) override; 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 CPLErr SetMetadata(char **papszMetadata, const char *pszDomain = "") override; diff --git a/ogr/ogrsf_frmts/oci/ogr_oci.h b/ogr/ogrsf_frmts/oci/ogr_oci.h index 329c9cef0c68..4a37e9f7379c 100644 --- a/ogr/ogrsf_frmts/oci/ogr_oci.h +++ b/ogr/ogrsf_frmts/oci/ogr_oci.h @@ -347,7 +347,7 @@ class OGROCIWritableLayer CPL_NON_FINAL : public OGROCILayer int bExactMatch) override; // following methods are not base class overrides - void SetOptions(char **); + void SetOptions(CSLConstList); void SetDimension(int); void SetLaunderFlag(int bFlag) @@ -596,10 +596,10 @@ class OGROCIDataSource final : public OGRDataSource OGRLayer *GetLayerByName(const char *pszName) override; virtual OGRErr DeleteLayer(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/oci/ogrocidatasource.cpp b/ogr/ogrsf_frmts/oci/ogrocidatasource.cpp index 4b45d965bb92..6bacb96f811e 100644 --- a/ogr/ogrsf_frmts/oci/ogrocidatasource.cpp +++ b/ogr/ogrsf_frmts/oci/ogrocidatasource.cpp @@ -474,12 +474,16 @@ void OGROCIDataSource::TruncateLayer(const char *pszLayerName) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGROCIDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGROCIDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + char *pszSafeLayerName = CPLStrdup(pszLayerName); poSession->CleanName(pszSafeLayerName); diff --git a/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp b/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp index 424caa7e7dc8..2457e38d5c13 100644 --- a/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp +++ b/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp @@ -231,7 +231,7 @@ void OGROCIWritableLayer::ReportTruncation(OGRFieldDefn *psFldDefn) /* Set layer creation or other options. */ /************************************************************************/ -void OGROCIWritableLayer::SetOptions(char **papszOptionsIn) +void OGROCIWritableLayer::SetOptions(CSLConstList papszOptionsIn) { CSLDestroy(papszOptions); diff --git a/ogr/ogrsf_frmts/ods/ogr_ods.h b/ogr/ogrsf_frmts/ods/ogr_ods.h index 81cff4c8e798..f572ecb51afb 100644 --- a/ogr/ogrsf_frmts/ods/ogr_ods.h +++ b/ogr/ogrsf_frmts/ods/ogr_ods.h @@ -247,10 +247,10 @@ class OGRODSDataSource final : public GDALDataset virtual int TestCapability(const char *) 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 CPLErr FlushCache(bool bAtClosing) override; diff --git a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp index 62a4ff213bd3..b32cba139b55 100644 --- a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp +++ b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp @@ -1524,9 +1524,10 @@ void OGRODSDataSource::AnalyseSettings() /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRODSDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference * /* poSRS */, - OGRwkbGeometryType /* eType */, char **papszOptions) +OGRLayer * +OGRODSDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn * /*poGeomFieldDefn*/, + CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ /* Verify we are in update mode. */ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h index d2b36d4e2b6b..3c2e3aa474aa 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h +++ b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h @@ -552,10 +552,10 @@ class OGROpenFileGDBDataSource final : public OGRDataSource virtual int TestCapability(const char *) 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; virtual char **GetFileList() override; diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index 27370a22c1a1..b2c26a294a0d 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -1242,9 +1242,10 @@ bool OGROpenFileGDBDataSource::Create(const char *pszName) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGROpenFileGDBDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, char **papszOptions) +OGRLayer * +OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (eAccess != GA_Update) return nullptr; @@ -1258,6 +1259,10 @@ OGRLayer *OGROpenFileGDBDataSource::ICreateLayer( return nullptr; } + auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + FileGDBTable oTable; if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false)) return nullptr; @@ -1841,7 +1846,7 @@ bool OGROpenFileGDBDataSource::AddRelationship( CPLStringList aosOptions; aosOptions.SetNameValue("FID", "RID"); OGRLayer *poMappingTable = ICreateLayer( - relationship->GetName().c_str(), nullptr, wkbNone, aosOptions); + relationship->GetName().c_str(), nullptr, aosOptions.List()); if (!poMappingTable) { failureReason = "Could not create mapping table " + diff --git a/ogr/ogrsf_frmts/parquet/ogr_parquet.h b/ogr/ogrsf_frmts/parquet/ogr_parquet.h index 2744f4e29c2b..6880801ede3e 100644 --- a/ogr/ogrsf_frmts/parquet/ogr_parquet.h +++ b/ogr/ogrsf_frmts/parquet/ogr_parquet.h @@ -368,9 +368,8 @@ class OGRParquetWriterDataset 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_PARQUET_H diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetwriterdataset.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetwriterdataset.cpp index 2ee361310384..545b45a420b8 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetwriterdataset.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetwriterdataset.cpp @@ -76,9 +76,10 @@ int OGRParquetWriterDataset::TestCapability(const char *pszCap) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRParquetWriterDataset::ICreateLayer( - const char *pszName, const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRParquetWriterDataset::ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (m_poLayer) { @@ -86,6 +87,11 @@ OGRLayer *OGRParquetWriterDataset::ICreateLayer( "Can write only one layer in a Parquet file"); return nullptr; } + + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + m_poLayer = std::make_unique( this, m_poMemoryPool.get(), m_poOutputStream, pszName); if (!m_poLayer->SetOptions(papszOptions, poSpatialRef, eGType)) diff --git a/ogr/ogrsf_frmts/pg/ogr_pg.h b/ogr/ogrsf_frmts/pg/ogr_pg.h index 557b1d84f231..bdb28ab62b7c 100644 --- a/ogr/ogrsf_frmts/pg/ogr_pg.h +++ b/ogr/ogrsf_frmts/pg/ogr_pg.h @@ -681,10 +681,9 @@ class OGRPGDataSource final : public OGRDataSource virtual CPLErr FlushCache(bool bAtClosing) 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/pg/ogrpgdatasource.cpp b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp index 8b6a8db7e528..c899982d57a4 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp @@ -1507,9 +1507,8 @@ OGRErr OGRPGDataSource::DeleteLayer(int iLayer) /************************************************************************/ OGRLayer *OGRPGDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { const char *pszGeomType = nullptr; @@ -1517,6 +1516,10 @@ OGRLayer *OGRPGDataSource::ICreateLayer(const char *pszLayerName, char *pszSchemaName = nullptr; int GeometryTypeFlags = 0; + auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + if (pszLayerName == nullptr) return nullptr; diff --git a/ogr/ogrsf_frmts/pgdump/ogr_pgdump.h b/ogr/ogrsf_frmts/pgdump/ogr_pgdump.h index d5b947302d70..200d1803aae7 100644 --- a/ogr/ogrsf_frmts/pgdump/ogr_pgdump.h +++ b/ogr/ogrsf_frmts/pgdump/ogr_pgdump.h @@ -263,10 +263,9 @@ class OGRPGDumpDataSource final : public GDALDataset } virtual OGRLayer *GetLayer(int) override; - virtual OGRLayer *ICreateLayer(const char *, - const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + virtual OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; virtual int TestCapability(const char *) override; diff --git a/ogr/ogrsf_frmts/pgdump/ogrpgdumpdatasource.cpp b/ogr/ogrsf_frmts/pgdump/ogrpgdumpdatasource.cpp index 12ba68be8e5a..50c5da7e2378 100644 --- a/ogr/ogrsf_frmts/pgdump/ogrpgdumpdatasource.cpp +++ b/ogr/ogrsf_frmts/pgdump/ogrpgdumpdatasource.cpp @@ -166,10 +166,10 @@ char *OGRPGCommonLaunderName(const char *pszSrcName, const char *pszDebugPrefix) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRPGDumpDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGRPGDumpDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (STARTS_WITH(pszLayerName, "pg")) @@ -179,6 +179,10 @@ OGRLayer *OGRPGDumpDataSource::ICreateLayer(const char *pszLayerName, "prefix"); } + auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + const bool bCreateTable = CPLFetchBool(papszOptions, "CREATE_TABLE", true); const bool bCreateSchema = CPLFetchBool(papszOptions, "CREATE_SCHEMA", true); @@ -186,9 +190,9 @@ OGRLayer *OGRPGDumpDataSource::ICreateLayer(const char *pszLayerName, CSLFetchNameValueDef(papszOptions, "DROP_TABLE", "IF_EXISTS"); int nGeometryTypeFlags = 0; - if (OGR_GT_HasZ((OGRwkbGeometryType)eType)) + if (OGR_GT_HasZ(eType)) nGeometryTypeFlags |= OGRGeometry::OGR_G_3D; - if (OGR_GT_HasM((OGRwkbGeometryType)eType)) + if (OGR_GT_HasM(eType)) nGeometryTypeFlags |= OGRGeometry::OGR_G_MEASURED; int nForcedGeometryTypeFlags = -1; diff --git a/ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h b/ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h index 3b689bcc229c..028f23e0f3c3 100644 --- a/ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h +++ b/ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h @@ -366,8 +366,9 @@ class OGRPMTilesWriterDataset final : public GDALDataset CPLErr Close() override; - OGRLayer *ICreateLayer(const char *, const OGRSpatialReference *, - OGRwkbGeometryType, char **) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; int TestCapability(const char *) override; }; diff --git a/ogr/ogrsf_frmts/pmtiles/ogrpmtileswriterdataset.cpp b/ogr/ogrsf_frmts/pmtiles/ogrpmtileswriterdataset.cpp index 2562c641c849..44ea4aa67e74 100644 --- a/ogr/ogrsf_frmts/pmtiles/ogrpmtileswriterdataset.cpp +++ b/ogr/ogrsf_frmts/pmtiles/ogrpmtileswriterdataset.cpp @@ -112,11 +112,12 @@ bool OGRPMTilesWriterDataset::Create(const char *pszFilename, /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRPMTilesWriterDataset::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSRS, - OGRwkbGeometryType eGeomType, char **papszOptions) +OGRLayer * +OGRPMTilesWriterDataset::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { - return m_poMBTilesWriterDataset->CreateLayer(pszLayerName, poSRS, eGeomType, + return m_poMBTilesWriterDataset->CreateLayer(pszLayerName, poGeomFieldDefn, papszOptions); } diff --git a/ogr/ogrsf_frmts/selafin/ogr_selafin.h b/ogr/ogrsf_frmts/selafin/ogr_selafin.h index 816d8dd56b55..dbfef05d73ef 100644 --- a/ogr/ogrsf_frmts/selafin/ogr_selafin.h +++ b/ogr/ogrsf_frmts/selafin/ogr_selafin.h @@ -161,11 +161,11 @@ class OGRSelafinDataSource final : public OGRDataSource return nLayers; } OGRLayer *GetLayer(int) override; - virtual OGRLayer * - ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRefP = nullptr, - OGRwkbGeometryType eGType = wkbUnknown, - char **papszOptions = nullptr) override; + + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + virtual OGRErr DeleteLayer(int) override; int TestCapability(const char *) override; void SetDefaultSelafinName(const char *pszNameIn) diff --git a/ogr/ogrsf_frmts/selafin/ogrselafindatasource.cpp b/ogr/ogrsf_frmts/selafin/ogrselafindatasource.cpp index fcbc4a0c6b69..7df69123b11a 100644 --- a/ogr/ogrsf_frmts/selafin/ogrselafindatasource.cpp +++ b/ogr/ogrsf_frmts/selafin/ogrselafindatasource.cpp @@ -587,10 +587,16 @@ int OGRSelafinDataSource::OpenTable(const char *pszFilename) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRSelafinDataSource::ICreateLayer( - const char *pszLayerName, const OGRSpatialReference *poSpatialRefP, - OGRwkbGeometryType eGType, char **papszOptions) +OGRLayer * +OGRSelafinDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) + { + auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRefP = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + CPLDebug("Selafin", "CreateLayer(%s,%s)", pszLayerName, (eGType == wkbPoint) ? "wkbPoint" : "wkbPolygon"); // Verify we are in update mode. @@ -602,6 +608,7 @@ OGRLayer *OGRSelafinDataSource::ICreateLayer( pszName, pszLayerName); return nullptr; } + // Check that new layer is a point or polygon layer if (eGType != wkbPoint) { diff --git a/ogr/ogrsf_frmts/shape/ogrshape.h b/ogr/ogrsf_frmts/shape/ogrshape.h index d827c22ef34f..a323c16a35ea 100644 --- a/ogr/ogrsf_frmts/shape/ogrshape.h +++ b/ogr/ogrsf_frmts/shape/ogrshape.h @@ -360,9 +360,9 @@ class OGRShapeDataSource final : public OGRDataSource OGRLayer *GetLayer(int) override; OGRLayer *GetLayerByName(const char *) override; - OGRLayer *ICreateLayer(const char *, const OGRSpatialReference * = nullptr, - OGRwkbGeometryType = wkbUnknown, - char ** = nullptr) override; + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; OGRLayer *ExecuteSQL(const char *pszStatement, OGRGeometry *poSpatialFilter, const char *pszDialect) override; diff --git a/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp b/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp index d1c346d140ff..8b8d432e1373 100644 --- a/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp +++ b/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp @@ -570,15 +570,19 @@ static CPLString LaunderLayerName(const char *pszLayerName) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRShapeDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGRShapeDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { // To ensure that existing layers are created. GetLayerCount(); + auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + /* -------------------------------------------------------------------- */ /* Check that the layer doesn't already exist. */ /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h b/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h index da32aa6a63f8..0300c629eb31 100644 --- a/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h +++ b/ogr/ogrsf_frmts/sqlite/ogr_sqlite.h @@ -730,10 +730,9 @@ class OGRSQLiteDataSource final : public OGRSQLiteBaseDataSource virtual std::pair GetLayerWithGetSpatialWhereByName(const char *pszName) override; - 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 DeleteLayer(int) override; virtual int TestCapability(const char *) override; diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp index bddea3f46272..0fa3c5a1fe8b 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp @@ -3471,10 +3471,10 @@ void OGRSQLiteDataSource::ReleaseResultSet(OGRLayer *poLayer) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRSQLiteDataSource::ICreateLayer(const char *pszLayerNameIn, - const OGRSpatialReference *poSRS, - OGRwkbGeometryType eType, - char **papszOptions) +OGRLayer * +OGRSQLiteDataSource::ICreateLayer(const char *pszLayerNameIn, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ @@ -3491,6 +3491,10 @@ OGRLayer *OGRSQLiteDataSource::ICreateLayer(const char *pszLayerNameIn, return nullptr; } + const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSRS = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + if (m_bIsSpatiaLiteDB && eType != wkbNone) { // We need to catch this right now as AddGeometryColumn does not diff --git a/ogr/ogrsf_frmts/vdv/ogr_vdv.h b/ogr/ogrsf_frmts/vdv/ogr_vdv.h index 81ecb5c63785..76e48bd492c2 100644 --- a/ogr/ogrsf_frmts/vdv/ogr_vdv.h +++ b/ogr/ogrsf_frmts/vdv/ogr_vdv.h @@ -206,10 +206,11 @@ class OGRVDVDataSource final : public GDALDataset virtual int GetLayerCount() override; virtual OGRLayer *GetLayer(int) override; - virtual OGRLayer *ICreateLayer(const char *pszLayerName, - const OGRSpatialReference * /*poSpatialRef*/, - OGRwkbGeometryType /*eGType*/, - char **papszOptions) override; + + OGRLayer *ICreateLayer(const char *pszName, + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) override; + virtual int TestCapability(const char *pszCap) override; void SetCurrentWriterLayer(OGRVDVWriterLayer *poLayer); diff --git a/ogr/ogrsf_frmts/vdv/ogrvdvdatasource.cpp b/ogr/ogrsf_frmts/vdv/ogrvdvdatasource.cpp index a792d4c949f5..e8a69c78a697 100644 --- a/ogr/ogrsf_frmts/vdv/ogrvdvdatasource.cpp +++ b/ogr/ogrsf_frmts/vdv/ogrvdvdatasource.cpp @@ -1618,7 +1618,7 @@ void OGRVDVWriterLayer::StopAsCurrentLayer() /* OGRVDVWriteHeader() */ /************************************************************************/ -static bool OGRVDVWriteHeader(VSILFILE *fpL, char **papszOptions) +static bool OGRVDVWriteHeader(VSILFILE *fpL, CSLConstList papszOptions) { bool bRet = true; const bool bStandardHeader = @@ -1673,7 +1673,7 @@ static bool OGRVDVWriteHeader(VSILFILE *fpL, char **papszOptions) OGRVDVEscapeString(pszFft).c_str()) > 0; } - for (char **papszIter = papszOptions; + for (CSLConstList papszIter = papszOptions; papszIter != nullptr && *papszIter != nullptr; papszIter++) { if (STARTS_WITH_CI(*papszIter, "HEADER_") && @@ -1758,8 +1758,8 @@ static bool OGRVDVLoadVDV452Tables(OGRVDV452Tables &oTables) OGRLayer * OGRVDVDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference * /*poSpatialRef*/, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { if (!m_bUpdate) return nullptr; @@ -1925,6 +1925,7 @@ OGRVDVDataSource::ICreateLayer(const char *pszLayerName, m_papoLayers[m_nLayerCount] = poLayer; m_nLayerCount++; + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; if (eGType == wkbPoint && poVDV452Table != nullptr && (EQUAL(pszLayerName, "STOP") || EQUAL(pszLayerName, "REC_ORT"))) { diff --git a/ogr/ogrsf_frmts/wasp/ogrwasp.h b/ogr/ogrsf_frmts/wasp/ogrwasp.h index 902bcf1b8826..4b7cddb400c5 100644 --- a/ogr/ogrsf_frmts/wasp/ogrwasp.h +++ b/ogr/ogrsf_frmts/wasp/ogrwasp.h @@ -200,11 +200,9 @@ class OGRWAsPDataSource final : public OGRDataSource virtual OGRLayer *GetLayer(int) override; virtual OGRLayer *GetLayerByName(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 int TestCapability(const char *) override; OGRErr Load(bool bSilent = false); diff --git a/ogr/ogrsf_frmts/wasp/ogrwaspdatasource.cpp b/ogr/ogrsf_frmts/wasp/ogrwaspdatasource.cpp index 954bfecd8dd7..e72e6c4506b2 100644 --- a/ogr/ogrsf_frmts/wasp/ogrwaspdatasource.cpp +++ b/ogr/ogrsf_frmts/wasp/ogrwaspdatasource.cpp @@ -187,10 +187,13 @@ OGRLayer *OGRWAsPDataSource::GetLayer(int iLayer) OGRLayer * OGRWAsPDataSource::ICreateLayer(const char *pszName, - const OGRSpatialReference *poSpatialRef, - OGRwkbGeometryType eGType, char **papszOptions) + const OGRGeomFieldDefn *poGeomFieldDefn, + CSLConstList papszOptions) { + const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto poSpatialRef = + poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; if (eGType != wkbLineString && eGType != wkbLineString25D && eGType != wkbMultiLineString && eGType != wkbMultiLineString25D && diff --git a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h index b03f154099d7..fb9356ee1b44 100644 --- a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h +++ b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h @@ -295,10 +295,10 @@ class OGRXLSXDataSource final : public GDALDataset virtual int TestCapability(const char *) 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 CPLErr FlushCache(bool bAtClosing) override; diff --git a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp index 9f730184bf1f..bd25e9514d2f 100644 --- a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp +++ b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp @@ -1801,10 +1801,10 @@ void OGRXLSXDataSource::AnalyseStyles(VSILFILE *fpStyles) /* ICreateLayer() */ /************************************************************************/ -OGRLayer *OGRXLSXDataSource::ICreateLayer(const char *pszLayerName, - const OGRSpatialReference * /*poSRS*/, - OGRwkbGeometryType /*eType*/, - char **papszOptions) +OGRLayer * +OGRXLSXDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn * /*poGeomFieldDefn*/, + CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ diff --git a/swig/include/Dataset.i b/swig/include/Dataset.i index f7ddfbbdabbb..7942fe2cd447 100644 --- a/swig/include/Dataset.i +++ b/swig/include/Dataset.i @@ -787,6 +787,20 @@ CPLErr AdviseRead( int xoff, int yoff, int xsize, int ysize, return layer; } + /* Note that datasources own their layers */ +#ifndef SWIGJAVA + %feature( "kwargs" ) CreateLayer; +#endif + OGRLayerShadow *CreateLayerFromGeomFieldDefn(const char* name, + OGRGeomFieldDefnShadow* geom_field, + char** options=0) { + OGRLayerShadow* layer = (OGRLayerShadow*) GDALDatasetCreateLayerFromGeomFieldDefn( self, + name, + geom_field, + options); + return layer; + } + #ifndef SWIGJAVA %feature( "kwargs" ) CopyLayer; #endif diff --git a/swig/include/gdal.i b/swig/include/gdal.i index 23df17027431..6212f749953a 100644 --- a/swig/include/gdal.i +++ b/swig/include/gdal.i @@ -110,6 +110,7 @@ typedef void OGRGeometryShadow; typedef struct OGRStyleTableHS OGRStyleTableShadow; typedef struct OGRFieldDomainHS OGRFieldDomainShadow; +typedef struct OGRGeomFieldDefnHS OGRGeomFieldDefnShadow; %} #endif /* #if defined(SWIGPYTHON) || defined(SWIGJAVA) */ diff --git a/swig/include/java/gdal_java.i b/swig/include/java/gdal_java.i index ab6940dcad6a..6063145c7adb 100644 --- a/swig/include/java/gdal_java.i +++ b/swig/include/java/gdal_java.i @@ -43,6 +43,7 @@ import org.gdal.ogr.StyleTable; import org.gdal.ogr.Layer; import org.gdal.ogr.Feature; import org.gdal.ogr.FieldDomain; +import org.gdal.ogr.GeomFieldDefn; %} %pragma(java) moduleimports=%{ @@ -61,6 +62,7 @@ import org.gdal.ogr.StyleTable; import org.gdal.ogr.Layer; import org.gdal.ogr.Feature; import org.gdal.ogr.FieldDomain; +import org.gdal.ogr.GeomFieldDefn; %} %typemap(javaimports) GDALDimensionHS %{ diff --git a/swig/include/java/ogr_java.i b/swig/include/java/ogr_java.i index 5d12786fe14c..cd229defac5e 100644 --- a/swig/include/java/ogr_java.i +++ b/swig/include/java/ogr_java.i @@ -155,6 +155,22 @@ import org.gdal.osr.SpatialReference; } %} +%typemap(javabody) OGRGeomFieldDefnShadow %{ + private boolean swigCMemOwn; + private long swigCPtr; + + public $javaclassname(long cPtr, boolean cMemoryOwn) { + if (cPtr == 0) + throw new RuntimeException(); + swigCMemOwn = cMemoryOwn; + swigCPtr = cPtr; + } + + public static long getCPtr($javaclassname obj) { + return (obj == null) ? 0 : obj.swigCPtr; + } +%} + %typemap(javacode) OGRLayerShadow %{ private Object parentReference; From 08cebe041fe61cf2cd7b2a4777a1c40c868ddaa9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 23:55:02 +0100 Subject: [PATCH 057/163] ogrinfo: output coordinate resolution in -json mode --- apps/data/ogrinfo_output.schema.json | 12 +++++ apps/ogrinfo_lib.cpp | 68 +++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) 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/ogrinfo_lib.cpp b/apps/ogrinfo_lib.cpp index ed1785c22636..0aad5ad84130 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 { From f91d8b457cef9db5efd87e787e8959e84bd22842 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 00:06:59 +0100 Subject: [PATCH 058/163] ogr2ogr: propagate input coordinate precision To be fixed by later commit to take into account potential SRS change --- apps/ogr2ogr_lib.cpp | 88 +++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index a3c404c53133..577fc2d1f2dc 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -4235,27 +4235,10 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, eGCreateLayerType = wkbNone; } - // 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 - // supports it) - if (eGType != wkbNone && anRequestedGeomFields.empty() && - nSrcGeomFieldCount >= 1 && - !poSrcFDefn->GetGeomFieldDefn(0)->IsNullable() && - pszDestCreationOptions != nullptr && - strstr(pszDestCreationOptions, "GEOMETRY_NULLABLE") != nullptr && - CSLFetchNameValue(m_papszLCO, "GEOMETRY_NULLABLE") == nullptr && - !m_bForceNullable) - { - papszLCOTemp = - CSLSetNameValue(papszLCOTemp, "GEOMETRY_NULLABLE", "NO"); - CPLDebug("GDALVectorTranslate", "Using GEOMETRY_NULLABLE=NO"); - } + OGRGeomCoordinatePrecision oCoordPrec; + std::string osGeomFieldName; + bool bGeomFieldNullable = true; - // Use source geometry field name as much as possible - if (eGType != wkbNone && pszDestCreationOptions && - strstr(pszDestCreationOptions, "GEOMETRY_NAME") != nullptr && - CSLFetchNameValue(m_papszLCO, "GEOMETRY_NAME") == nullptr) { int iSrcGeomField = -1; if (anRequestedGeomFields.empty() && @@ -4273,17 +4256,51 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, if (iSrcGeomField >= 0) { - const char *pszGFldName = - poSrcFDefn->GetGeomFieldDefn(iSrcGeomField)->GetNameRef(); + const auto poSrcGeomFieldDefn = + poSrcFDefn->GetGeomFieldDefn(iSrcGeomField); + oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + + bGeomFieldNullable = + CPL_TO_BOOL(poSrcGeomFieldDefn->IsNullable()); + + const char *pszGFldName = poSrcGeomFieldDefn->GetNameRef(); if (pszGFldName != nullptr && !EQUAL(pszGFldName, "") && poSrcFDefn->GetFieldIndex(pszGFldName) < 0) { - papszLCOTemp = CSLSetNameValue( - papszLCOTemp, "GEOMETRY_NAME", pszGFldName); + 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 + // supports it) + if (eGType != wkbNone && anRequestedGeomFields.empty() && + nSrcGeomFieldCount >= 1 && + !poSrcFDefn->GetGeomFieldDefn(0)->IsNullable() && + pszDestCreationOptions != nullptr && + strstr(pszDestCreationOptions, "GEOMETRY_NULLABLE") != nullptr && + CSLFetchNameValue(m_papszLCO, "GEOMETRY_NULLABLE") == nullptr && + !m_bForceNullable) + { + bGeomFieldNullable = false; + papszLCOTemp = + CSLSetNameValue(papszLCOTemp, "GEOMETRY_NULLABLE", "NO"); + CPLDebug("GDALVectorTranslate", "Using GEOMETRY_NULLABLE=NO"); + } + // Force FID column as 64 bit if the source feature has a 64 bit FID, // the target driver supports 64 bit FID and the user didn't set it // manually. @@ -4420,21 +4437,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 +4516,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(); } From def94f6a8022b8c43f2587727134935df9ad98eb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 00:53:19 +0100 Subject: [PATCH 059/163] OGRWktOptions: replace precision field with xyPrecision, zPrecision, mPrecision; add a nDimIdx parameter to OGRFormatDouble() --- ogr/ogr_geometry.h | 23 +++++----- ogr/ogr_p.h | 7 +-- .../arrow_common/ograrrowwriterlayer.hpp | 4 +- ogr/ogrutils.cpp | 46 +++++++++---------- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 735d699197a6..11f243f700ab 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -70,24 +70,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 diff --git a/ogr/ogr_p.h b/ogr/ogr_p.h index ef396798f45a..f3cad371af4c 100644 --- a/ogr/ogr_p.h +++ b/ogr/ogr_p.h @@ -86,12 +86,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 +100,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, 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/ogrutils.cpp b/ogr/ogrutils.cpp index 877108fbd06b..0b1383550a5b 100644 --- a/ogr/ogrutils.cpp +++ b/ogr/ogrutils.cpp @@ -204,12 +204,14 @@ void OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal, { OGRWktOptions opts; - opts.precision = nPrecision; + opts.xyPrecision = nPrecision; + opts.zPrecision = nPrecision; + opts.mPrecision = nPrecision; opts.format = (chConversionSpecifier == 'g' || chConversionSpecifier == 'G') ? OGRWktFormat::G : OGRWktFormat::F; - std::string s = OGRFormatDouble(dfVal, opts); + std::string s = OGRFormatDouble(dfVal, opts, 1); if (chDecimalSep != '\0' && chDecimalSep != '.') { auto pos = s.find('.'); @@ -229,7 +231,7 @@ void OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal, /// Simplified OGRFormatDouble that can be made to adhere to provided /// options. -std::string OGRFormatDouble(double val, const OGRWktOptions &opts) +std::string OGRFormatDouble(double val, const OGRWktOptions &opts, int nDimIdx) { // So to have identical cross platform representation. if (std::isinf(val)) @@ -249,7 +251,9 @@ std::string OGRFormatDouble(double val, const OGRWktOptions &opts) oss << std::uppercase; l_round = false; } - oss << std::setprecision(opts.precision); + oss << std::setprecision(nDimIdx < 3 ? opts.xyPrecision + : nDimIdx == 3 ? opts.zPrecision + : opts.mPrecision); oss << val; std::string sval = oss.str(); @@ -285,7 +289,7 @@ static bool isInteger(const std::string &s) } std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension, - OGRWktOptions opts) + const OGRWktOptions &opts) { std::string wkt; @@ -300,19 +304,18 @@ std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension, } else { - wkt = OGRFormatDouble(x, opts); + wkt = OGRFormatDouble(x, opts, 1); // ABELL - Why do we do special formatting? if (isInteger(wkt)) wkt += ".0"; wkt += ' '; - std::string yval = OGRFormatDouble(y, opts); + std::string yval = OGRFormatDouble(y, opts, 2); if (isInteger(yval)) yval += ".0"; wkt += yval; } - // Why do we always format Z with type G. if (nDimension == 3) { wkt += ' '; @@ -320,8 +323,7 @@ std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension, wkt += std::to_string(static_cast(z)); else { - opts.format = OGRWktFormat::G; - wkt += OGRFormatDouble(z, opts); + wkt += OGRFormatDouble(z, opts, 3); } } return wkt; @@ -349,7 +351,7 @@ void OGRMakeWktCoordinateM(char *pszTarget, double x, double y, double z, std::string OGRMakeWktCoordinateM(double x, double y, double z, double m, OGRBoolean hasZ, OGRBoolean hasM, - OGRWktOptions opts) + const OGRWktOptions &opts) { std::string wkt; if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) && @@ -361,35 +363,33 @@ std::string OGRMakeWktCoordinateM(double x, double y, double z, double m, } else { - wkt = OGRFormatDouble(x, opts); + wkt = OGRFormatDouble(x, opts, 1); if (isInteger(wkt)) wkt += ".0"; wkt += ' '; - std::string yval = OGRFormatDouble(y, opts); + std::string yval = OGRFormatDouble(y, opts, 2); if (isInteger(yval)) yval += ".0"; wkt += yval; } - // For some reason we always format Z and M as G-type - opts.format = OGRWktFormat::G; if (hasZ) { - /*if( opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z) ) - wkt += " " + std::to_string(static_cast(z)); - else*/ wkt += ' '; - wkt += OGRFormatDouble(z, opts); + if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z)) + wkt += std::to_string(static_cast(z)); + else + wkt += OGRFormatDouble(z, opts, 3); } if (hasM) { - /*if( opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(m) ) - wkt += " " + std::to_string(static_cast(m)); - else*/ wkt += ' '; - wkt += OGRFormatDouble(m, opts); + if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(m)) + wkt += std::to_string(static_cast(m)); + else + wkt += OGRFormatDouble(m, opts, 4); } return wkt; } From 7ef12ce0e6a301bc3b87eee4ba0328555cf15a47 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 22:48:48 +0100 Subject: [PATCH 060/163] Rename ogr_gml_read.py to ogr_gml.py --- autotest/ogr/{ogr_gml_read.py => ogr_gml.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename autotest/ogr/{ogr_gml_read.py => ogr_gml.py} (99%) diff --git a/autotest/ogr/ogr_gml_read.py b/autotest/ogr/ogr_gml.py similarity index 99% rename from autotest/ogr/ogr_gml_read.py rename to autotest/ogr/ogr_gml.py index 01e6d6525799..251568c742d6 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 # ############################################################################### From 4b4d204134ab2c89f774ea780233aab10d2de560 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 23:38:31 +0100 Subject: [PATCH 061/163] GML: add support for coordinate precision --- autotest/ogr/ogr_gml.py | 67 ++++++++ doc/source/drivers/vector/gml.rst | 29 ++++ ogr/ogr2gmlgeometry.cpp | 186 ++++++++++++++++------- ogr/ogrgeometry.cpp | 67 ++++++-- ogr/ogrsf_frmts/gml/gmlfeatureclass.cpp | 40 +++-- ogr/ogrsf_frmts/gml/gmlpropertydefn.cpp | 11 +- ogr/ogrsf_frmts/gml/gmlreader.cpp | 5 +- ogr/ogrsf_frmts/gml/gmlreader.h | 17 ++- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 131 ++++++++++++---- ogr/ogrsf_frmts/gml/ogrgmldriver.cpp | 1 + ogr/ogrsf_frmts/gml/ogrgmllayer.cpp | 69 +++++++-- ogr/ogrsf_frmts/gml/parsexsd.cpp | 59 ++++++- ogr/ogrsf_frmts/nas/nasreader.cpp | 2 +- 13 files changed, 540 insertions(+), 144 deletions(-) diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index 251568c742d6..dd1427619d3a 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -4242,3 +4242,70 @@ 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("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)")) + 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) + prec = geom_fld.GetCoordinatePrecision() + assert prec.GetXYResolution() == 1e-5 + assert prec.GetZResolution() == 1e-3 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/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/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index c5cf6f511a62..5aae7d2a4033 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -2953,21 +2953,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 behaviour. + *
    • + *
    • 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 behaviour. + *
    • *
    * * This method is the same as the C function OGR_G_ExportToGMLEx(). 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/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 9f4a9af45aff..d11e617d3896 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); } @@ -1970,7 +2006,7 @@ void OGRGMLDataSource::WriteTopElements() OGRLayer * OGRGMLDataSource::ICreateLayer(const char *pszLayerName, - const OGRGeomFieldDefn *poGeomFieldDefn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList /*papszOptions*/) { // Verify we are in update mode. @@ -1984,9 +2020,10 @@ OGRGMLDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } - const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto eType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; // Ensure name is safe as an element name. char *pszCleanLayerName = CPLStrdup(pszLayerName); @@ -2037,16 +2074,17 @@ OGRGMLDataSource::ICreateLayer(const char *pszLayerName, poLayer->GetLayerDefn()->SetGeomType(eType); if (eType != wkbNone) { - poLayer->GetLayerDefn()->GetGeomFieldDefn(0)->SetName( - "geometryProperty"); + auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); + poGeomFieldDefn->SetName("geometryProperty"); 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); @@ -2596,12 +2634,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/nas/nasreader.cpp b/ogr/ogrsf_frmts/nas/nasreader.cpp index a0a5bb25aa74..40bc5bafac7a 100644 --- a/ogr/ogrsf_frmts/nas/nasreader.cpp +++ b/ogr/ogrsf_frmts/nas/nasreader.cpp @@ -917,7 +917,7 @@ bool NASReader::PrescanForSchema(bool bGetExtents, bool /*bOnlyDetectSRS*/) eGType = wkbNone; poClass->GetGeometryProperty(0)->SetType( - (int)OGRMergeGeometryTypesEx( + OGRMergeGeometryTypesEx( eGType, poGeometry->getGeometryType(), TRUE)); // merge extents. From 4815f1ac34318eca813990c4ca11144f7f19fb4c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 20:46:54 +0100 Subject: [PATCH 062/163] Make OGRFeature::DumpReadableAsString() honor coordinate precision --- ogr/ogrfeature.cpp | 32 +++++++++++++++++++++--- ogr/ogrgeometry.cpp | 59 +++++++++++++++++++++++++++++---------------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/ogr/ogrfeature.cpp b/ogr/ogrfeature.cpp index 0d4a454ecbd9..66cdd49ef13f 100644 --- a/ogr/ogrfeature.cpp +++ b/ogr/ogrfeature.cpp @@ -5648,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()); } } } diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index 5aae7d2a4033..3b28893b1b56 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; } @@ -3000,12 +3017,12 @@ void OGR_G_FlattenTo2D(OGRGeometryH hGeom) *
  • 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 behaviour. + * 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 behaviour. + * 0 for the default behavior. *
  • * * From 34f3a4be346684efeec146e8d825294524e8ae39 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 22:33:01 +0100 Subject: [PATCH 063/163] GeoJSON: add support for coordinate precision --- autotest/ogr/ogr_geojson.py | 122 +++++++++++++++++- doc/source/drivers/vector/geojson.rst | 31 +++++ ogr/ogrsf_frmts/geojson/ogr_geojson.h | 9 +- .../geojson/ogrgeojsondatasource.cpp | 90 ++++++++++++- ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp | 1 + ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp | 2 +- ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp | 51 ++++++++ .../geojson/ogrgeojsonseqdriver.cpp | 4 +- .../geojson/ogrgeojsonwritelayer.cpp | 28 ++-- ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.cpp | 76 ++++++----- ogr/ogrsf_frmts/geojson/ogrgeojsonwriter.h | 6 +- .../jsonfg/ogrjsonfgwritelayer.cpp | 8 +- 12 files changed, 373 insertions(+), 55 deletions(-) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 8fad3db57da7..cb6e66c92850 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -4349,12 +4349,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 +5062,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/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/ogr/ogrsf_frmts/geojson/ogr_geojson.h b/ogr/ogrsf_frmts/geojson/ogr_geojson.h index f7b065beb543..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(); @@ -190,7 +198,6 @@ class OGRGeoJSONWriteLayer final : public OGRLayer bool bWriteFC_BBOX; OGREnvelope3D sEnvelopeLayer; - int nCoordPrecision_; int nSignificantFigures_; bool bRFC7946_; diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp index 6e6a73de72fb..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 @@ -256,7 +257,7 @@ OGRLayer *OGRGeoJSONDataSource::GetLayer(int nLayer) OGRLayer * OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, - const OGRGeomFieldDefn *poGeomFieldDefn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions) { if (nullptr == fpOut_) @@ -274,9 +275,10 @@ OGRGeoJSONDataSource::ICreateLayer(const char *pszNameIn, return nullptr; } - const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; const char *pszForeignMembersCollection = CSLFetchNameValue(papszOptions, "FOREIGN_MEMBERS_COLLECTION"); @@ -411,6 +413,12 @@ 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); @@ -532,6 +540,60 @@ 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_)); @@ -543,7 +605,27 @@ 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..c9fb2e221b0c 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); } /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp index 3aec64188e8e..bcb7c071197e 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp @@ -281,8 +281,10 @@ OGRGeoJSONSeqLayer::OGRGeoJSONSeqLayer( m_oWriteOptions.SetRFC7946Settings(); m_oWriteOptions.SetIDOptions(papszOptions); - m_oWriteOptions.nCoordPrecision = + m_oWriteOptions.nXYCoordPrecision = atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "7")); + m_oWriteOptions.nZCoordPrecision = + atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "3")); m_oWriteOptions.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); m_oWriteOptions.bAllowNonFiniteValues = CPLTestBool( diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp index 2dae4d434161..34ce6ef631ec 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonwritelayer.cpp @@ -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"); @@ -235,7 +244,8 @@ OGRErr OGRGeoJSONWriteLayer::ICreateFeature(OGRFeature *poFeature) // Special processing to detect and repair invalid geometries due to // coordinate precision. OGRGeometry *poOrigGeom = poFeature->GetGeometryRef(); - if (OGRGeometryFactory::haveGEOS() && nCoordPrecision_ >= 0 && poOrigGeom && + if (OGRGeometryFactory::haveGEOS() && + oWriteOptions_.nXYCoordPrecision >= 0 && poOrigGeom && wkbFlatten(poOrigGeom->getGeometryType()) != wkbPoint && IsValid(poOrigGeom)) { @@ -258,7 +268,7 @@ OGRErr OGRGeoJSONWriteLayer::ICreateFeature(OGRFeature *poFeature) } }; - CoordinateRoundingVisitor oVisitor(nCoordPrecision_); + CoordinateRoundingVisitor oVisitor(oWriteOptions_.nXYCoordPrecision); auto poNewGeom = poFeature == poFeatureToWrite ? poOrigGeom->clone() : poFeatureToWrite->GetGeometryRef(); 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/jsonfg/ogrjsonfgwritelayer.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp index 76be52597d6c..a2f249f28a76 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp @@ -60,14 +60,18 @@ OGRJSONFGWriteLayer::OGRJSONFGWriteLayer( osCoordRefSys_.find("[EPSG:4326]") != std::string::npos || osCoordRefSys_.find("[EPSG:4979]") != std::string::npos; - oWriteOptions_.nCoordPrecision = atoi(CSLFetchNameValueDef( + oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef( + papszOptions, "COORDINATE_PRECISION_GEOMETRY", "-1")); + oWriteOptions_.nZCoordPrecision = atoi(CSLFetchNameValueDef( papszOptions, "COORDINATE_PRECISION_GEOMETRY", "-1")); oWriteOptions_.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); oWriteOptions_.SetRFC7946Settings(); oWriteOptions_.SetIDOptions(papszOptions); - oWriteOptionsPlace_.nCoordPrecision = atoi( + oWriteOptionsPlace_.nXYCoordPrecision = atoi( + CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION_PLACE", "-1")); + oWriteOptionsPlace_.nZCoordPrecision = atoi( CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION_PLACE", "-1")); oWriteOptionsPlace_.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); From 0adb14ccdccc07ba75919eb3b5de4d5581b9c746 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 22:50:37 +0100 Subject: [PATCH 064/163] OGRGeometry::exportToJson() / OGR_G_ExportToJsonEx(): add support for specifying coordinate precision --- ogr/ogr_geometry.h | 2 +- ogr/ogrgeometry.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 11f243f700ab..df697391b2b4 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -465,7 +465,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; diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index 3b28893b1b56..913694ea81f1 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -3072,15 +3072,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)); } /************************************************************************/ From f616eb51bb4aa891db2c91736634b943c4555cbc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 22:51:09 +0100 Subject: [PATCH 065/163] ogrinfo: honour coordinate resolution for GeoJSON geometries in -json -features mode --- apps/ogrinfo_lib.cpp | 36 ++++++++++++++++++++++++-- autotest/utilities/test_ogrinfo_lib.py | 36 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/apps/ogrinfo_lib.cpp b/apps/ogrinfo_lib.cpp index 0aad5ad84130..1450cb307945 100644 --- a/apps/ogrinfo_lib.cpp +++ b/apps/ogrinfo_lib.cpp @@ -1560,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 @@ -1569,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) { @@ -1601,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/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 From e795d18aa868ca21342d827ec974b7d4ebac4465 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 1 Mar 2024 15:17:07 +0100 Subject: [PATCH 066/163] GeoJSONSeq: add support for coordinate precision --- autotest/ogr/ogr_geojsonseq.py | 92 ++++++++++++++++++ doc/source/drivers/vector/geojsonseq.rst | 21 +++- .../geojson/ogrgeojsonseqdriver.cpp | 96 ++++++++++++++++--- 3 files changed, 197 insertions(+), 12 deletions(-) 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/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/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp index bcb7c071197e..7ae861e30957 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp @@ -173,16 +173,15 @@ OGRLayer *OGRGeoJSONSeqDataSource::GetLayer(int nIndex) /* ICreateLayer() */ /************************************************************************/ -OGRLayer * -OGRGeoJSONSeqDataSource::ICreateLayer(const char *pszNameIn, - const OGRGeomFieldDefn *poGeomFieldDefn, - CSLConstList papszOptions) +OGRLayer *OGRGeoJSONSeqDataSource::ICreateLayer( + const char *pszNameIn, const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { if (!TestCapability(ODsCCreateLayer)) return nullptr; const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; std::unique_ptr poCT; if (poSRS == nullptr) @@ -219,9 +218,71 @@ OGRGeoJSONSeqDataSource::ICreateLayer(const char *pszNameIn, 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; } /************************************************************************/ @@ -281,10 +342,22 @@ OGRGeoJSONSeqLayer::OGRGeoJSONSeqLayer( m_oWriteOptions.SetRFC7946Settings(); m_oWriteOptions.SetIDOptions(papszOptions); - m_oWriteOptions.nXYCoordPrecision = - atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "7")); - m_oWriteOptions.nZCoordPrecision = - atoi(CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION", "3")); + + 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( @@ -978,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; From 32858d6c8224b87185cc9de1739694dbbdb3291b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 2 Mar 2024 19:19:15 +0100 Subject: [PATCH 067/163] jsonfg.rst: fix wrong directive for SINGLE_LAYER dsco --- doc/source/drivers/vector/jsonfg.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/drivers/vector/jsonfg.rst b/doc/source/drivers/vector/jsonfg.rst index 3f93dfa490ff..60d759f1f653 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 From b6b1aefa751d65150f1e1a865cfa343d040d9fbd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 1 Mar 2024 17:06:07 +0100 Subject: [PATCH 068/163] JSONFG: add support for coordinate precision --- autotest/ogr/ogr_jsonfg.py | 63 ++++- doc/source/drivers/vector/jsonfg.rst | 38 +++ ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp | 249 +++++++++++++++++- ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp | 1 + ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp | 29 ++ .../jsonfg/ogrjsonfgwritelayer.cpp | 10 +- 6 files changed, 378 insertions(+), 12 deletions(-) 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/doc/source/drivers/vector/jsonfg.rst b/doc/source/drivers/vector/jsonfg.rst index 60d759f1f653..1d33b08c1422 100644 --- a/doc/source/drivers/vector/jsonfg.rst +++ b/doc/source/drivers/vector/jsonfg.rst @@ -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/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp index a875984dbf39..02a62c918d75 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp @@ -33,6 +33,8 @@ #include "cpl_vsi_error.h" #include "cpl_vsi_virtual.h" +#include + /************************************************************************/ /* OGRJSONFGDataset::~OGRJSONFGDataset() */ /************************************************************************/ @@ -60,7 +62,77 @@ void OGRJSONFGDataset::FinishWriting() if (!EmitStartFeaturesIfNeededAndReturnIfFirstFeature()) VSIFPrintfL(fpOut_, "\n"); - VSIFPrintfL(fpOut_, "]\n}\n"); + VSIFPrintfL(fpOut_, "]"); + + // When we didn't know if there was a single layer, we omitted writing + // the coordinate precision at ICreateLayer() time. + // Now we can check if there was a single layer, or several layers with + // same precision setting, and write it when possible. + if (!bSingleOutputLayer_ && !apoLayers_.empty() && + apoLayers_.front()->GetLayerDefn()->GetGeomFieldCount() > 0) + { + const auto &oCoordPrec = apoLayers_.front() + ->GetLayerDefn() + ->GetGeomFieldDefn(0) + ->GetCoordinatePrecision(); + bool bSameGeomCoordPrec = + (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN || + oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN); + for (size_t i = 1; i < apoLayers_.size(); ++i) + { + if (apoLayers_[i]->GetLayerDefn()->GetGeomFieldCount() > 0) + { + const auto &oOtherCoordPrec = + apoLayers_[i] + ->GetLayerDefn() + ->GetGeomFieldDefn(0) + ->GetCoordinatePrecision(); + bSameGeomCoordPrec &= (oOtherCoordPrec.dfXYResolution == + oCoordPrec.dfXYResolution && + oOtherCoordPrec.dfZResolution == + oCoordPrec.dfZResolution); + } + } + if (bSameGeomCoordPrec) + { + if (oCoordPrec.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + VSIFPrintfL(fpOut_, + ",\n\"xy_coordinate_resolution_place\":%g", + oCoordPrec.dfXYResolution); + } + if (oCoordPrec.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + VSIFPrintfL(fpOut_, + ",\n\"z_coordinate_resolution_place\":%g", + oCoordPrec.dfZResolution); + } + + OGRSpatialReference oSRSWGS84; + oSRSWGS84.SetWellKnownGeogCS("WGS84"); + const auto oCoordPrecWGS84 = oCoordPrec.ConvertToOtherSRS( + apoLayers_.front()->GetSpatialRef(), &oSRSWGS84); + + if (oCoordPrecWGS84.dfXYResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + VSIFPrintfL(fpOut_, ",\n\"xy_coordinate_resolution\":%g", + oCoordPrecWGS84.dfXYResolution); + } + if (oCoordPrecWGS84.dfZResolution != + OGRGeomCoordinatePrecision::UNKNOWN) + { + VSIFPrintfL(fpOut_, ",\n\"z_coordinate_resolution\":%g", + oCoordPrecWGS84.dfZResolution); + } + } + } + + VSIFPrintfL(fpOut_, "\n}\n"); fpOut_->Flush(); } } @@ -510,7 +582,7 @@ bool OGRJSONFGDataset::EmitStartFeaturesIfNeededAndReturnIfFirstFeature() OGRLayer * OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, - const OGRGeomFieldDefn *poGeomFieldDefn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions) { if (nullptr == fpOut_) @@ -529,9 +601,10 @@ OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, return nullptr; } - const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; std::string osCoordRefSys; std::unique_ptr poCTToWGS84; @@ -637,12 +710,176 @@ OGRJSONFGDataset::ICreateLayer(const char *pszNameIn, "ellipsoid"); } + CPLStringList aosOptions(papszOptions); + + if (const char *pszCoordPrecisionGeom = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_GEOMETRY")) + { + double dfXYResolutionGeometry = + std::pow(10.0, -CPLAtof(pszCoordPrecisionGeom)); + double dfZResolutionGeometry = dfXYResolutionGeometry; + aosOptions.SetNameValue("XY_COORD_PRECISION_GEOMETRY", + pszCoordPrecisionGeom); + aosOptions.SetNameValue("Z_COORD_PRECISION_GEOMETRY", + pszCoordPrecisionGeom); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n", + dfXYResolutionGeometry); + if (poSRS && poSRS->GetAxesCount() == 3) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n", + dfZResolutionGeometry); + } + } + } + else if (!poSrcGeomFieldDefn && + CSLFetchNameValue(papszOptions, "SIGNIFICANT_FIGURES") == nullptr) + { + const int nXYPrecisionGeometry = 7; + const int nZPrecisionGeometry = 3; + aosOptions.SetNameValue("XY_COORD_PRECISION_GEOMETRY", + CPLSPrintf("%d", nXYPrecisionGeometry)); + aosOptions.SetNameValue("Z_COORD_PRECISION_GEOMETRY", + CPLSPrintf("%d", nZPrecisionGeometry)); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n", + std::pow(10.0, -double(nXYPrecisionGeometry))); + if (poSRS && poSRS->GetAxesCount() == 3) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n", + std::pow(10.0, -double(nZPrecisionGeometry))); + } + } + } + + double dfXYResolution = OGRGeomCoordinatePrecision::UNKNOWN; + double dfZResolution = OGRGeomCoordinatePrecision::UNKNOWN; + + if (const char *pszCoordPrecisionPlace = + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_PLACE")) + { + dfXYResolution = std::pow(10.0, -CPLAtof(pszCoordPrecisionPlace)); + dfZResolution = dfXYResolution; + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution_place\": %g,\n", + dfXYResolution); + if (poSRS && poSRS->GetAxesCount() == 3) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution_place\": %g,\n", + dfZResolution); + } + } + } + else if (poSrcGeomFieldDefn && + CSLFetchNameValue(papszOptions, "COORDINATE_PRECISION_PLACE") == + nullptr && + CSLFetchNameValue(papszOptions, "SIGNIFICANT_FIGURES") == nullptr) + { + const auto &oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + OGRSpatialReference oSRSWGS84; + oSRSWGS84.SetWellKnownGeogCS("WGS84"); + const auto oCoordPrecWGS84 = + oCoordPrec.ConvertToOtherSRS(poSRS, &oSRSWGS84); + + if (oCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfXYResolution = oCoordPrec.dfXYResolution; + aosOptions.SetNameValue( + "XY_COORD_PRECISION_PLACE", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + oCoordPrec.dfXYResolution))); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution_place\": %g,\n", + oCoordPrec.dfXYResolution); + } + + if (CSLFetchNameValue(papszOptions, + "COORDINATE_PRECISION_GEOMETRY") == nullptr) + { + const double dfXYResolutionGeometry = + oCoordPrecWGS84.dfXYResolution; + + aosOptions.SetNameValue( + "XY_COORD_PRECISION_GEOMETRY", + CPLSPrintf( + "%d", OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfXYResolutionGeometry))); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"xy_coordinate_resolution\": %g,\n", + dfXYResolutionGeometry); + } + } + } + + if (oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfZResolution = oCoordPrec.dfZResolution; + aosOptions.SetNameValue( + "Z_COORD_PRECISION_PLACE", + CPLSPrintf("%d", + OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfZResolution))); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution_place\": %g,\n", + dfZResolution); + } + + if (CSLFetchNameValue(papszOptions, + "COORDINATE_PRECISION_GEOMETRY") == nullptr) + { + const double dfZResolutionGeometry = + oCoordPrecWGS84.dfZResolution; + + aosOptions.SetNameValue( + "Z_COORD_PRECISION_GEOMETRY", + CPLSPrintf( + "%d", OGRGeomCoordinatePrecision::ResolutionToPrecision( + dfZResolutionGeometry))); + if (IsSingleOutputLayer()) + { + VSIFPrintfL(fpOut_, "\"z_coordinate_resolution\": %g,\n", + dfZResolutionGeometry); + } + } + } + } + auto poLayer = std::make_unique( pszNameIn, poSRS, std::move(poCTToWGS84), osCoordRefSys, eGType, - papszOptions, this); + aosOptions.List(), this); apoLayers_.emplace_back(std::move(poLayer)); - return apoLayers_.back().get(); + auto poLayerAdded = apoLayers_.back().get(); + if (eGType != wkbNone && + dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = + poLayerAdded->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfXYResolution = dfXYResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + if (eGType != wkbNone && + dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + auto poGeomFieldDefn = + poLayerAdded->GetLayerDefn()->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfZResolution = dfZResolution; + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + return poLayerAdded; } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp index 73d517d386a1..a00e47e6cccc 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp @@ -154,6 +154,7 @@ void RegisterOGRJSONFG() poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Boolean"); poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE"); poDriver->SetMetadataItem(GDAL_DCAP_FLUSHCACHE_CONSISTENT_STATE, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->pfnOpen = OGRJSONFGDriverOpen; poDriver->pfnIdentify = OGRJSONFGDriverIdentify; diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp index 56c6a1035572..ecbfd7a2787b 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgreader.cpp @@ -494,6 +494,35 @@ void OGRJSONFGReader::FinalizeBuildContext(LayerDefnBuildContext &oBuildContext, OGRFeatureDefn *poLayerDefn = poLayer->GetLayerDefn(); auto oTemporaryUnsealer(poLayerDefn->GetTemporaryUnsealer()); + if (poLayer->GetLayerDefn()->GetGeomType() != wkbNone) + { + OGRGeoJSONWriteOptions options; + + json_object *poXYRes = CPL_json_object_object_get( + poObject_, "xy_coordinate_resolution_place"); + if (poXYRes && (json_object_get_type(poXYRes) == json_type_double || + json_object_get_type(poXYRes) == json_type_int)) + { + auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfXYResolution = json_object_get_double(poXYRes); + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + + json_object *poZRes = CPL_json_object_object_get( + poObject_, "z_coordinate_resolution_place"); + if (poZRes && (json_object_get_type(poZRes) == json_type_double || + json_object_get_type(poZRes) == json_type_int)) + { + auto poGeomFieldDefn = poLayerDefn->GetGeomFieldDefn(0); + OGRGeomCoordinatePrecision oCoordPrec( + poGeomFieldDefn->GetCoordinatePrecision()); + oCoordPrec.dfZResolution = json_object_get_double(poZRes); + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + } + } + std::set oSetFieldNames; for (const auto &poFieldDefn : oBuildContext.apoFieldDefn) oSetFieldNames.insert(poFieldDefn->GetNameRef()); diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp index a2f249f28a76..416f7cb53363 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgwritelayer.cpp @@ -61,18 +61,18 @@ OGRJSONFGWriteLayer::OGRJSONFGWriteLayer( osCoordRefSys_.find("[EPSG:4979]") != std::string::npos; oWriteOptions_.nXYCoordPrecision = atoi(CSLFetchNameValueDef( - papszOptions, "COORDINATE_PRECISION_GEOMETRY", "-1")); - oWriteOptions_.nZCoordPrecision = atoi(CSLFetchNameValueDef( - papszOptions, "COORDINATE_PRECISION_GEOMETRY", "-1")); + papszOptions, "XY_COORD_PRECISION_GEOMETRY", "-1")); + oWriteOptions_.nZCoordPrecision = atoi( + CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_GEOMETRY", "-1")); oWriteOptions_.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); oWriteOptions_.SetRFC7946Settings(); oWriteOptions_.SetIDOptions(papszOptions); oWriteOptionsPlace_.nXYCoordPrecision = atoi( - CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION_PLACE", "-1")); + CSLFetchNameValueDef(papszOptions, "XY_COORD_PRECISION_PLACE", "-1")); oWriteOptionsPlace_.nZCoordPrecision = atoi( - CSLFetchNameValueDef(papszOptions, "COORDINATE_PRECISION_PLACE", "-1")); + CSLFetchNameValueDef(papszOptions, "Z_COORD_PRECISION_PLACE", "-1")); oWriteOptionsPlace_.nSignificantFigures = atoi(CSLFetchNameValueDef(papszOptions, "SIGNIFICANT_FIGURES", "-1")); From 6dbef10483b62d94b0b5aef1ad7406b14fa63b18 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 1 Mar 2024 18:57:27 +0100 Subject: [PATCH 069/163] OGRGeometry: add OGRGeomCoordinateBinaryPrecision and OGRwkbExportOptions structures with settings to quantize coordinates and use that in WKB export; add corresponding C API --- autotest/cpp/test_ogr.cpp | 399 ++++++++++++++++++++++++++++++++++ ogr/ogr_api.h | 15 ++ ogr/ogr_geometry.h | 93 ++++++-- ogr/ogr_p.h | 100 +++++++++ ogr/ogrcircularstring.cpp | 15 +- ogr/ogrcompoundcurve.cpp | 15 +- ogr/ogrcurvecollection.cpp | 26 ++- ogr/ogrcurvepolygon.cpp | 17 +- ogr/ogrgeometry.cpp | 259 +++++++++++++++++++++- ogr/ogrgeometrycollection.cpp | 32 ++- ogr/ogrlinearring.cpp | 31 ++- ogr/ogrlinestring.cpp | 45 +++- ogr/ogrpoint.cpp | 53 +++-- ogr/ogrpolygon.cpp | 24 +- ogr/ogrpolyhedralsurface.cpp | 22 +- 15 files changed, 1022 insertions(+), 124 deletions(-) diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index 498abf8370ee..666b07a7f919 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 @@ -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.SetFromMetre(&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.SetFromMetre(&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.SetFromMetre(&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/ogr/ogr_api.h b/ogr/ogr_api.h index 83b0c8f34e5f..e8ac0046c7db 100644 --- a/ogr/ogr_api.h +++ b/ogr/ogr_api.h @@ -170,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 **); diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index df697391b2b4..56128cb7f5fe 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 @@ -306,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 */ /************************************************************************/ @@ -427,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 @@ -484,6 +526,8 @@ class CPL_DLL OGRGeometry double dfMaxAngleStepSizeDegrees = 0, const char *const *papszOptions = nullptr) const CPL_WARN_UNUSED_RESULT; + void roundCoordinatesIEEE754(const OGRGeomCoordinateBinaryPrecision &options); + // SFCGAL interfacing methods. //! @cond Doxygen_Suppress static sfcgal_geometry_t *OGRexportToSFCGAL(const OGRGeometry *poGeom); @@ -1101,8 +1145,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 */ @@ -1538,8 +1582,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 */ @@ -1774,8 +1819,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 @@ -1787,8 +1832,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; @@ -1881,8 +1926,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 */ @@ -2032,8 +2077,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); @@ -2136,8 +2181,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 */ @@ -2396,8 +2441,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 */ @@ -2579,8 +2624,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 */ @@ -2825,8 +2870,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 */ @@ -3287,8 +3332,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 f3cad371af4c..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; @@ -238,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/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/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index 913694ea81f1..acb22f97bf2e 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -1544,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. @@ -1572,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() */ @@ -1648,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 ); * @@ -7556,6 +7619,77 @@ 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, but will 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() */ /************************************************************************/ @@ -7777,3 +7911,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/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(); } From 711d1b766d763611e5ec782dfe4a5422f38356e0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 2 Mar 2024 17:28:23 +0100 Subject: [PATCH 070/163] GPKG: add support for coordinate precision --- autotest/ogr/ogr_gpkg.py | 98 +++++++++++++++ doc/source/drivers/vector/gpkg.rst | 28 +++++ ogr/ogrsf_frmts/gpkg/ogr_geopackage.h | 2 + .../gpkg/ogrgeopackagedatasource.cpp | 31 +++-- ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp | 2 + .../gpkg/ogrgeopackagetablelayer.cpp | 116 +++++++++++++++++- ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp | 11 +- ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.h | 1 + 8 files changed, 270 insertions(+), 19 deletions(-) diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 6d58c77795dd..93d23426290e 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -10140,3 +10140,101 @@ 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", [True, False]) +def test_ogr_gpkg_geom_coord_precision(tmp_vsimem, with_metadata): + + 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) + 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) + 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(-1.23456789, abs=1e-2) + assert g.GetM(0) != pytest.approx(-1.23456789, abs=1e-8) + + # 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) + 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(1.23456789, abs=1e-2) + 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/doc/source/drivers/vector/gpkg.rst b/doc/source/drivers/vector/gpkg.rst index 798192145868..49e93ac4f8e7 100644 --- a/doc/source/drivers/vector/gpkg.rst +++ b/doc/source/drivers/vector/gpkg.rst @@ -632,6 +632,34 @@ 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. +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`, as well as setting to zero the less-significant +bits which are not relevant for the requested precision. This can improve +further lossless compression stages, for example when putting a GeoPackage in +an archive. + +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/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h index 74a0ab3d627b..4367680810f0 100644 --- a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h +++ b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h @@ -646,6 +646,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; @@ -886,6 +887,7 @@ class OGRGeoPackageTableLayer final : public OGRGeoPackageLayer void SetCreationParameters(OGRwkbGeometryType eGType, const char *pszGeomColumnName, int bGeomNullable, OGRSpatialReference *poSRS, + const OGRGeomCoordinatePrecision &oCoordPrec, const char *pszFIDColumnName, const char *pszIdentifier, const char *pszDescription); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index ed0c70ebdf11..e6c4d5a92d28 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -6604,7 +6604,7 @@ OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) OGRLayer * GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, - const OGRGeomFieldDefn *poGeomFieldDefn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions) { /* -------------------------------------------------------------------- */ @@ -6620,9 +6620,10 @@ GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, return nullptr; } - const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; const auto poSpatialRef = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; if (!m_bHasGPKGGeometryColumns) { @@ -6680,6 +6681,12 @@ GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, 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 = @@ -6764,8 +6771,11 @@ GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, 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(), + pszFIDColumnName, pszIdentifier, + CSLFetchNameValue(papszOptions, "DESCRIPTION")); if (poSRS) { poSRS->Release(); @@ -8148,7 +8158,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); @@ -8213,8 +8223,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); @@ -8421,7 +8431,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); @@ -8975,7 +8985,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..e68d6fa37ccc 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp @@ -745,6 +745,8 @@ void RegisterOGRGeoPackage() GDAL_DMD_RELATIONSHIP_RELATED_TABLE_TYPES, "features media simple_attributes attributes tiles"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); + #ifdef ENABLE_SQL_GPKG_FORMAT poDriver->SetMetadataItem("ENABLE_SQL_GPKG_FORMAT", "YES"); #endif diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp index 9813599d8ca3..e9f24d9a21ef 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp @@ -248,7 +248,8 @@ OGRErr OGRGeoPackageTableLayer::FeatureBindParameters( if (poGeom) { size_t szWkb = 0; - GByte *pabyWkb = GPkgGeometryFromOGR(poGeom, m_iSrs, &szWkb); + GByte *pabyWkb = GPkgGeometryFromOGR(poGeom, m_iSrs, + &m_sBinaryPrecision, &szWkb); if (!pabyWkb) return OGRERR_FAILURE; int err = sqlite3_bind_blob(poStmt, nColCount++, pabyWkb, @@ -1426,6 +1427,63 @@ OGRErr OGRGeoPackageTableLayer::ReadTableDefinition() } } + // Look for geometry column coordinate precision in gpkg_metadata + if (m_poDS->HasMetadataTables() && m_poFeatureDefn->GetGeomFieldCount() > 0) + { + pszSQL = sqlite3_mprintf( + "SELECT md.metadata, mdr.column_name " + "FROM gpkg_metadata md " + "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id) " + "WHERE lower(mdr.table_name) = lower('%q') " + "AND md.md_standard_uri = 'http://gdal.org' " + "AND md.mime_type = 'text/xml' " + "AND mdr.reference_scope = 'column' " + "AND md.metadata LIKE 'GetDB(), pszSQL); + sqlite3_free(pszSQL); + + for (int i = 0; oResult && i < oResult->RowCount(); i++) + { + const char *pszMetadata = oResult->GetValue(0, i); + const char *pszColumn = oResult->GetValue(1, i); + if (pszMetadata && pszColumn) + { + const int iGeomCol = + m_poFeatureDefn->GetGeomFieldIndex(pszColumn); + if (iGeomCol >= 0) + { + auto psXMLNode = + CPLXMLTreeCloser(CPLParseXMLString(pszMetadata)); + if (psXMLNode) + { + OGRGeomCoordinatePrecision sCoordPrec; + if (const char *pszVal = CPLGetXMLValue( + psXMLNode.get(), "xy_resolution", nullptr)) + { + sCoordPrec.dfXYResolution = CPLAtof(pszVal); + } + if (const char *pszVal = CPLGetXMLValue( + psXMLNode.get(), "z_resolution", nullptr)) + { + sCoordPrec.dfZResolution = CPLAtof(pszVal); + } + if (const char *pszVal = CPLGetXMLValue( + psXMLNode.get(), "m_resolution", nullptr)) + { + sCoordPrec.dfMResolution = CPLAtof(pszVal); + } + m_poFeatureDefn->GetGeomFieldDefn(iGeomCol) + ->SetCoordinatePrecision(sCoordPrec); + m_sBinaryPrecision.SetFrom(sCoordPrec); + } + } + } + } + } + /* Update the columns string */ BuildColumns(); @@ -5535,8 +5593,9 @@ void OGRGeoPackageTableLayer::SetOpeningParameters( void OGRGeoPackageTableLayer::SetCreationParameters( OGRwkbGeometryType eGType, const char *pszGeomColumnName, int bGeomNullable, - OGRSpatialReference *poSRS, const char *pszFIDColumnName, - const char *pszIdentifier, const char *pszDescription) + OGRSpatialReference *poSRS, const OGRGeomCoordinatePrecision &oCoordPrec, + const char *pszFIDColumnName, const char *pszIdentifier, + const char *pszDescription) { m_bIsSpatial = eGType != wkbNone; m_bIsInGpkgContents = @@ -5557,6 +5616,49 @@ void OGRGeoPackageTableLayer::SetCreationParameters( m_iSrs = m_poDS->GetSrsId(*poSRS); oGeomFieldDefn.SetSpatialRef(poSRS); oGeomFieldDefn.SetNullable(bGeomNullable); + oGeomFieldDefn.SetCoordinatePrecision(oCoordPrec); + m_sBinaryPrecision.SetFrom(oCoordPrec); + + // Save coordinate precision in gpkg_metadata/gpkg_metadata_reference + if ((oCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN || + oCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN || + oCoordPrec.dfMResolution != OGRGeomCoordinatePrecision::UNKNOWN) && + (m_poDS->HasMetadataTables() || m_poDS->CreateMetadataTables())) + { + std::string osCoordPrecision = "GetDB(), pszSQL)); + sqlite3_free(pszSQL); + + const sqlite_int64 nFID = + sqlite3_last_insert_rowid(m_poDS->GetDB()); + pszSQL = sqlite3_mprintf( + "INSERT INTO gpkg_metadata_reference (reference_scope, " + "table_name, column_name, timestamp, md_file_id) VALUES " + "('column', '%q', '%q', %s, %d)", + m_pszTableName, pszGeomColumnName, + m_poDS->GetCurrentDateEscapedSQL().c_str(), + static_cast(nFID)); + CPL_IGNORE_RET_VAL(SQLCommand(m_poDS->GetDB(), pszSQL)); + sqlite3_free(pszSQL); + } + m_poFeatureDefn->AddGeomFieldDefn(&oGeomFieldDefn); } if (pszIdentifier) @@ -5881,7 +5983,8 @@ char **OGRGeoPackageTableLayer::GetMetadata(const char *pszDomain) return OGRLayer::GetMetadata(pszDomain); char *pszSQL = sqlite3_mprintf( - "SELECT md.metadata, md.md_standard_uri, md.mime_type " + "SELECT md.metadata, md.md_standard_uri, md.mime_type, " + "mdr.reference_scope " "FROM gpkg_metadata md " "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) " "WHERE lower(mdr.table_name) = lower('%q') ORDER BY md.id " @@ -5903,9 +6006,10 @@ char **OGRGeoPackageTableLayer::GetMetadata(const char *pszDomain) const char *pszMetadata = oResult->GetValue(0, i); const char *pszMDStandardURI = oResult->GetValue(1, i); const char *pszMimeType = oResult->GetValue(2, i); + const char *pszReferenceScope = oResult->GetValue(3, i); if (pszMetadata && pszMDStandardURI && pszMimeType && - EQUAL(pszMDStandardURI, "http://gdal.org") && - EQUAL(pszMimeType, "text/xml")) + pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") && + EQUAL(pszMimeType, "text/xml") && EQUAL(pszReferenceScope, "table")) { CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata); if (psXMLNode) diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp index 3163987e34af..70210463a029 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp @@ -242,13 +242,17 @@ const char *GPkgFieldFromOGR(OGRFieldType eType, OGRFieldSubType eSubType, */ GByte *GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, + const OGRGeomCoordinateBinaryPrecision *psPrecision, size_t *pnWkbLen) { CPLAssert(poGeometry != nullptr); GByte byFlags = 0; GByte byEnv = 1; - OGRwkbByteOrder eByteOrder = static_cast(CPL_IS_LSB); + OGRwkbExportOptions wkbExportOptions; + if (psPrecision) + wkbExportOptions.sPrecision = *psPrecision; + wkbExportOptions.eByteOrder = static_cast(CPL_IS_LSB); OGRErr err; OGRBoolean bPoint = (wkbFlatten(poGeometry->getGeometryType()) == wkbPoint); OGRBoolean bEmpty = poGeometry->IsEmpty(); @@ -313,7 +317,7 @@ GByte *GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, /* Byte order of header? */ /* Use native endianness */ - byFlags |= eByteOrder; + byFlags |= wkbExportOptions.eByteOrder; /* Write flags byte */ pabyWkb[3] = byFlags; @@ -350,7 +354,8 @@ GByte *GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, GByte *pabyPtr = pabyWkb + nHeaderLen; /* Use the wkbVariantIso for ISO SQL/MM output (differs for 3d geometry) */ - err = poGeometry->exportToWkb(eByteOrder, pabyPtr, wkbVariantIso); + wkbExportOptions.eWkbVariant = wkbVariantIso; + err = poGeometry->exportToWkb(pabyPtr, &wkbExportOptions); if (err != OGRERR_NONE) { CPLFree(pabyWkb); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.h b/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.h index 4d850e3b560b..19b07248994e 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.h +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.h @@ -59,6 +59,7 @@ OGRwkbGeometryType GPkgGeometryTypeToWKB(const char *pszGpkgType, bool bHasZ, bool bHasM); GByte *GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, + const OGRGeomCoordinateBinaryPrecision *psPrecision, size_t *pnWkbLen); OGRGeometry *GPkgGeometryToOGR(const GByte *pabyGpkg, size_t nGpkgLen, OGRSpatialReference *poSrs); From c54715cc2bc54e35967e193988af6774e27e4dee Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 2 Mar 2024 22:54:09 +0100 Subject: [PATCH 071/163] ogr2ogr: add options -xyRes, -zRes, -mRes and -unsetCoordPrecision --- apps/ogr2ogr_bin.cpp | 3 + apps/ogr2ogr_lib.cpp | 191 ++++++++++++++++++++++++- autotest/utilities/test_ogr2ogr_lib.py | 155 ++++++++++++++++++++ doc/source/programs/ogr2ogr.rst | 38 +++++ swig/include/python/gdal_python.i | 21 +++ 5 files changed, 407 insertions(+), 1 deletion(-) 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 577fc2d1f2dc..1ca8e121c21d 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 @@ -4258,7 +4276,13 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, { const auto poSrcGeomFieldDefn = poSrcFDefn->GetGeomFieldDefn(iSrcGeomField); - oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + if (!psOptions->bUnsetCoordPrecision) + { + oCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision() + .ConvertToOtherSRS( + poSrcGeomFieldDefn->GetSpatialRef(), + poOutputSRS); + } bGeomFieldNullable = CPL_TO_BOOL(poSrcGeomFieldDefn->IsNullable()); @@ -4301,6 +4325,101 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, CPLDebug("GDALVectorTranslate", "Using GEOMETRY_NULLABLE=NO"); } + if (psOptions->dfXYRes != OGRGeomCoordinatePrecision::UNKNOWN) + { + if (m_poDstDS->GetDriver()->GetMetadataItem( + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr) + { + CPLError(CE_Warning, CPLE_AppDefined, + "-xyRes specified, but driver does not expose the " + "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability"); + } + + oCoordPrec.dfXYResolution = psOptions->dfXYRes; + if (!psOptions->osXYResUnit.empty()) + { + 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 dfFactorDegToMetre = + poOutputSRS->GetSemiMajor(nullptr) * M_PI / 180; + oCoordPrec.dfXYResolution *= dfFactorDegToMetre; + } + else + { + // Checked at argument parsing time + CPLAssert(psOptions->osXYResUnit == "m"); + } + + OGRGeomCoordinatePrecision tmp; + tmp.SetFromMetre(poOutputSRS, oCoordPrec.dfXYResolution, 0, 0); + oCoordPrec.dfXYResolution = tmp.dfXYResolution; + } + } + + if (psOptions->dfZRes != OGRGeomCoordinatePrecision::UNKNOWN) + { + if (m_poDstDS->GetDriver()->GetMetadataItem( + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr) + { + 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) + { + 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.SetFromMetre(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, // the target driver supports 64 bit FID and the user didn't set it // manually. @@ -7309,6 +7428,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/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index b29a5ee33b0b..5cc79afe9f5f 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -2464,3 +2464,158 @@ 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() diff --git a/doc/source/programs/ogr2ogr.rst b/doc/source/programs/ogr2ogr.rst index 0cc31653c721..a5655cbab169 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,42 @@ 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 metre, millimeter or degree. + 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 metre 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/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 968ab566da26..764e2e95d69e 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -2849,6 +2849,10 @@ def VectorTranslateOptions(options=None, format=None, resolveDomains=False, skipFailures=False, limit=None, + xyRes=None, + zRes=None, + mRes=None, + setCoordPrecision=True, callback=None, callback_data=None): """ Create a VectorTranslateOptions() object that can be passed to @@ -2969,6 +2973,14 @@ def VectorTranslateOptions(options=None, format=None, whether to skip failures limit: maximum number of features to read per layer + xyRes: + Geometry X,Y coordinate resolution. Numeric value, or numeric value suffixed with " m", " mm" or "deg". + zRes: + Geometry Z coordinate resolution. Numeric value, or numeric value suffixed with " m" or " mm". + mRes: + Geometry M coordinate resolution. Numeric value. + setCoordPrecision: + Set to False to unset the geometry coordinate precision. callback: callback method callback_data: @@ -3149,6 +3161,15 @@ def VectorTranslateOptions(options=None, format=None, new_options += ['-skip'] if limit is not None: new_options += ['-limit', str(limit)] + if xyRes is not None: + new_options += ['-xyRes', str(xyRes)] + if zRes is not None: + new_options += ['-zRes', str(zRes)] + if mRes is not None: + new_options += ['-mRes', str(mRes)] + if setCoordPrecision is False: + new_options += ["-unsetCoordPrecision"] + if callback is not None: new_options += ['-progress'] From c9d6b721efe0c2ead2816e4f6b50b90162c59f3e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 2 Mar 2024 23:33:02 +0100 Subject: [PATCH 072/163] CSV: add support for coordinate precision --- autotest/ogr/ogr_csv.py | 39 +++++++ ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp | 15 ++- ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp | 1 + ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp | 123 ++++++++++++++++------- 4 files changed, 137 insertions(+), 41 deletions(-) diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 9008bec734ba..9e6502d08e3e 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -3016,6 +3016,45 @@ 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 + + ############################################################################### diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp index fcea67fe433f..62d2e1c4ebdb 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp @@ -919,7 +919,7 @@ bool OGRCSVDataSource::OpenTable(const char *pszFilename, OGRLayer * OGRCSVDataSource::ICreateLayer(const char *pszLayerName, - const OGRGeomFieldDefn *poGeomFieldDefn, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions) { // Verify we are in update mode. @@ -933,9 +933,10 @@ OGRCSVDataSource::ICreateLayer(const char *pszLayerName, return nullptr; } - const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; + const auto eGType = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; const auto poSpatialRef = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; // Verify that the datasource is a directory. VSIStatBufL sStatBuf; @@ -1125,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 { From 9d07b3cb57897f1c34880b6e2c07c0cb3d7ed0ac Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 2 Mar 2024 23:42:39 +0100 Subject: [PATCH 073/163] MIGRATION_GUIDE.TXT: update with RFC99 changes --- MIGRATION_GUIDE.TXT | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) 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 From 7481f9a3ed3161a8a4350e7e9dd6c69b6a2448d7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 4 Mar 2024 19:14:06 +0100 Subject: [PATCH 074/163] OpenFileGDB: add support for coordinate precision --- autotest/ogr/ogr_openfilegdb_write.py | 127 +++++++++++++ doc/source/drivers/vector/openfilegdb.rst | 24 +++ .../openfilegdb/filegdb_coordprec_read.h | 82 ++++++++ .../openfilegdb/filegdb_coordprec_write.h | 160 ++++++++++++++++ ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h | 2 +- .../ogropenfilegdbdatasource_write.cpp | 4 +- .../openfilegdb/ogropenfilegdbdrivercore.cpp | 1 + .../openfilegdb/ogropenfilegdblayer.cpp | 18 +- .../openfilegdb/ogropenfilegdblayer_write.cpp | 178 ++++++++---------- 9 files changed, 484 insertions(+), 112 deletions(-) create mode 100644 ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h create mode 100644 ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_write.h 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/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/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h new file mode 100644 index 000000000000..7728e4a15a13 --- /dev/null +++ b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: Implements Open FileGDB OGR driver. + * 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 FILEGDB_COORDPREC_READ_H +#define FILEGDB_COORDPREC_READ_H + +#include "cpl_minixml.h" + +#include "ogr_geomcoordinateprecision.h" + +/*************************************************************************/ +/* GDBGridSettingsToOGR() */ +/*************************************************************************/ + +/** Fill OGRGeomCoordinatePrecision from XML "psSpatialReference" node. + */ +static OGRGeomCoordinatePrecision +GDBGridSettingsToOGR(const CPLXMLNode *psSpatialReference) +{ + OGRGeomCoordinatePrecision oCoordPrec; + const char *pszXYScale = + CPLGetXMLValue(psSpatialReference, "XYScale", nullptr); + if (pszXYScale && CPLAtof(pszXYScale) > 0) + { + oCoordPrec.dfXYResolution = 1.0 / CPLAtof(pszXYScale); + } + const char *pszZScale = + CPLGetXMLValue(psSpatialReference, "ZScale", nullptr); + if (pszZScale && CPLAtof(pszZScale) > 0) + { + oCoordPrec.dfZResolution = 1.0 / CPLAtof(pszZScale); + } + const char *pszMScale = + CPLGetXMLValue(psSpatialReference, "MScale", nullptr); + if (pszMScale && CPLAtof(pszMScale) > 0) + { + oCoordPrec.dfMResolution = 1.0 / CPLAtof(pszMScale); + } + + CPLStringList aosSpecificOptions; + for (const CPLXMLNode *psChild = psSpatialReference->psChild; psChild; + psChild = psChild->psNext) + { + if (psChild->eType == CXT_Element) + { + const char *pszValue = CPLGetXMLValue(psChild, "", ""); + if (CPLGetValueType(pszValue) == CPL_VALUE_REAL) + pszValue = CPLSPrintf("%.15g", CPLAtof(pszValue)); + aosSpecificOptions.SetNameValue(psChild->pszValue, pszValue); + } + } + oCoordPrec.oFormatSpecificOptions["FileGeodatabase"] = + std::move(aosSpecificOptions); + return oCoordPrec; +} + +#endif /* FILEGDB_COORDPREC_READ_H */ diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_write.h b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_write.h new file mode 100644 index 000000000000..b00b10cdf8ce --- /dev/null +++ b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_write.h @@ -0,0 +1,160 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: Implements Open FileGDB OGR driver. + * 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 FILEGDB_COORDPREC_WRITE_H +#define FILEGDB_COORDPREC_WRITE_H + +#include "ogr_spatialref.h" +#include "ogr_geomcoordinateprecision.h" + +/*************************************************************************/ +/* GDBGridSettingsFromOGR() */ +/*************************************************************************/ + +/** Compute grid settings from coordinate precision of source geometry field + * and layer creation options. + * The "FileGeodatabase" key of the output oFormatSpecificOptions will be + * set with the values. + */ +static OGRGeomCoordinatePrecision +GDBGridSettingsFromOGR(const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList aosLayerCreationOptions) +{ + const auto poSRS = + poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr; + + double dfXOrigin; + double dfYOrigin; + double dfXYScale; + double dfZOrigin = -100000; + double dfMOrigin = -100000; + double dfMScale = 10000; + double dfXYTolerance; + // default tolerance is 1mm in the units of the coordinate system + double dfZTolerance = + 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("VERT_CS") : 1.0); + double dfZScale = 1 / dfZTolerance * 10; + double dfMTolerance = 0.001; + + if (poSRS == nullptr || poSRS->IsProjected()) + { + // default tolerance is 1mm in the units of the coordinate system + dfXYTolerance = + 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("PROJCS") : 1.0); + // default scale is 10x the tolerance + dfXYScale = 1 / dfXYTolerance * 10; + + // Ideally we would use the same X/Y origins as ArcGIS, but we need + // the algorithm they use. + dfXOrigin = -2147483647; + dfYOrigin = -2147483647; + } + else + { + dfXOrigin = -400; + dfYOrigin = -400; + dfXYScale = 1000000000; + dfXYTolerance = 0.000000008983153; + } + + const auto &oSrcCoordPrec = poSrcGeomFieldDefn->GetCoordinatePrecision(); + + if (oSrcCoordPrec.dfXYResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfXYScale = 1.0 / oSrcCoordPrec.dfXYResolution; + dfXYTolerance = oSrcCoordPrec.dfXYResolution / 10.0; + } + + if (oSrcCoordPrec.dfZResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfZScale = 1.0 / oSrcCoordPrec.dfZResolution; + dfZTolerance = oSrcCoordPrec.dfZResolution / 10.0; + } + + if (oSrcCoordPrec.dfMResolution != OGRGeomCoordinatePrecision::UNKNOWN) + { + dfMScale = 1.0 / oSrcCoordPrec.dfMResolution; + dfMTolerance = oSrcCoordPrec.dfMResolution / 10.0; + } + + const char *const paramNames[] = { + "XOrigin", "YOrigin", "XYScale", "ZOrigin", "ZScale", + "MOrigin", "MScale", "XYTolerance", "ZTolerance", "MTolerance"}; + double *pGridValues[] = { + &dfXOrigin, &dfYOrigin, &dfXYScale, &dfZOrigin, &dfZScale, + &dfMOrigin, &dfMScale, &dfXYTolerance, &dfZTolerance, &dfMTolerance}; + static_assert(CPL_ARRAYSIZE(paramNames) == CPL_ARRAYSIZE(pGridValues)); + + const auto oIterCoordPrecFileGeodatabase = + oSrcCoordPrec.oFormatSpecificOptions.find("FileGeodatabase"); + + /* + * Use coordinate precision layer creation options in priority. + * Otherwise use the settings from the "FileGeodatabase" entry in + * oSrcCoordPrec.oFormatSpecificOptions when set. + * Otherwise, use above defaults. + */ + CPLStringList aosCoordinatePrecisionOptions; + for (size_t i = 0; i < CPL_ARRAYSIZE(paramNames); i++) + { + const char *pszVal = CSLFetchNameValueDef( + aosLayerCreationOptions, paramNames[i], + oIterCoordPrecFileGeodatabase != + oSrcCoordPrec.oFormatSpecificOptions.end() + ? oIterCoordPrecFileGeodatabase->second.FetchNameValue( + paramNames[i]) + : nullptr); + if (pszVal) + { + *(pGridValues[i]) = CPLAtof(pszVal); + if (strstr(paramNames[i], "Scale") || + strstr(paramNames[i], "Tolerance")) + { + if (*(pGridValues[i]) <= 0) + { + CPLError(CE_Warning, CPLE_AppDefined, + "%s should be strictly greater than zero", + paramNames[i]); + } + } + } + aosCoordinatePrecisionOptions.SetNameValue( + paramNames[i], CPLSPrintf("%.15g", *(pGridValues[i]))); + } + + OGRGeomCoordinatePrecision oCoordPrec; + oCoordPrec.dfXYResolution = 1.0 / dfXYScale; + oCoordPrec.dfZResolution = 1.0 / dfZScale; + oCoordPrec.dfMResolution = 1.0 / dfMScale; + oCoordPrec.oFormatSpecificOptions["FileGeodatabase"] = + std::move(aosCoordinatePrecisionOptions); + + return oCoordPrec; +} + +#endif /* FILEGDB_COORDPREC_WRITE_H */ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h index 3c2e3aa474aa..8c96408425bf 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h +++ b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h @@ -210,7 +210,7 @@ class OGROpenFileGDBLayer final : public OGRLayer virtual ~OGROpenFileGDBLayer(); - bool Create(const OGRSpatialReference *poSRS); + bool Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn); void Close(); const std::string &GetFilename() const diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index b2c26a294a0d..6cf35031911b 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -1260,8 +1260,6 @@ OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName, } auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; - const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; FileGDBTable oTable; if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false)) @@ -1281,7 +1279,7 @@ OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName, auto poLayer = std::make_unique( this, osFilename.c_str(), pszLayerName, eType, papszOptions); - if (!poLayer->Create(poSRS)) + if (!poLayer->Create(poGeomFieldDefn)) return nullptr; if (m_bInTransaction) { diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdrivercore.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdrivercore.cpp index 394a6c2b9b76..e70dbc4ccba8 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdrivercore.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdrivercore.cpp @@ -206,6 +206,7 @@ void OGROpenFileGDBDriverSetCommonMetadata(GDALDriver *poDriver) "Coded Range"); poDriver->SetMetadataItem(GDAL_DMD_ALTER_GEOM_FIELD_DEFN_FLAGS, "Name SRS"); + poDriver->SetMetadataItem(GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION, "YES"); poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST, diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp index 8302cf69e249..dc2861e0d024 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp @@ -53,6 +53,7 @@ #include "ogrsf_frmts.h" #include "filegdbtable.h" #include "ogr_swq.h" +#include "filegdb_coordprec_read.h" /************************************************************************/ /* OGROpenFileGDBLayer() */ @@ -156,7 +157,7 @@ int OGROpenFileGDBLayer::BuildGeometryColumnGDBv10( CPLStripXMLNamespace(psTree, nullptr, TRUE); /* CPLSerializeXMLTreeToFile( psTree, "/dev/stderr" ); */ - CPLXMLNode *psInfo = CPLSearchXMLNode(psTree, "=DEFeatureClassInfo"); + const CPLXMLNode *psInfo = CPLSearchXMLNode(psTree, "=DEFeatureClassInfo"); if (psInfo == nullptr) psInfo = CPLSearchXMLNode(psTree, "=DETableInfo"); if (psInfo == nullptr) @@ -222,11 +223,12 @@ int OGROpenFileGDBLayer::BuildGeometryColumnGDBv10( auto poGeomFieldDefn = std::make_unique( nullptr, pszShapeFieldName, m_eGeomType); - CPLXMLNode *psGPFieldInfoExs = CPLGetXMLNode(psInfo, "GPFieldInfoExs"); + const CPLXMLNode *psGPFieldInfoExs = + CPLGetXMLNode(psInfo, "GPFieldInfoExs"); if (psGPFieldInfoExs) { - for (CPLXMLNode *psChild = psGPFieldInfoExs->psChild; - psChild != nullptr; psChild = psChild->psNext) + for (const CPLXMLNode *psChild = psGPFieldInfoExs->psChild; psChild; + psChild = psChild->psNext) { if (psChild->eType != CXT_Element) continue; @@ -241,6 +243,14 @@ int OGROpenFileGDBLayer::BuildGeometryColumnGDBv10( } } + const CPLXMLNode *psSpatialReference = + CPLGetXMLNode(psInfo, "SpatialReference"); + if (psSpatialReference) + { + poGeomFieldDefn->SetCoordinatePrecision( + GDBGridSettingsToOGR(psSpatialReference)); + } + OGRSpatialReference *poParentSRS = nullptr; if (!osParentDefinition.empty()) { diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp index ec6429b9550d..819b24554db3 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp @@ -53,6 +53,7 @@ #include "ogrsf_frmts.h" #include "filegdbtable.h" #include "filegdbtable_priv.h" +#include "filegdb_coordprec_write.h" /*************************************************************************/ /* StringToWString() */ @@ -400,7 +401,7 @@ OGROpenFileGDBLayer::GetLaunderedLayerName(const std::string &osNameOri) const /* Create() */ /***********************************************************************/ -bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) +bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn) { FileGDBTableGeometryType eTableGeomType = FGTGT_NONE; const auto eFlattenType = wkbFlatten(OGR_GT_GetLinear(m_eGeomType)); @@ -511,41 +512,6 @@ bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) SetDescription(m_poFeatureDefn->GetName()); m_poFeatureDefn->SetGeomType(wkbNone); m_poFeatureDefn->Reference(); - if (m_eGeomType != wkbNone) - { - auto poGeomFieldDefn = std::make_unique( - this, - m_aosCreationOptions.FetchNameValueDef("GEOMETRY_NAME", "SHAPE"), - m_eGeomType); - poGeomFieldDefn->SetNullable( - CPLTestBool(m_aosCreationOptions.FetchNameValueDef( - "GEOMETRY_NULLABLE", "YES"))); - - if (poSRS) - { - const char *const apszOptions[] = { - "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr}; - if (poFeatureDatasetSRS && - !poSRS->IsSame(poFeatureDatasetSRS.get(), apszOptions)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Layer CRS does not match feature dataset CRS"); - return false; - } - - auto poSRSClone = poSRS->Clone(); - poGeomFieldDefn->SetSpatialRef(poSRSClone); - poSRSClone->Release(); - } - else if (poFeatureDatasetSRS) - { - auto poSRSClone = poFeatureDatasetSRS->Clone(); - poGeomFieldDefn->SetSpatialRef(poSRSClone); - poSRSClone->Release(); - } - - m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn)); - } m_osThisGUID = OFGDBGenerateUUID(); @@ -623,8 +589,41 @@ bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) m_poFeatureDefn->AddFieldDefn(&oField); } - if (m_eGeomType != wkbNone) + if (m_eGeomType != wkbNone && poSrcGeomFieldDefn) { + const auto poSRS = poSrcGeomFieldDefn->GetSpatialRef(); + + auto poGeomFieldDefn = std::make_unique( + this, + m_aosCreationOptions.FetchNameValueDef("GEOMETRY_NAME", "SHAPE"), + m_eGeomType); + poGeomFieldDefn->SetNullable( + CPLTestBool(m_aosCreationOptions.FetchNameValueDef( + "GEOMETRY_NULLABLE", "YES"))); + + if (poSRS) + { + const char *const apszOptions[] = { + "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr}; + if (poFeatureDatasetSRS && + !poSRS->IsSame(poFeatureDatasetSRS.get(), apszOptions)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Layer CRS does not match feature dataset CRS"); + return false; + } + + auto poSRSClone = poSRS->Clone(); + poGeomFieldDefn->SetSpatialRef(poSRSClone); + poSRSClone->Release(); + } + else if (poFeatureDatasetSRS) + { + auto poSRSClone = poFeatureDatasetSRS->Clone(); + poGeomFieldDefn->SetSpatialRef(poSRSClone); + poSRSClone->Release(); + } + std::string osWKT; if (poSRS) { @@ -639,60 +638,29 @@ bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}"; } - double dfXOrigin; - double dfYOrigin; - double dfXYScale; - double dfZOrigin = -100000; - double dfMOrigin = -100000; - double dfMScale = 10000; - double dfXYTolerance; - // default tolerance is 1mm in the units of the coordinate system - double dfZTolerance = - 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("VERT_CS") : 1.0); - double dfZScale = 1 / dfZTolerance * 10; - double dfMTolerance = 0.001; - - if (poSRS == nullptr || poSRS->IsProjected()) - { - // default tolerance is 1mm in the units of the coordinate system - dfXYTolerance = - 0.001 * (poSRS ? poSRS->GetTargetLinearUnits("PROJCS") : 1.0); - // default scale is 10x the tolerance - dfXYScale = 1 / dfXYTolerance * 10; - - // Ideally we would use the same X/Y origins as ArcGIS, but we need - // the algorithm they use. - dfXOrigin = -2147483647; - dfYOrigin = -2147483647; - } - else - { - dfXOrigin = -400; - dfYOrigin = -400; - dfXYScale = 1000000000; - dfXYTolerance = 0.000000008983153; - } - - const char *const paramNames[] = { - "XOrigin", "YOrigin", "XYScale", "ZOrigin", "ZScale", - "MOrigin", "MScale", "XYTolerance", "ZTolerance", "MTolerance"}; - double *pGridValues[] = {&dfXOrigin, &dfYOrigin, &dfXYScale, - &dfZOrigin, &dfZScale, &dfMOrigin, - &dfMScale, &dfXYTolerance, &dfZTolerance, - &dfMTolerance}; - static_assert( - CPL_ARRAYSIZE(paramNames) == CPL_ARRAYSIZE(pGridValues), - "CPL_ARRAYSIZE(paramNames) == CPL_ARRAYSIZE(pGridValues)"); - - /* Convert any layer creation options available, use defaults otherwise - */ - for (size_t i = 0; i < CPL_ARRAYSIZE(paramNames); i++) - { - const char *pszVal = - m_aosCreationOptions.FetchNameValue(paramNames[i]); - if (pszVal) - *(pGridValues[i]) = CPLAtof(pszVal); - } + const auto oCoordPrec = GDBGridSettingsFromOGR( + poSrcGeomFieldDefn, m_aosCreationOptions.List()); + poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec); + const auto &oGridsOptions = + oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase")->second; + const double dfXOrigin = + CPLAtof(oGridsOptions.FetchNameValue("XOrigin")); + const double dfYOrigin = + CPLAtof(oGridsOptions.FetchNameValue("YOrigin")); + const double dfXYScale = + CPLAtof(oGridsOptions.FetchNameValue("XYScale")); + const double dfXYTolerance = + CPLAtof(oGridsOptions.FetchNameValue("XYTolerance")); + const double dfZOrigin = + CPLAtof(oGridsOptions.FetchNameValue("ZOrigin")); + const double dfZScale = CPLAtof(oGridsOptions.FetchNameValue("ZScale")); + const double dfZTolerance = + CPLAtof(oGridsOptions.FetchNameValue("ZTolerance")); + const double dfMOrigin = + CPLAtof(oGridsOptions.FetchNameValue("MOrigin")); + const double dfMScale = CPLAtof(oGridsOptions.FetchNameValue("MScale")); + const double dfMTolerance = + CPLAtof(oGridsOptions.FetchNameValue("MTolerance")); if (!m_poDS->GetExistingSpatialRef( osWKT, dfXOrigin, dfYOrigin, dfXYScale, dfZOrigin, dfZScale, @@ -702,21 +670,21 @@ bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) dfZOrigin, dfZScale, dfMOrigin, dfMScale, dfXYTolerance, dfZTolerance, dfMTolerance); } + // Will be patched later constexpr double dfSpatialGridResolution = 0; - auto poGeomField = - std::unique_ptr(new FileGDBGeomField( - m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef(), - std::string(), // alias - CPL_TO_BOOL(m_poFeatureDefn->GetGeomFieldDefn(0)->IsNullable()), - osWKT, dfXOrigin, dfYOrigin, dfXYScale, dfXYTolerance, - {dfSpatialGridResolution})); - poGeomField->SetZOriginScaleTolerance(dfZOrigin, dfZScale, - dfZTolerance); - poGeomField->SetMOriginScaleTolerance(dfMOrigin, dfMScale, - dfMTolerance); - - if (!m_poLyrTable->CreateField(std::move(poGeomField))) + auto poTableGeomField = std::make_unique( + poGeomFieldDefn->GetNameRef(), + std::string(), // alias + CPL_TO_BOOL(poGeomFieldDefn->IsNullable()), osWKT, dfXOrigin, + dfYOrigin, dfXYScale, dfXYTolerance, + std::vector{dfSpatialGridResolution}); + poTableGeomField->SetZOriginScaleTolerance(dfZOrigin, dfZScale, + dfZTolerance); + poTableGeomField->SetMOriginScaleTolerance(dfMOrigin, dfMScale, + dfMTolerance); + + if (!m_poLyrTable->CreateField(std::move(poTableGeomField))) { Close(); return false; @@ -725,6 +693,8 @@ bool OGROpenFileGDBLayer::Create(const OGRSpatialReference *poSRS) m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx(); m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter( m_poLyrTable->GetGeomField())); + + m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn)); } const std::string osFIDName = From 997c8db93bfd1d6961daad47b4a6c04f3b8c11d5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 4 Mar 2024 20:30:34 +0100 Subject: [PATCH 075/163] FileGDB: add support for coordinate precision --- autotest/ogr/ogr_fgdb.py | 101 ++++++++++++ doc/source/drivers/vector/filegdb.rst | 40 +++-- ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp | 13 +- ogr/ogrsf_frmts/filegdb/FGdbDriverCore.cpp | 1 + ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 144 +++++++----------- ogr/ogrsf_frmts/filegdb/ogr_fgdb.h | 8 +- .../openfilegdb/filegdb_coordprec_read.h | 6 +- 7 files changed, 191 insertions(+), 122 deletions(-) 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/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/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp index f06b6884b7ed..f0e0dacd0ae5 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDatasource.cpp @@ -695,19 +695,16 @@ OGRLayer *FGdbDataSource::GetLayer(int iLayer) /* See FGdbLayer::Create for creation options */ /************************************************************************/ -OGRLayer *FGdbDataSource::ICreateLayer(const char *pszLayerName, - const OGRGeomFieldDefn *poGeomFieldDefn, - CSLConstList papszOptions) +OGRLayer * +FGdbDataSource::ICreateLayer(const char *pszLayerName, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, + CSLConstList papszOptions) { if (!m_bUpdate || m_pGeodatabase == nullptr) return nullptr; - const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; - const auto poSRS = - poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : 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 6e6e5bafa70a..a65fdb8de11f 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, - CSLConstList 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,7 +2292,7 @@ static CPLXMLNode *XMLSpatialReference(const OGRSpatialReference *poSRS, bool FGdbLayer::CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, - const OGRSpatialReference *poSRS, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions) { /* XML node */ @@ -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, CSLConstList 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; @@ -3100,7 +3060,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 +3068,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 bda0905bad36..d8544e626730 100644 --- a/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h +++ b/ogr/ogrsf_frmts/filegdb/ogr_fgdb.h @@ -165,11 +165,11 @@ 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, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions); static bool CreateFeatureDataset(FGdbDataSource *pParentDataSource, const std::string &feature_dataset_name, - const OGRSpatialReference *poSRS, + const OGRGeomFieldDefn *poSrcGeomFieldDefn, CSLConstList papszOptions); // virtual const char *GetName(); @@ -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); diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h index 7728e4a15a13..2c7741fbecd4 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h +++ b/ogr/ogrsf_frmts/openfilegdb/filegdb_coordprec_read.h @@ -66,7 +66,11 @@ GDBGridSettingsToOGR(const CPLXMLNode *psSpatialReference) for (const CPLXMLNode *psChild = psSpatialReference->psChild; psChild; psChild = psChild->psNext) { - if (psChild->eType == CXT_Element) + if (psChild->eType == CXT_Element && + // The 3 below values are only generated by the FileGDB SDK + !EQUAL(psChild->pszValue, "WKID") && + !EQUAL(psChild->pszValue, "LatestWKID") && + !EQUAL(psChild->pszValue, "WKT")) { const char *pszValue = CPLGetXMLValue(psChild, "", ""); if (CPLGetValueType(pszValue) == CPL_VALUE_REAL) From 7936f1510d32062f138829e250e2ef02141c43ff Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 4 Mar 2024 20:30:58 +0100 Subject: [PATCH 076/163] FileGDB: remove warning 'Empty Spatial Reference' --- ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index a65fdb8de11f..4ff1a2bd733b 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -3047,11 +3047,6 @@ bool FGdbLayer::ParseGeometryDef(const 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; } From f424fac7b5441f6511fc7c62d62fbc64c03138cf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 4 Mar 2024 22:23:27 +0100 Subject: [PATCH 077/163] GML writer: honour geometry field name and isnullable passed to ICreateLayer() --- autotest/ogr/ogr_gml.py | 5 ++++- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index dd1427619d3a..83651bcc7fe7 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -4252,10 +4252,11 @@ 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("geometry", ogr.wkbUnknown) + 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() @@ -4306,6 +4307,8 @@ def test_ogr_gml_geom_coord_precision(tmp_vsimem): 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/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index d11e617d3896..dc7cda9e009d 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -2075,7 +2075,11 @@ OGRGMLDataSource::ICreateLayer(const char *pszLayerName, if (eType != wkbNone) { auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0); - poGeomFieldDefn->SetName("geometryProperty"); + 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(); From 95857daf7a823260a3f92e99fec222b752974239 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 4 Mar 2024 23:00:56 +0100 Subject: [PATCH 078/163] OGR VRT: add support for coordinate precision --- autotest/ogr/ogr_vrt.py | 28 +++++++++++++++++++++++++++ doc/source/drivers/vector/vrt.rst | 8 ++++++++ ogr/ogrsf_frmts/vrt/data/ogrvrt.xsd | 3 +++ ogr/ogrsf_frmts/vrt/ogr_vrt.h | 2 ++ ogr/ogrsf_frmts/vrt/ogrvrtlayer.cpp | 30 +++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+) 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/doc/source/drivers/vector/vrt.rst b/doc/source/drivers/vector/vrt.rst index b8149bdef518..05a9102f40f8 100644 --- a/doc/source/drivers/vector/vrt.rst +++ b/doc/source/drivers/vector/vrt.rst @@ -208,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/ogr/ogrsf_frmts/vrt/data/ogrvrt.xsd b/ogr/ogrsf_frmts/vrt/data/ogrvrt.xsd index 6adc7911cedb..d7458319a691 100644 --- a/ogr/ogrsf_frmts/vrt/data/ogrvrt.xsd +++ b/ogr/ogrsf_frmts/vrt/data/ogrvrt.xsd @@ -393,6 +393,9 @@ + + + diff --git a/ogr/ogrsf_frmts/vrt/ogr_vrt.h b/ogr/ogrsf_frmts/vrt/ogr_vrt.h index a4587c955b0f..8ed870f7b016 100644 --- a/ogr/ogrsf_frmts/vrt/ogr_vrt.h +++ b/ogr/ogrsf_frmts/vrt/ogr_vrt.h @@ -83,6 +83,8 @@ class OGRVRTGeomFieldProps OGREnvelope sStaticEnvelope; + OGRGeomCoordinatePrecision sCoordinatePrecision{}; + OGRVRTGeomFieldProps(); ~OGRVRTGeomFieldProps(); }; diff --git a/ogr/ogrsf_frmts/vrt/ogrvrtlayer.cpp b/ogr/ogrsf_frmts/vrt/ogrvrtlayer.cpp index 12fb32820457..4decb5cf644c 100644 --- a/ogr/ogrsf_frmts/vrt/ogrvrtlayer.cpp +++ b/ogr/ogrsf_frmts/vrt/ogrvrtlayer.cpp @@ -513,6 +513,34 @@ bool OGRVRTLayer::ParseGeometryField(CPLXMLNode *psNode, poProps->bNullable = CPLTestBool(CPLGetXMLValue(psNode, "nullable", "TRUE")); + if (GetSrcLayerDefn()->GetGeomFieldCount() == 1) + { + poProps->sCoordinatePrecision = + GetSrcLayerDefn()->GetGeomFieldDefn(0)->GetCoordinatePrecision(); + } + else if (poProps->eGeometryStyle == VGS_Direct && poProps->iGeomField >= 0) + { + poProps->sCoordinatePrecision = + GetSrcLayerDefn() + ->GetGeomFieldDefn(poProps->iGeomField) + ->GetCoordinatePrecision(); + } + if (const char *pszXYResolution = + CPLGetXMLValue(psNode, "XYResolution", nullptr)) + { + poProps->sCoordinatePrecision.dfXYResolution = CPLAtof(pszXYResolution); + } + if (const char *pszZResolution = + CPLGetXMLValue(psNode, "ZResolution", nullptr)) + { + poProps->sCoordinatePrecision.dfZResolution = CPLAtof(pszZResolution); + } + if (const char *pszMResolution = + CPLGetXMLValue(psNode, "MResolution", nullptr)) + { + poProps->sCoordinatePrecision.dfMResolution = CPLAtof(pszMResolution); + } + return true; } @@ -811,6 +839,8 @@ bool OGRVRTLayer::FullInitialize() apoGeomFieldProps[i]->eGeomType); oFieldDefn.SetSpatialRef(apoGeomFieldProps[i]->poSRS); oFieldDefn.SetNullable(apoGeomFieldProps[i]->bNullable); + oFieldDefn.SetCoordinatePrecision( + apoGeomFieldProps[i]->sCoordinatePrecision); poFeatureDefn->AddGeomFieldDefn(&oFieldDefn); } From f6f11fdc676df5416bdc7fb98dd414bd2eeb0d58 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 5 Mar 2024 00:00:14 +0100 Subject: [PATCH 079/163] Add OGRGeometry::SetPrecision() / OGR_G_SetPrecision(), as wrapper of GEOSGeom_setPrecision_r() --- autotest/ogr/ogr_geos.py | 10 +++++ ogr/ogr_api.h | 11 +++++ ogr/ogr_geometry.h | 2 + ogr/ogrgeometry.cpp | 92 ++++++++++++++++++++++++++++++++++++++++ swig/include/ogr.i | 11 +++++ 5 files changed, 126 insertions(+) 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/ogr/ogr_api.h b/ogr/ogr_api.h index e8ac0046c7db..ab090f7e8d37 100644 --- a/ogr/ogr_api.h +++ b/ogr/ogr_api.h @@ -295,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 */ diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 56128cb7f5fe..ac972c96b4b7 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -594,6 +594,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 diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index acb22f97bf2e..860d09874204 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -6144,6 +6144,98 @@ OGRGeometryH OGR_G_SimplifyPreserveTopology(OGRGeometryH hThis, OGRGeometry::FromHandle(hThis)->SimplifyPreserveTopology(dTolerance)); } +/************************************************************************/ +/* SetPrecision() */ +/************************************************************************/ + +/** Set the geometry's precision, rounding all its coordinates to the precision + * grid. + * + * 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. + * + * 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() */ /************************************************************************/ diff --git a/swig/include/ogr.i b/swig/include/ogr.i index 1bc57a73dda9..8d47cb68c729 100644 --- a/swig/include/ogr.i +++ b/swig/include/ogr.i @@ -532,6 +532,9 @@ typedef void retGetPoints; %constant char *OLMD_FID64 = "OLMD_FID64"; +%constant int GEOS_PREC_NO_TOPO = 1; +%constant int GEOS_PREC_KEEP_COLLAPSED = 2; + #else typedef int OGRErr; @@ -583,6 +586,9 @@ typedef int OGRErr; #define OLMD_FID64 "OLMD_FID64" +#define GEOS_PREC_NO_TOPO 1 +#define GEOS_PREC_KEEP_COLLAPSED 2 + #endif #if defined(SWIGCSHARP) || defined(SWIGJAVA) || defined(SWIGPYTHON) @@ -3654,6 +3660,11 @@ public: return (OGRGeometryShadow*) OGR_G_MakeValidEx(self, options); } + %newobject SetPrecision; + OGRGeometryShadow* SetPrecision(double gridSize, int flags = 0) { + return (OGRGeometryShadow*) OGR_G_SetPrecision(self, gridSize, flags); + } + %newobject Normalize; OGRGeometryShadow* Normalize() { return (OGRGeometryShadow*) OGR_G_Normalize(self); From 99f79434ce96359bec8dcb5f4493528c4c6ba5cb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 6 Mar 2024 13:50:07 +0100 Subject: [PATCH 080/163] OGRLayer::CreateFeature()/SetFeature(): honor OGR_APPLY_GEOM_SET_PRECISION=YES configuration option to call OGRGeometry::SetPrecision() --- autotest/ogr/ogr_csv.py | 34 +++++ doc/source/user/configoptions.rst | 167 ++++++++++++--------- gcore/gdaldataset.cpp | 39 ++++- ogr/ogrsf_frmts/generic/ogrlayer.cpp | 86 ++++++++--- ogr/ogrsf_frmts/generic/ogrlayer_private.h | 12 ++ 5 files changed, 240 insertions(+), 98 deletions(-) diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 9e6502d08e3e..60ee4122d6c6 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -3055,6 +3055,40 @@ def test_ogr_csv_geom_coord_precision(tmp_vsimem, geometry_format): 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/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/gcore/gdaldataset.cpp b/gcore/gdaldataset.cpp index c407bb78048c..8ffe1a75ef6d 100644 --- a/gcore/gdaldataset.cpp +++ b/gcore/gdaldataset.cpp @@ -4963,6 +4963,22 @@ OGRLayer *GDALDataset::CreateLayer(const char *pszName, \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. @@ -4980,12 +4996,7 @@ This method is the same as the C function GDALDatasetCreateLayerFromGeomFieldDef @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. 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. +or NULL if there is no geometry field. @param papszOptions a StringList of name=value options. Options are driver specific. @@ -5148,6 +5159,22 @@ OGRLayerH GDALDatasetCreateLayer(GDALDatasetH hDS, const char *pszName, \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. 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 From 3e360b52a5b1929fe4b08a36db872f58644313f7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 5 Mar 2024 00:11:41 +0100 Subject: [PATCH 081/163] ogr2ogr: run OGRGeometry::SetPrecision() when specifying -xyRes only on non-curve geometries, and if OGR_APPLY_GEOM_SET_PRECISION is not set to NO --- apps/ogr2ogr_lib.cpp | 37 ++++++++++++++++++++++++-- autotest/utilities/test_ogr2ogr_lib.py | 20 ++++++++++++++ doc/source/programs/ogr2ogr.rst | 7 +++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index 1ca8e121c21d..f25b08639d8b 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -3893,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; @@ -4328,11 +4329,13 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, if (psOptions->dfXYRes != OGRGeomCoordinatePrecision::UNKNOWN) { if (m_poDstDS->GetDriver()->GetMetadataItem( - GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr) + GDAL_DCAP_HONOR_GEOM_COORDINATE_PRECISION) == nullptr && + !OGRGeometryFactory::haveGEOS()) { CPLError(CE_Warning, CPLE_AppDefined, "-xyRes specified, but driver does not expose the " - "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability"); + "DCAP_HONOR_GEOM_COORDINATE_PRECISION capability, " + "and this build has no GEOS support"); } oCoordPrec.dfXYResolution = psOptions->dfXYRes; @@ -5789,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(); @@ -6378,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 = diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index 5cc79afe9f5f..1f1a6999e1e4 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -2619,3 +2619,23 @@ def test_ogr2ogr_lib_coordinate_precision(tmp_vsimem): 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/doc/source/programs/ogr2ogr.rst b/doc/source/programs/ogr2ogr.rst index a5655cbab169..1cdafa1a87dd 100644 --- a/doc/source/programs/ogr2ogr.rst +++ b/doc/source/programs/ogr2ogr.rst @@ -265,6 +265,13 @@ output coordinate system or even reprojecting the features during translation. 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 metre, 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. From 95e6d2c38a7965c8e9f0a76f8b7d0b85827d0b7d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 6 Mar 2024 15:10:03 +0100 Subject: [PATCH 082/163] Add a OGRGeometry::roundCoordinates() method --- ogr/ogr_geometry.h | 4 ++- ogr/ogrgeometry.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index ac972c96b4b7..8413e7a206c5 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -526,7 +526,9 @@ class CPL_DLL OGRGeometry double dfMaxAngleStepSizeDegrees = 0, const char *const *papszOptions = nullptr) const CPL_WARN_UNUSED_RESULT; - void roundCoordinatesIEEE754(const OGRGeomCoordinateBinaryPrecision &options); + void roundCoordinates(const OGRGeomCoordinatePrecision &sPrecision); + void + roundCoordinatesIEEE754(const OGRGeomCoordinateBinaryPrecision &options); // SFCGAL interfacing methods. //! @cond Doxygen_Suppress diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index 860d09874204..476772b8a793 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -6144,12 +6144,89 @@ 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. + * 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 @@ -6204,7 +6281,9 @@ OGRGeometry *OGRGeometry::SetPrecision(UNUSED_IF_NO_GEOS double dfGridSize, /************************************************************************/ /** Set the geometry's precision, rounding all its coordinates to the precision - * grid. + * 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 @@ -7721,7 +7800,8 @@ OGRBoolean OGRGeometry::IsSFCGALCompatible() const * 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, but will have improved compressiblity. + * 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 From e91ce0693d1485a0bc715d8a51ab64a73de3f9bf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 6 Mar 2024 15:10:07 +0100 Subject: [PATCH 083/163] GPKG: only quantize geometries if DISCARD_COORD_LSB layer creation option is set to YES. Also add a UNDO_DISCARD_COORD_LSB_ON_READING layer creation option --- autotest/ogr/ogr_gpkg.py | 89 +++++++++++++++++-- doc/source/drivers/vector/gpkg.rst | 52 +++++++++-- ogr/ogrsf_frmts/gpkg/ogr_geopackage.h | 16 ++-- .../gpkg/ogrgeopackagedatasource.cpp | 4 + ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp | 7 ++ ogr/ogrsf_frmts/gpkg/ogrgeopackagelayer.cpp | 19 +++- .../gpkg/ogrgeopackagetablelayer.cpp | 62 +++++++++++-- 7 files changed, 218 insertions(+), 31 deletions(-) diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 93d23426290e..67fb786af1ba 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -10147,8 +10147,13 @@ def test_ogr_gpkg_creation_with_foreign_key_constraint_enabled(tmp_vsimem): @gdaltest.enable_exceptions() -@pytest.mark.parametrize("with_metadata", [True, False]) -def test_ogr_gpkg_geom_coord_precision(tmp_vsimem, with_metadata): +@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) @@ -10156,7 +10161,14 @@ def test_ogr_gpkg_geom_coord_precision(tmp_vsimem, with_metadata): prec = ogr.CreateGeomCoordinatePrecision() prec.Set(1e-5, 1e-3, 1e-2) geom_fld.SetCoordinatePrecision(prec) - lyr = ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + 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 @@ -10183,15 +10195,71 @@ def test_ogr_gpkg_geom_coord_precision(tmp_vsimem, with_metadata): 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) - assert g.GetX(0) != pytest.approx(1.23456789, abs=1e-8) + 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) - assert g.GetZ(0) != pytest.approx(9.87654321, abs=1e-8) + 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) - assert g.GetM(0) != pytest.approx(-1.23456789, abs=1e-8) + 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( @@ -10225,12 +10293,15 @@ def test_ogr_gpkg_geom_coord_precision(tmp_vsimem, with_metadata): 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) + 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) - assert g.GetZ(0) != pytest.approx(-9.87654321, abs=1e-8) + 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) - assert g.GetM(0) != pytest.approx(1.23456789, abs=1e-8) + if DISCARD_COORD_LSB == "YES": + assert g.GetM(0) != pytest.approx(1.23456789, abs=1e-8) ds.DeleteLayer(0) diff --git a/doc/source/drivers/vector/gpkg.rst b/doc/source/drivers/vector/gpkg.rst index 49e93ac4f8e7..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,8 @@ 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 ----------------------------- @@ -639,10 +655,36 @@ Geometry coordinate precision The GeoPackage driver supports reading and writing the geometry coordinate precision, using the :cpp:class:`OGRGeomCoordinatePrecision` settings of the -:cpp:class:`OGRGeomFieldDefn`, as well as setting to zero the less-significant -bits which are not relevant for the requested precision. This can improve -further lossless compression stages, for example when putting a GeoPackage in -an archive. +: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 @@ -651,7 +693,7 @@ specification: - gpkg_metadata.md_standard_uri = 'http://gdal.org' - gpkg_metadata.mime_type = 'text/xml' -- gpkg_metadata.metadata = '' +- gpkg_metadata.metadata = '' - gpkg_metadata_reference.reference_scope = 'column' - gpkg_metadata_reference.table_name = '{table_name}' - gpkg_metadata_reference.column_name = '{geometry_column_name}' diff --git a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h index 4367680810f0..f8c7688201aa 100644 --- a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h +++ b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h @@ -574,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; @@ -884,13 +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 OGRGeomCoordinatePrecision &oCoordPrec, - 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 e6c4d5a92d28..7b122f5e090a 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -6774,6 +6774,10 @@ GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName, 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) diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp index e68d6fa37ccc..e5e3043bad72 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedriver.cpp @@ -675,6 +675,13 @@ void RegisterOGRGeoPackage() "