[geos-commits] [SCM] GEOS branch main updated. 1765c1222357af9c9d255ff536572f8e387470c6

git at osgeo.org git at osgeo.org
Mon Sep 9 09:29:48 PDT 2024


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, main has been updated
       via  1765c1222357af9c9d255ff536572f8e387470c6 (commit)
      from  1c0840a47ac3c189baff87ca46b105d108363e31 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 1765c1222357af9c9d255ff536572f8e387470c6
Author: Oreille <33065839+Oreilles at users.noreply.github.com>
Date:   Mon Sep 9 18:29:27 2024 +0200

    Add GEOSGeom_transformXYZ (#1157)
    
    * Add GEOSGeom_transformXYZ

diff --git a/capi/geos_c.cpp b/capi/geos_c.cpp
index 834f3ef51..39d34ff5e 100644
--- a/capi/geos_c.cpp
+++ b/capi/geos_c.cpp
@@ -663,6 +663,12 @@ extern "C" {
     }
 
 
+    Geometry*
+    GEOSGeom_transformXYZ(const GEOSGeometry* g, GEOSTransformXYZCallback callback, void* userdata) {
+        return GEOSGeom_transformXYZ_r(handle, g, callback, userdata);
+    }
+
+
 //-------------------------------------------------------------------
 // memory management functions
 //------------------------------------------------------------------
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 19f0dcb27..2dab4135a 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -284,7 +284,7 @@ typedef int (*GEOSDistanceCallback)(
 /**
 * Callback function for use in GEOSGeom_transformXY.
 * Allows custom function to be applied to x and y values for each coordinate
-* in a geometry.  Z values are unchanged by this function.
+* in a geometry.  Z and M values are unchanged by this function.
 * Extra data for the calculation can be passed via the userdata.
 *
 * \param x coordinate value to be updated
@@ -299,6 +299,26 @@ typedef int (*GEOSTransformXYCallback)(
     void* userdata);
 
 
+/**
+* Callback function for use in GEOSGeom_transformXYZ.
+* Allows custom function to be applied to x, y and z values for each coordinate
+* in a geometry.  M values are unchanged by this function.
+* Extra data for the calculation can be passed via the userdata.
+*
+* \param x coordinate value to be updated
+* \param y coordinate value to be updated
+* \param z coordinate value to be updated
+* \param userdata extra data for the calculation
+*
+* \return 1 if calculation succeeded, 0 on failure
+*/
+typedef int (*GEOSTransformXYZCallback)(
+    double* x,
+    double* y,
+    double* z,
+    void* userdata);
+
+
 /* ========== Interruption ========== */
 
 /**
@@ -1821,6 +1841,13 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXY_r(
     GEOSTransformXYCallback callback,
     void* userdata);
 
+/** \see GEOSGeom_transformXYZ */
+extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXYZ_r(
+    GEOSContextHandle_t handle,
+    const GEOSGeometry* g,
+    GEOSTransformXYZCallback callback,
+    void* userdata);
+
 /* ========= Algorithms ========= */
 
 /** \see GEOSOrientationIndex */
@@ -4696,7 +4723,7 @@ extern int GEOS_DLL GEOSHilbertCode(
 /**
 * Apply XY coordinate transform callback to all coordinates in a copy of
 * input geometry.  If the callback returns an error, returned geometry will be
-* NULL.  Z values, if present, are not modified by this function.
+* NULL.  Z and M values, if present, are not modified by this function.
 * \param[in] g Input geometry
 * \param[in] callback a function to be executed for each coordinate in the
                 geometry.  The callback takes 3 parameters: x and y coordinate
@@ -4712,6 +4739,25 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXY(
     GEOSTransformXYCallback callback,
     void* userdata);
 
+/**
+* Apply XYZ coordinate transform callback to all coordinates in a copy of
+* input geometry.  If the callback returns an error, returned geometry will be
+* NULL.  M values, if present, are not modified by this function.
+* \param[in] g Input geometry
+* \param[in] callback a function to be executed for each coordinate in the
+                geometry.  The callback takes 4 parameters: x, y and z coordinate
+                values to be updated and a void userdata pointer.
+* \param userdata an optional pointer to pe passed to 'callback' as an argument
+* \return a copy of the input geometry with transformed coordinates.
+* Caller must free with GEOSGeom_destroy().
+*
+* \since 3.13
+*/
+extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXYZ(
+    const GEOSGeometry* g,
+    GEOSTransformXYZCallback callback,
+    void* userdata);
+
 /**
 * Snaps the vertices and segments of the first geometry to vertices of the
 * second geometry within the given tolerance.
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index ef238229a..b77d872ba 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -1655,6 +1655,35 @@ extern "C" {
     }
 
 
+    Geometry*
+    GEOSGeom_transformXYZ_r(GEOSContextHandle_t handle, const GEOSGeometry* g, GEOSTransformXYZCallback callback, void* userdata) {
+
+        struct TransformFilter final: public geos::geom::CoordinateFilter {
+            TransformFilter(GEOSTransformXYZCallback p_callback,
+                            void* p_userdata) :
+                            m_callback(p_callback),
+                            m_userdata(p_userdata) {}
+
+            void filter_rw(Coordinate* c) const override {
+                if (!m_callback(&(c->x), &(c->y), &(c->z), m_userdata)) {
+                    throw std::runtime_error(std::string("Failed to transform coordinates."));
+                }
+            }
+
+            GEOSTransformXYZCallback m_callback;
+            void* m_userdata;
+        };
+
+        return execute(handle, [&]() {
+            TransformFilter filter(callback, userdata);
+            auto ret = g->clone();
+            ret->apply_rw(&filter);
+            ret->geometryChanged();
+            return ret.release();
+        });
+    }
+
+
 //-------------------------------------------------------------------
 // memory management functions
 //------------------------------------------------------------------
diff --git a/tests/unit/capi/GEOSGeom_transformXYZTest.cpp b/tests/unit/capi/GEOSGeom_transformXYZTest.cpp
new file mode 100644
index 000000000..c696b4da5
--- /dev/null
+++ b/tests/unit/capi/GEOSGeom_transformXYZTest.cpp
@@ -0,0 +1,333 @@
+//
+// Test Suite for C-API GEOSGeom_transformXYZ
+
+#include <tut/tut.hpp>
+// geos
+#include <geos_c.h>
+// std
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+
+#include "capi_test_utils.h"
+
+namespace tut {
+struct test_capi_geosgeom_transformxyz : public capitest::utility {};
+
+typedef test_group<test_capi_geosgeom_transformxyz> group;
+typedef group::object object;
+
+group test_capi_geosgeom_transformxyz_group("capi::GEOSGeom_transformXYZ");
+
+static int SCALE_2_3_4(double* x, double* y, double* z, void* userdata) {
+    (*x) *= 2;
+    (*y) *= 3;
+    (*z) *= 4;
+    (void)(userdata);  // make unused parameter warning go away
+    return 1;
+}
+
+// callback that doesn't update coordinates should return original values
+template <>
+template <>
+void object::test<1>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT (1 1 1)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(
+        geom,
+        [](double* x, double* y, double* z, void* userdata) {
+            (void)(x);         // make unused parameter warning go away
+            (void)(y);         // make unused parameter warning go away
+            (void)(z);         // make unused parameter warning go away
+            (void)(userdata);  // make unused parameter warning go away
+            return 1;
+        },
+        nullptr);
+
+    ensure(out != nullptr);
+    ensure_equals(GEOSEqualsExact(out, geom, 0), 1);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// failed callback should return NULL
+template <>
+template <>
+void object::test<2>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT (1 1 1)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(
+        geom,
+        [](double* x, double* y, double* z, void* userdata) {
+            (void)(x);         // make unused parameter warning go away
+            (void)(y);         // make unused parameter warning go away
+            (void)(z);         // make unused parameter warning go away
+            (void)(userdata);  // make unused parameter warning go away
+            return 0;          // indicates error
+        },
+        nullptr);
+
+    ensure(out == nullptr);
+
+    GEOSGeom_destroy(geom);
+}
+
+// callback should modify point coordinates
+template <>
+template <>
+void object::test<3>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT (1 1 1)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "POINT (2 3 4)");
+
+    double xmin, ymin, xmax, ymax;
+    GEOSGeom_getExtent(out, &xmin, &ymin, &xmax, &ymax);
+    ensure_equals(xmin, 2);
+    ensure_equals(ymin, 3);
+    ensure_equals(xmax, 2);
+    ensure_equals(ymax, 3);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify linestring coordinates
+template <>
+template <>
+void object::test<4>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("LINESTRING (1 1 1, 2 2 2)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "LINESTRING (2 3 4, 4 6 8)");
+
+    double xmin, ymin, xmax, ymax;
+    GEOSGeom_getExtent(out, &xmin, &ymin, &xmax, &ymax);
+    ensure_equals(xmin, 2);
+    ensure_equals(ymin, 3);
+    ensure_equals(xmax, 4);
+    ensure_equals(ymax, 6);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify polygon coordinates
+template <>
+template <>
+void object::test<5>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT(
+        "POLYGON ((1 1 1, 1 10 100, 10 10 10, 10 1 .1, 1 1 1),  (2 2 2, 2 4 8, 4 4 4, 4 2 1, 2 2 2))");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out,
+        "POLYGON ((2 3 4, 2 30 400, 20 30 40, 20 3 0.4, 2 3 4), (4 6 8, 4 12 32, 8 12 16, 8 6 4, 4 6 8))");
+
+    double xmin, ymin, xmax, ymax;
+    GEOSGeom_getExtent(out, &xmin, &ymin, &xmax, &ymax);
+    ensure_equals(xmin, 2);
+    ensure_equals(ymin, 3);
+    ensure_equals(xmax, 20);
+    ensure_equals(ymax, 30);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify multi point coordinates
+template <>
+template <>
+void object::test<6>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT(
+        "MULTIPOINT ((1 1 1), (2 2 2))");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out,
+        "MULTIPOINT ((2 3 4), (4 6 8))");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify multi linestring coordinates
+template <>
+template <>
+void object::test<7>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT(
+        "MULTILINESTRING ((1 1 1, 2 2 2), (3 3 3, 4 4 4))");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out,
+        "MULTILINESTRING ((2 3 4, 4 6 8), (6 9 12, 8 12 16))");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify multi polygon coordinates
+template <>
+template <>
+void object::test<8>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT(
+        "MULTIPOLYGON (((1 1 1, 1 10 100, 10 10 100, 10 1 0.1, 1 1 1),  (2 2 2, 2 4 8, 4 4 4, 4 2 1, 2 2 2)), ((0 0 0, 0 100 1000, 100 100 100, 100 0 -100, 0 0 0)))");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out,
+        "MULTIPOLYGON (((2 3 4, 2 30 400, 20 30 40, 20 3 0.4, 2 3 4), (4 6 8, 4 12 16, 8 12 16, 8 6 4, 4 6 8)), ((0 0 0, 0 300 4000, 200 300 400, 200 0 -400, 0 0 0)))");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// callback should modify geometry collection coordinates
+template <>
+template <>
+void object::test<9>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT(
+        "GEOMETRYCOLLECTION (POINT (1 1 1), LINESTRING (1 1 1, 2 2 2), POLYGON ((1 1 1, 1 2 3, 2 2 2, 4 2 1, 1 1 1)))");
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "GEOMETRYCOLLECTION (POINT (2 3 4), LINESTRING (2 3 4, 4 6 8), POLYGON ((2 3 4, 2 6 12, 4 6 8, 8 6 4, 2 3 4)))");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// should not fail for empty geometry
+template <>
+template <>
+void object::test<10>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT EMPTY");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "POINT EMPTY");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// should retain original coords even if they collapse to same coordinate
+template <>
+template <>
+void object::test<11>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("LINESTRING (1 1 1, 2 2 2)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(
+        geom,
+        [](double* x, double* y, double* z, void* userdata) {
+            *x = 0;
+            *y = 0;
+            *z = 0;
+            (void)(userdata);  // make unused parameter warning go away
+            return 1;
+        },
+        nullptr);
+
+    ensure(out != nullptr);
+    ensure_equals(GEOSGetNumCoordinates(out), 2);
+
+    // Cannot construct WKT for this case, must test coords directly
+    const GEOSCoordSequence* seq = GEOSGeom_getCoordSeq(out);
+
+    double x, y, z;
+    GEOSCoordSeq_getXYZ(seq, 0, &x, &y, &z);
+    ensure_equals(x, 0);
+    ensure_equals(y, 0);
+    ensure_equals(z, 0);
+
+    GEOSCoordSeq_getXYZ(seq, 1, &x, &y, &z);
+    ensure_equals(x, 0);
+    ensure_equals(y, 0);
+    ensure_equals(z, 0);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// should pass through userdata
+template <>
+template <>
+void object::test<12>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("LINESTRING (1 1 1, 2 2 2)");
+
+    double userdata_scale = 5.0;
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(
+        geom,
+        [](double* x, double* y, double* z, void* userdata) {
+            double scale = *(double *)(userdata);
+            (*x) *= scale;
+            (*y) *= scale;
+            (*z) *= scale;
+            return 1;
+        },
+        (void *)(&userdata_scale));
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "LINESTRING (5 5 5, 10 10 10)");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+// transform should preserve existing M coordinate values
+template <>
+template <>
+void object::test<13>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT ZM (1 1 1 5)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "POINT ZM (2 3 4 5)");
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+template <>
+template <>
+void object::test<14>() {
+    input_ = GEOSGeomFromWKT("CIRCULARSTRING Z (0 0 0, 1 1 1, 2 1 0)");
+    ensure(input_);
+
+    result_ = GEOSGeom_transformXYZ(input_, SCALE_2_3_4, nullptr);
+
+    ensure_equals(toWKT(result_), "CIRCULARSTRING Z (0 0 0, 2 3 4, 4 3 0)");
+}
+
+// callback should succed on 2D geometry
+template <>
+template <>
+void object::test<15>() {
+    GEOSGeometry* geom = GEOSGeomFromWKT("POINT (1 1)");
+
+    GEOSGeometry* out = GEOSGeom_transformXYZ(geom, SCALE_2_3_4, nullptr);
+
+    ensure(out != nullptr);
+    ensure_geometry_equals(out, "POINT (2 3)");
+
+    int dims = GEOSGeom_getCoordinateDimension(geom);
+    ensure_equals(dims, 2);
+
+    GEOSGeom_destroy(geom);
+    GEOSGeom_destroy(out);
+}
+
+}  // namespace tut

-----------------------------------------------------------------------

Summary of changes:
 capi/geos_c.cpp                                    |   6 +
 capi/geos_c.h.in                                   |  50 ++++++++-
 capi/geos_ts_c.cpp                                 |  29 +++++
 ...ormXYTest.cpp => GEOSGeom_transformXYZTest.cpp} | 125 ++++++++++++---------
 4 files changed, 158 insertions(+), 52 deletions(-)
 copy tests/unit/capi/{GEOSGeom_transformXYTest.cpp => GEOSGeom_transformXYZTest.cpp} (56%)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list