Repository: PDAL/PDAL
Pull request #4697: Fix build with GDAL 3.11.0 debug (`GDAL_DEBUG`)
Author: login: dg0yt
Labels: name: bug
name: failed backport
Created: 2025-04-24T08:12:55Z | Merged: 2025-04-26T13:27:51Z

Build tested (modified for 2.8.4) locally with the vcpkg ports (and only `pdal[core]`). https://github.com/microsoft/vcpkg/pull/45153

Sharing this now as a draft.

There is a tricky mix of C and C++ API of GDAL in `pdal/private/gdal` - and there are two `reinterpret_cast` which let the compiler continue but come with smell.


Diff:
diff --git a/filters/OverlayFilter.cpp b/filters/OverlayFilter.cpp
index 5246779c27..590ddccfec 100644
--- a/filters/OverlayFilter.cpp
+++ b/filters/OverlayFilter.cpp
@@ -93,7 +93,7 @@ void OverlayFilter::prepared(PointTableRef table)
 void OverlayFilter::ready(PointTableRef table)
 {
     m_ds = OGRDSPtr(OGROpen(m_datasource.c_str(), 0, 0),
-            [](void *p){ if (p) ::OGR_DS_Destroy(p); });
+            [](OGRDSPtr::element_type *p){ if (p) ::OGR_DS_Destroy(p); });
     if (!m_ds)
         throwError("Unable to open data source '" + m_datasource + "'");
 
@@ -114,7 +114,7 @@ void OverlayFilter::ready(PointTableRef table)
         OGR_L_SetSpatialFilter(m_lyr, g.getOGRHandle());
     }
 
-    auto featureDeleter = [](void *p)
+    auto featureDeleter = [](OGRFeaturePtr::element_type *p)
     {
         if (p)
             ::OGR_F_Destroy(p);
diff --git a/filters/OverlayFilter.hpp b/filters/OverlayFilter.hpp
index 2a8f5705ab..141bf5132d 100644
--- a/filters/OverlayFilter.hpp
+++ b/filters/OverlayFilter.hpp
@@ -41,8 +41,15 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <type_traits>
 
-typedef void *OGRLayerH;
+// Get GDAL's forward decls if available
+// otherwise make our own
+#if __has_include(<gdal_fwd.h>)
+#include <gdal_fwd.h>
+#else
+using OGRLayerH = void *;
+#endif
 
 namespace pdal
 {
@@ -52,9 +59,13 @@ namespace gdal
     class ErrorHandler;
 }
 
+#if __has_include(<gdal_fwd.h>)
+typedef std::shared_ptr<std::remove_pointer<OGRDataSourceH>::type> OGRDSPtr;
+typedef std::shared_ptr<std::remove_pointer<OGRFeatureH>::type> OGRFeaturePtr;
+#else
 typedef std::shared_ptr<void> OGRDSPtr;
 typedef std::shared_ptr<void> OGRFeaturePtr;
-typedef std::shared_ptr<void> OGRGeometryPtr;
+#endif
 
 class Arg;
 
@@ -84,8 +95,6 @@ class PDAL_EXPORT OverlayFilter : public Filter, public Streamable
     OverlayFilter& operator=(const OverlayFilter&) = delete;
     OverlayFilter(const OverlayFilter&) = delete;
 
-    typedef std::shared_ptr<void> OGRDSPtr;
-
     OGRDSPtr m_ds;
     OGRLayerH m_lyr;
     std::string m_dimName;
diff --git a/io/TIndexReader.cpp b/io/TIndexReader.cpp
index 0d9c4caed2..551b3ea586 100644
--- a/io/TIndexReader.cpp
+++ b/io/TIndexReader.cpp
@@ -89,7 +89,7 @@ TIndexReader::FieldIndexes TIndexReader::getFields()
 {
     FieldIndexes indexes;
 
-    void *fDefn = OGR_L_GetLayerDefn(m_layer);
+    OGRFeatureDefnH fDefn = OGR_L_GetLayerDefn(m_layer);
 
     indexes.m_filename = OGR_FD_GetFieldIndex(fDefn,
         m_args->m_tileIndexColumnName.c_str());
diff --git a/io/TIndexReader.hpp b/io/TIndexReader.hpp
index 1188a1c54d..ccedfb61ff 100644
--- a/io/TIndexReader.hpp
+++ b/io/TIndexReader.hpp
@@ -41,6 +41,15 @@
 
 #include <pdal/JsonFwd.hpp>
 
+// Get GDAL's forward decls if available
+// otherwise make our own
+#if __has_include(<gdal_fwd.h>)
+#include <gdal_fwd.h>
+#else
+using OGRDataSourceH = void *;
+using OGRLayerH = void *;
+#endif
+
 namespace pdal
 {
 
@@ -88,8 +97,8 @@ class PDAL_EXPORT TIndexReader : public Reader, public Streamable
 
     std::unique_ptr<Args> m_args;
     std::unique_ptr<gdal::SpatialRef> m_out_ref;
-    void *m_dataset;
-    void *m_layer;
+    OGRDataSourceH m_dataset;
+    OGRLayerH m_layer;
 
     StageFactory m_factory;
     MergeFilter m_merge;
diff --git a/kernels/TIndexKernel.cpp b/kernels/TIndexKernel.cpp
index 1f43c75bba..82d6c81043 100644
--- a/kernels/TIndexKernel.cpp
+++ b/kernels/TIndexKernel.cpp
@@ -723,7 +723,7 @@ TIndexKernel::FieldIndexes TIndexKernel::getFields()
 {
     FieldIndexes indexes;
 
-    void *fDefn = OGR_L_GetLayerDefn(m_layer);
+    OGRFeatureDefnH fDefn = OGR_L_GetLayerDefn(m_layer);
 
     indexes.m_filename = OGR_FD_GetFieldIndex(fDefn,
         m_tileIndexColumnName.c_str());
diff --git a/kernels/TIndexKernel.hpp b/kernels/TIndexKernel.hpp
index a6d8930d73..72849f7785 100644
--- a/kernels/TIndexKernel.hpp
+++ b/kernels/TIndexKernel.hpp
@@ -40,6 +40,15 @@
 #include <pdal/SubcommandKernel.hpp>
 #include <pdal/util/FileUtils.hpp>
 
+// Get GDAL's forward decls if available
+// otherwise make our own
+#if __has_include(<gdal_fwd.h>)
+#include <gdal_fwd.h>
+#else
+using OGRDataSourceH = void *;
+using OGRLayerH = void *;
+#endif
+
 namespace pdal
 {
     class Polygon;
@@ -118,8 +127,8 @@ class PDAL_EXPORT TIndexKernel : public SubcommandKernel
     uint32_t m_sampleSize;
     std::string m_boundaryExpr;
 
-    void *m_dataset;
-    void *m_layer;
+    OGRDataSourceH m_dataset;
+    OGRLayerH m_layer;
     std::string m_tgtSrsString;
     std::string m_assignSrsString;
     bool m_fastBoundary;
diff --git a/kernels/private/density/OGR.hpp b/kernels/private/density/OGR.hpp
index 243a67addc..f61ad4ed7d 100644
--- a/kernels/private/density/OGR.hpp
+++ b/kernels/private/density/OGR.hpp
@@ -35,6 +35,15 @@
 
 #include <string>
 
+// Get GDAL's forward decls if available
+// otherwise make our own
+#if __has_include(<gdal_fwd.h>)
+#include <gdal_fwd.h>
+#else
+using OGRDataSourceH = void *;
+using OGRLayerH = void *;
+#endif
+
 namespace hexer
 {
     class BaseGrid;
@@ -45,9 +54,6 @@ namespace pdal
 
 class OGR
 {
-    using OGRDataSourceH = void *;
-    using OGRLayerH = void *;
-
 public:
     OGR(std::string const& filename, const std::string& srs,
         std::string driver = "ESRI Shapefile", std::string layerName ="");
diff --git a/pdal/Geometry.cpp b/pdal/Geometry.cpp
index c598e66463..fec6107a46 100644
--- a/pdal/Geometry.cpp
+++ b/pdal/Geometry.cpp
@@ -275,11 +275,11 @@ Geometry Geometry::getRing() const
 {
     throwNoGeos();
 
-    int count = OGR_G_GetGeometryCount(m_geom.get());
+    int count = OGR_G_GetGeometryCount(gdal::toHandle(m_geom.get()));
     if (count)
     {
 
-        OGRGeometryH ring = OGR_G_Clone(OGR_G_GetGeometryRef(m_geom.get(), 0));
+        OGRGeometryH ring = OGR_G_Clone(OGR_G_GetGeometryRef(gdal::toHandle(m_geom.get()), 0));
         OGRGeometryH linestring = OGR_G_ForceToLineString(ring);
 
         return Geometry(linestring, getSpatialReference());
diff --git a/pdal/SpatialReference.cpp b/pdal/SpatialReference.cpp
index cf7abaacf0..f5d9980a3c 100644
--- a/pdal/SpatialReference.cpp
+++ b/pdal/SpatialReference.cpp
@@ -53,22 +53,13 @@
 namespace
 {
 
-struct OGRDeleter
-{
-    void operator()(OGRSpatialReference* o)
-    {
-        OSRDestroySpatialReference(o);
-    };
-};
-
 using OGRScopedSpatialReference =
-    std::unique_ptr<OGRSpatialReference, OGRDeleter>;
+    std::unique_ptr<OGRSpatialReference>;
 
 OGRScopedSpatialReference ogrCreateSrs(std::string s = "", double epoch=0.0)
 {
     OGRScopedSpatialReference r(
-        static_cast<OGRSpatialReference*>(
-            OSRNewSpatialReference(s.size() ? s.c_str() : nullptr)));
+        new OGRSpatialReference(s.size() ? s.c_str() : nullptr));
     if (!pdal::Utils::compare_approx(epoch, 0.0f, 0.00001f))
     {
 #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,4,0)
@@ -129,7 +120,7 @@ bool SpatialReference::valid() const
 {
     OGRSpatialReference current(m_wkt.data());
 
-    return OSRValidate(&current) == OGRERR_NONE;
+    return current.Validate() == OGRERR_NONE;
 }
 
 
@@ -367,7 +358,7 @@ bool SpatialReference::equals(const SpatialReference& input) const
     if (!current || !other)
         return false;
 
-    int output = OSRIsSame(current.get(), other.get());
+    int output = current->IsSame(other.get());
 
     return (output == 1);
 }
@@ -398,7 +389,7 @@ bool SpatialReference::isGeographic() const
     if (!current)
         return false;
 
-    bool output = OSRIsGeographic(current.get());
+    bool output = current->IsGeographic();
     return output;
 }
 
@@ -409,7 +400,7 @@ bool SpatialReference::isGeocentric() const
     if (!current)
         return false;
 
-    bool output = OSRIsGeocentric(current.get());
+    bool output = current->IsGeocentric();
     return output;
 }
 
@@ -420,7 +411,7 @@ bool SpatialReference::isProjected() const
     if (!current)
         return false;
 
-    bool output = OSRIsProjected(current.get());
+    bool output = current->IsProjected();
     return output;
 }
 
@@ -429,7 +420,7 @@ std::vector<int> SpatialReference::getAxisOrdering() const
     std::vector<int> output;
     OGRScopedSpatialReference current = ogrCreateSrs(m_wkt, m_epoch);
     if (current)
-        output = current.get()->GetDataAxisToSRSAxisMapping();
+        output = current->GetDataAxisToSRSAxisMapping();
     return output;
 }
 
@@ -573,7 +564,7 @@ int SpatialReference::getUTMZone() const
         throw pdal_error("Could not fetch current SRS");
 
     int north(0);
-    int zone = OSRGetUTMZone(current.get(), &north);
+    int zone = current->GetUTMZone(&north);
     return (north ? 1 : -1) * zone;
 }
 
diff --git a/pdal/private/gdal/GDALUtils.cpp b/pdal/private/gdal/GDALUtils.cpp
index b9d295ce01..04b7c1761d 100644
--- a/pdal/private/gdal/GDALUtils.cpp
+++ b/pdal/private/gdal/GDALUtils.cpp
@@ -357,7 +357,7 @@ std::vector<Polygon> getPolygons(const OGRSpecOptions& ogr)
                 throw pdal_error("Unable to execute OGR SQL query.");
 
             SpatialRef sref;
-            sref.setFromLayer(poLayer);
+            sref.setFromLayer(reinterpret_cast<OGRLayerH>(poLayer));
             ds->ReleaseResultSet(poLayer);
 
             poly.update(ogr.geometry);
@@ -380,7 +380,7 @@ std::vector<Polygon> getPolygons(const OGRSpecOptions& ogr)
     std::vector<Polygon> polys;
     while ((poFeature = poLayer->GetNextFeature()) != NULL)
     {
-        polys.emplace_back(poFeature->GetGeometryRef());
+        polys.emplace_back(reinterpret_cast<OGRGeometryH>(poFeature->GetGeometryRef()));
         OGRFeature::DestroyFeature( poFeature );
     }
 
diff --git a/pdal/private/gdal/SpatialRef.cpp b/pdal/private/gdal/SpatialRef.cpp
index 049867e696..63bba9ee0b 100644
--- a/pdal/private/gdal/SpatialRef.cpp
+++ b/pdal/private/gdal/SpatialRef.cpp
@@ -106,9 +106,9 @@ bool SpatialRef::empty() const
     return wkt().empty();
 }
 
-void SpatialRef::newRef(void *v)
+void SpatialRef::newRef(OGRSpatialReferenceH v)
 {
-    m_ref = RefPtr(v, [](void* t){ OSRDestroySpatialReference(t); } );
+    m_ref = RefPtr(v, [](OGRSpatialReferenceH t){ OSRDestroySpatialReference(t); } );
 }
 
 } // namespace gdal
diff --git a/pdal/private/gdal/SpatialRef.hpp b/pdal/private/gdal/SpatialRef.hpp
index 4d2b34d51a..80a114cf0c 100644
--- a/pdal/private/gdal/SpatialRef.hpp
+++ b/pdal/private/gdal/SpatialRef.hpp
@@ -35,6 +35,7 @@
 #pragma once
 
 #include <memory>
+#include <type_traits>
 
 // Get GDAL's forward decls if available
 // otherwise make our own
@@ -42,6 +43,7 @@
 #include <gdal_fwd.h>
 #else
     using OGRLayerH = void *;
+    using OGRSpatialReferenceH = void *;
 #endif
 
 
@@ -53,7 +55,7 @@ namespace gdal
 
 class SpatialRef
 {
-    typedef std::shared_ptr<void> RefPtr;
+    typedef std::shared_ptr<std::remove_pointer<OGRSpatialReferenceH>::type> RefPtr;
 public:
     SpatialRef();
     SpatialRef(const std::string& srs);
@@ -65,7 +67,7 @@ class SpatialRef
     bool empty() const;
 
 private:
-    void newRef(void *v);
+    void newRef(OGRSpatialReferenceH v);
 
     RefPtr m_ref;
 };


Reviews:
user: login: hobu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T12:17:08Z
user: login: dg0yt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:59:05Z
user: login: abellgithub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:12:36Z
user: login: hobu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T14:15:28Z
user: login: abellgithub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T15:20:22Z
user: login: dg0yt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:54:52Z

Review comments:
diff_hunk: @@ -53,22 +53,13 @@
 namespace
 {
 
-struct OGRDeleter
path: pdal/SpatialReference.cpp
user: login: hobu
body: Was there a reason for removing this deleter? All of the SRS handling code expects these to clean up after themselves and is going to leak unless I'm misunderstanding something.
created_at: 2025-04-24T12:17:08Z
author_association: MEMBER
diff_hunk: @@ -53,22 +53,13 @@
 namespace
 {
 
-struct OGRDeleter
path: pdal/SpatialReference.cpp
user: login: dg0yt
body: The cleanup isn't going away. I just remove the need for the *custom*  deleter by letting `unique_ptr<OGRSpatialReference>` fully embrace the C++ nature of the managed pointer and use the *default* deleter. 

See also the following lines: I changed construction to `new OGRSpatialReference(...)` instead of `static_cast<OGRSpatialReference*>(OSRNewSpatialReference(...))`.

I did not change the managed type.
created_at: 2025-04-24T13:59:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,22 +53,13 @@
 namespace
 {
 
-struct OGRDeleter
path: pdal/SpatialReference.cpp
user: login: abellgithub
body: I get that OSRDestroySpatialReference ends up invoking `delete`, but should anyone care? I'm not sure the history of why there's all specialized deleter functions that don't do anything specialized.
created_at: 2025-04-24T14:12:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,22 +53,13 @@
 namespace
 {
 
-struct OGRDeleter
path: pdal/SpatialReference.cpp
user: login: hobu
body: > I'm not sure the history of why there's all specialized deleter functions that don't do anything specialized.

Because the interface predates working smart pointers? Or maybe there was specialized behavior in them at one time 🤷 
created_at: 2025-04-24T14:15:27Z
author_association: MEMBER
diff_hunk: @@ -367,7 +358,7 @@ bool SpatialReference::equals(const SpatialReference& input) const
     if (!current || !other)
         return false;
 
-    int output = OSRIsSame(current.get(), other.get());
+    int output = current.get()->IsSame(other.get());
path: pdal/SpatialReference.cpp
user: login: abellgithub
body: Shouldn't need all the `get` calls.
created_at: 2025-04-24T15:20:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -429,7 +420,7 @@ std::vector<int> SpatialReference::getAxisOrdering() const
     std::vector<int> output;
     OGRScopedSpatialReference current = ogrCreateSrs(m_wkt, m_epoch);
     if (current)
-        output = current.get()->GetDataAxisToSRSAxisMapping();
+        output = current->GetDataAxisToSRSAxisMapping();
path: pdal/SpatialReference.cpp
user: login: dg0yt
body: Note that this spot was already using C++ API.
created_at: 2025-04-25T07:54:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: hobu
created_at: 2025-04-26T13:28:08Z
body: The backport to `2.8-maintenance` failed:
```
The process '/usr/bin/git' failed with exit code 1
```
<details>
<summary>stderr</summary>

```
error: could not apply f43a7629c... Fix build with GDAL 3.11.0 GDAL_DEBUG
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

```
</details>

<details>
<summary>stdout</summary>

```
Auto-merging filters/OverlayFilter.hpp
[backport-4697-to-2.8-maintenance 31df40f2a] Use gdal_fwd for OGRLayerH if available
 Author: Howard Butler <hobu.inc@gmail.com>
 Date: Wed Apr 23 11:54:26 2025 -0500
 1 file changed, 7 insertions(+), 1 deletion(-)
Auto-merging filters/OverlayFilter.hpp
Auto-merging io/TIndexReader.cpp
Auto-merging io/TIndexReader.hpp
Auto-merging kernels/TIndexKernel.cpp
Auto-merging kernels/TIndexKernel.hpp
Auto-merging kernels/private/density/OGR.hpp
Auto-merging pdal/private/gdal/GDALUtils.cpp
CONFLICT (content): Merge conflict in pdal/private/gdal/GDALUtils.cpp

```
</details>

To backport manually, run these commands in your terminal:
```bash
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-2.8-maintenance 2.8-maintenance
# Navigate to the new working tree
cd .worktrees/backport-2.8-maintenance
# Create a new branch
git switch --create backport-4697-to-2.8-maintenance
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick 6eab0d87c9bd550d59c08fa4496872f63ac62062,f43a7629c437978b84abc4ffb497470808cb9053,4cb7d23071f0ccb4708214ef808c6623ea2c473e
# Push it to GitHub
git push --set-upstream origin backport-4697-to-2.8-maintenance
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-2.8-maintenance
```
Then, create a pull request where the `base` branch is `2.8-maintenance` and the `compare`/`head` branch is `backport-4697-to-2.8-maintenance`.
author_association: MEMBER
user: login: hobu
created_at: 2025-04-26T13:28:19Z
body: Thank you so much for providing this. I'm frustrated that I wasn't able to easily replicate the scenario.
author_association: MEMBER
user: login: dg0yt
created_at: 2025-04-27T12:46:17Z
body: FTR I did this locally in x64-linux.
author_association: CONTRIBUTOR