[geos-commits] [SCM] GEOS branch main updated. 0296a92919e54257adcd2bc6faa9a83de768b252

git at osgeo.org git at osgeo.org
Thu Aug 22 09:00:10 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  0296a92919e54257adcd2bc6faa9a83de768b252 (commit)
      from  e8028977055522fd103d06d75e1e2940ae52bfc3 (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 0296a92919e54257adcd2bc6faa9a83de768b252
Author: Oreille <33065839+Oreilles at users.noreply.github.com>
Date:   Thu Aug 22 17:59:39 2024 +0200

    Add 3D supports to GeoJSON reader and writer (#1150)

diff --git a/include/geos/io/GeoJSONWriter.h b/include/geos/io/GeoJSONWriter.h
index 8bb7e663b..002a6f501 100644
--- a/include/geos/io/GeoJSONWriter.h
+++ b/include/geos/io/GeoJSONWriter.h
@@ -74,11 +74,33 @@ public:
 
     std::string write(const GeoJSONFeatureCollection& features);
 
+    /*
+     * \brief
+     * Returns the output dimension used by the
+     * <code>GeoJSONWriter</code>.
+     */
+    int
+    getOutputDimension() const
+    {
+        return defaultOutputDimension;
+    }
+
+    /*
+     * Sets the output dimension used by the <code>GeoJSONWriter</code>.
+     *
+     * @param newOutputDimension Supported values are 2 or 3.
+     *        Default since GEOS 3.12 is 3.
+     *        Note that 3 indicates up to 3 dimensions will be
+     *        written but 2D GeoJSON is still produced for 2D geometries.
+     */
+    void setOutputDimension(uint8_t newOutputDimension);
+
 private:
+    uint8_t defaultOutputDimension = 3;
 
-    std::pair<double, double> convertCoordinate(const geom::CoordinateXY* c);
+    std::vector<double> convertCoordinate(const geom::Coordinate* c);
 
-    std::vector<std::pair<double, double>> convertCoordinateSequence(const geom::CoordinateSequence* c);
+    std::vector<std::vector<double>> convertCoordinateSequence(const geom::CoordinateSequence* c);
 
     void encode(const geom::Geometry* g, GeoJSONType type, geos_nlohmann::ordered_json& j);
 
diff --git a/src/io/GeoJSONReader.cpp b/src/io/GeoJSONReader.cpp
index 9daa5ac15..fe53b63c7 100644
--- a/src/io/GeoJSONReader.cpp
+++ b/src/io/GeoJSONReader.cpp
@@ -210,13 +210,16 @@ geom::Coordinate GeoJSONReader::readCoordinate(
     const std::vector<double>& coords) const
 {
     if (coords.size() == 1) {
-        throw  ParseException("Expected two coordinates found one");
+        throw  ParseException("Expected two or three coordinates found one");
     }
-    else if (coords.size() > 2) {
-        throw  ParseException("Expected two coordinates found more than two");
+    else if (coords.size() == 2) {
+        return geom::Coordinate { coords[0], coords[1] };
+    }
+    else if (coords.size() == 3) {
+        return geom::Coordinate { coords[0], coords[1], coords[2] };
     }
     else {
-        return geom::Coordinate {coords[0], coords[1]};
+        throw  ParseException("Expected two or three coordinates found more than three");
     }
 }
 
@@ -225,7 +228,7 @@ std::unique_ptr<geom::Point> GeoJSONReader::readPoint(
 {
     const auto& coords = j.at("coordinates").get<std::vector<double>>();
     if (coords.size() == 1) {
-        throw  ParseException("Expected two coordinates found one");
+        throw  ParseException("Expected two or three coordinates found one");
     }
     else if (coords.size() < 2) {
         return geometryFactory.createPoint(2);
@@ -240,7 +243,8 @@ std::unique_ptr<geom::LineString> GeoJSONReader::readLineString(
     const geos_nlohmann::json& j) const
 {
     const auto& coords = j.at("coordinates").get<std::vector<std::vector<double>>>();
-    auto coordinates = detail::make_unique<CoordinateSequence>(0u, 2u);
+    bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; });
+    auto coordinates = detail::make_unique<CoordinateSequence>(0u, has_z, false);
     coordinates->reserve(coords.size());
     for (const auto& coord : coords) {
         const geom::Coordinate& c = readCoordinate(coord);
@@ -263,7 +267,8 @@ std::unique_ptr<geom::Polygon> GeoJSONReader::readPolygon(
     std::vector<std::unique_ptr<geom::LinearRing>> rings;
     rings.reserve(polygonCoords.size());
     for (const auto& ring : polygonCoords) {
-    auto coordinates = detail::make_unique<CoordinateSequence>(0u, 2u);
+        bool has_z = std::any_of(ring.begin(), ring.end(), [](auto v) { return v.size() > 2; });
+        auto coordinates = detail::make_unique<CoordinateSequence>(0u, has_z, false);
         coordinates->reserve(ring.size());
         for (const auto& coord : ring) {
             const geom::Coordinate& c = readCoordinate(coord);
@@ -307,7 +312,8 @@ std::unique_ptr<geom::MultiLineString> GeoJSONReader::readMultiLineString(
     std::vector<std::unique_ptr<geom::LineString>> lines;
     lines.reserve(listOfCoords.size());
     for (const auto& coords :  listOfCoords) {
-        auto coordinates = detail::make_unique<geom::CoordinateSequence>(0u, 2u);
+        bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; });
+        auto coordinates = detail::make_unique<geom::CoordinateSequence>(0u, has_z, false);
         coordinates->reserve(coords.size());
         for (const auto& coord : coords) {
             const geom::Coordinate& c = readCoordinate(coord);
diff --git a/src/io/GeoJSONWriter.cpp b/src/io/GeoJSONWriter.cpp
index 0fd4bc297..bf3826d49 100644
--- a/src/io/GeoJSONWriter.cpp
+++ b/src/io/GeoJSONWriter.cpp
@@ -29,6 +29,7 @@
 #include <ostream>
 #include <sstream>
 #include <cassert>
+#include <cmath>
 
 #include "geos/util.h"
 
@@ -40,6 +41,17 @@ using json = geos_nlohmann::ordered_json;
 namespace geos {
 namespace io { // geos.io
 
+
+/* public */
+void
+GeoJSONWriter::setOutputDimension(uint8_t dims)
+{
+    if(dims < 2 || dims > 3) {
+        throw util::IllegalArgumentException("GeoJSON output dimension must be 2 or 3");
+    }
+    defaultOutputDimension = dims;
+}
+
 std::string GeoJSONWriter::write(const geom::Geometry* geometry, GeoJSONType type)
 {
     json j;
@@ -217,7 +229,8 @@ void GeoJSONWriter::encodePoint(const geom::Point* point, geos_nlohmann::ordered
 {
     j["type"] = "Point";
     if (!point->isEmpty()) {
-        j["coordinates"] = convertCoordinate(point->getCoordinate());
+        auto as_coord = Coordinate { point->getX(), point->getY(), point->getZ()};
+        j["coordinates"] = convertCoordinate(&as_coord);
     }
     else {
         j["coordinates"] = j.array();
@@ -233,7 +246,7 @@ void GeoJSONWriter::encodeLineString(const geom::LineString* line, geos_nlohmann
 void GeoJSONWriter::encodePolygon(const geom::Polygon* poly, geos_nlohmann::ordered_json& j)
 {
     j["type"] = "Polygon";
-    std::vector<std::vector<std::pair<double, double>>> rings;
+    std::vector<std::vector<std::vector<double>>> rings;
     auto ring = poly->getExteriorRing();
     rings.reserve(poly->getNumInteriorRing()+1);
     rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
@@ -252,7 +265,7 @@ void GeoJSONWriter::encodeMultiPoint(const geom::MultiPoint* multiPoint, geos_nl
 void GeoJSONWriter::encodeMultiLineString(const geom::MultiLineString* multiLineString, geos_nlohmann::ordered_json& j)
 {
     j["type"] = "MultiLineString";
-    std::vector<std::vector<std::pair<double, double>>> lines;
+    std::vector<std::vector<std::vector<double>>> lines;
     lines.reserve(multiLineString->getNumGeometries());
     for (size_t i = 0; i < multiLineString->getNumGeometries(); i++) {
         lines.push_back(convertCoordinateSequence(multiLineString->getGeometryN(i)->getCoordinates().get()));
@@ -263,11 +276,11 @@ void GeoJSONWriter::encodeMultiLineString(const geom::MultiLineString* multiLine
 void GeoJSONWriter::encodeMultiPolygon(const geom::MultiPolygon* multiPolygon, geos_nlohmann::ordered_json& json)
 {
     json["type"] = "MultiPolygon";
-    std::vector<std::vector<std::vector<std::pair<double, double>>>> polygons;
+    std::vector<std::vector<std::vector<std::vector<double>>>> polygons;
     polygons.reserve(multiPolygon->getNumGeometries());
     for (size_t i = 0; i < multiPolygon->getNumGeometries(); i++) {
         const Polygon* polygon = multiPolygon->getGeometryN(i);
-        std::vector<std::vector<std::pair<double, double>>> rings;
+        std::vector<std::vector<std::vector<double>>> rings;
         auto ring = polygon->getExteriorRing();
         rings.reserve(polygon->getNumInteriorRing() + 1);
         rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
@@ -291,15 +304,18 @@ void GeoJSONWriter::encodeGeometryCollection(const geom::GeometryCollection* g,
     j["geometries"] = geometryArray;
 }
 
-std::pair<double, double> GeoJSONWriter::convertCoordinate(const CoordinateXY* c)
+std::vector<double> GeoJSONWriter::convertCoordinate(const Coordinate* c)
 {
-    return std::make_pair(c->x, c->y);
+    if (std::isnan(c->z) || defaultOutputDimension == 2) {
+        return std::vector<double> { c->x, c->y };
+    }
+    return std::vector<double> { c->x, c->y, c->z };
 }
 
-std::vector<std::pair<double, double>> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence*
+std::vector<std::vector<double>> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence*
                                     coordinateSequence)
 {
-    std::vector<std::pair<double, double>> coordinates;
+    std::vector<std::vector<double>> coordinates;
     coordinates.reserve(coordinateSequence->size());
     for (size_t i = 0; i<coordinateSequence->size(); i++) {
         const geom::Coordinate& c = coordinateSequence->getAt(i);
diff --git a/tests/unit/io/GeoJSONReaderTest.cpp b/tests/unit/io/GeoJSONReaderTest.cpp
index bcbb9fead..2a8e7c030 100644
--- a/tests/unit/io/GeoJSONReaderTest.cpp
+++ b/tests/unit/io/GeoJSONReaderTest.cpp
@@ -336,7 +336,7 @@ void object::test<22>
         errorMessage = e.what();
     }
     ensure(error == true);
-    ensure_equals(errorMessage, "ParseException: Expected two coordinates found one");
+    ensure_equals(errorMessage, "ParseException: Expected two or three coordinates found one");
 }
 
 // Throw ParseException for bad GeoJSON
@@ -374,7 +374,7 @@ void object::test<24>
         errorMessage = e.what();
     }
     ensure(error == true);
-    ensure_equals(errorMessage, "ParseException: Expected two coordinates found one");
+    ensure_equals(errorMessage, "ParseException: Expected two or three coordinates found one");
 }
 
 // Throw error when geometry type is unsupported
@@ -412,7 +412,7 @@ void object::test<26>
         errorMessage = e.what();
     }
     ensure(error == true);
-    ensure_equals(errorMessage, "ParseException: Expected two coordinates found one");
+    ensure_equals(errorMessage, "ParseException: Expected two or three coordinates found one");
 }
 
 // Read a GeoJSON empty Polygon with empty shell and empty inner rings
@@ -446,7 +446,7 @@ void object::test<29>
 ()
 {
     std::string errorMessage;    
-    std::string geojson { "{\"type\":\"Point\",\"coordinates\":[1,2,3,4,5,6]}" };
+    std::string geojson { "{\"type\":\"Point\",\"coordinates\":[1,2,3,4]}" };
     bool error = false;
     try {
         GeomPtr geom(geojsonreader.read(geojson));
@@ -455,7 +455,7 @@ void object::test<29>
         errorMessage = e.what();
     }
     ensure(error == true);
-    ensure_equals(errorMessage, "ParseException: Expected two coordinates found more than two");
+    ensure_equals(errorMessage, "ParseException: Expected two or three coordinates found more than three");
 }
 
 // Throw ParseException for bad GeoJSON
@@ -505,5 +505,95 @@ void object::test<31>
     ensure_equals(features.getFeatures()[8].getId(), "");
 }
 
+// Read a point with all-null coordinates should fail
+template<>
+template<>
+void object::test<32>
+()
+{
+    std::string errorMessage;    
+    std::string geojson { "{\"type\":\"Point\",\"coordinates\":[null,null]}" };
+    bool error = false;
+    try {
+        GeomPtr geom(geojsonreader.read(geojson));
+    } catch (geos::io::ParseException& e) {
+        error = true;
+        errorMessage = e.what();
+    }
+    ensure(error == true);
+    ensure_equals(errorMessage, "ParseException: Error parsing JSON: '[json.exception.type_error.302] type must be number, but is null'");
 }
 
+// Read a GeoJSON Point with three dimensions
+template<>
+template<>
+void object::test<33>
+()
+{
+    std::string geojson { "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0,10.0]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals("POINT Z (-117 33 10)", geom->toText());
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+// Read a GeoJSON MultiPoint with mixed dimensions
+template<>
+template<>
+void object::test<34>
+()
+{
+    std::string geojson { "{\"type\":\"MultiPoint\",\"coordinates\":[[-117.0,33.0,10.0],[-116.0,34.0]]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals("MULTIPOINT Z ((-117 33 10), (-116 34 NaN))", geom->toText());
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+// Read a GeoJSON LineString with three dimensions
+template<>
+template<>
+void object::test<35>
+()
+{
+    std::string geojson { "{\"type\":\"LineString\",\"coordinates\":[[-117, 33, 2], [-116, 34, 4]]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals("LINESTRING Z (-117 33 2, -116 34 4)", geom->toText());
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+// Read a GeoJSON LineString with mixed dimensions
+template<>
+template<>
+void object::test<36>
+()
+{
+    std::string geojson { "{\"type\":\"LineString\",\"coordinates\":[[-117, 33], [-116, 34, 4]]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals("LINESTRING Z (-117 33 NaN, -116 34 4)", geom->toText());
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+// Read a GeoJSON Polygon with three dimensions
+template<>
+template<>
+void object::test<37>
+()
+{
+    std::string geojson { "{\"type\":\"Polygon\",\"coordinates\":[[[30,10,1],[40,40,2],[20,40,4],[10,20,8],[30,10,16]]]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals(geom->toText(), "POLYGON Z ((30 10 1, 40 40 2, 20 40 4, 10 20 8, 30 10 16))");
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+// Read a GeoJSON Polygon with mixed dimensions
+template<>
+template<>
+void object::test<38>
+()
+{
+    std::string geojson { "{\"type\":\"Polygon\",\"coordinates\":[[[30,10],[40,40,2],[20,40],[10,20,8],[30,10]]]}" };
+    GeomPtr geom(geojsonreader.read(geojson));
+    ensure_equals(geom->toText(), "POLYGON Z ((30 10 NaN, 40 40 2, 20 40 NaN, 10 20 8, 30 10 NaN))");
+    ensure_equals(static_cast<size_t>(geom->getCoordinateDimension()), 3u);
+}
+
+}
diff --git a/tests/unit/io/GeoJSONWriterTest.cpp b/tests/unit/io/GeoJSONWriterTest.cpp
index 26a2d8520..53d6e609a 100644
--- a/tests/unit/io/GeoJSONWriterTest.cpp
+++ b/tests/unit/io/GeoJSONWriterTest.cpp
@@ -318,5 +318,106 @@ void object::test<20>
     ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[null,null]}");
 }
 
+// Write a Point Z to GeoJSON 
+template<>
+template<>
+void object::test<21>
+()
+{
+    GeomPtr geom(wktreader.read("POINT Z (-117 33 10)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0,10.0]}");
+}
+
+// Write a Point Z with NaN to GeoJSON 
+template<>
+template<>
+void object::test<22>
+()
+{
+    GeomPtr geom(wktreader.read("POINT Z (-117 33 NaN)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]}");
+}
+
+// Write a Point M to GeoJSON ignores M
+template<>
+template<>
+void object::test<23>
+()
+{
+    GeomPtr geom(wktreader.read("POINT M (-117 33 10)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]}");
+}
+
+// Write a Point ZM to GeoJSON ignores M
+template<>
+template<>
+void object::test<24>
+()
+{
+    GeomPtr geom(wktreader.read("POINT ZM (-117 33 10 2)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0,10.0]}");
+}
+
+// Write a LineString Z to GeoJSON
+template<>
+template<>
+void object::test<25>
+()
+{
+    GeomPtr geom(wktreader.read("LINESTRING Z (102 0 2, 103 1 4, 104 0 8, 105 1 16)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"LineString\",\"coordinates\":[[102.0,0.0,2.0],[103.0,1.0,4.0],[104.0,0.0,8.0],[105.0,1.0,16.0]]}");
+}
+
+// Write a LineString Z with some NaN Z to GeoJSON
+template<>
+template<>
+void object::test<26>
+()
+{
+    GeomPtr geom(wktreader.read("LINESTRING Z (102 0 2, 103 1 NaN, 104 0 8, 105 1 NaN)"));
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"LineString\",\"coordinates\":[[102.0,0.0,2.0],[103.0,1.0],[104.0,0.0,8.0],[105.0,1.0]]}");
+}
+
+
+// Setting outputs dimensions to an invalid value should raise
+template<>
+template<>
+void object::test<27>
+()
+{
+    std::string errorMessage;    
+    bool error;
+    for (auto dims: { uint8_t{1}, uint8_t{4} }) {
+        errorMessage = "";
+        error = false;
+        try {
+            geojsonwriter.setOutputDimension(dims);
+        } catch (geos::util::IllegalArgumentException& e) {
+            error = true;
+            errorMessage = e.what();
+        }
+        ensure(error == true);
+        ensure_equals(errorMessage, "IllegalArgumentException: GeoJSON output dimension must be 2 or 3");
+    }
+}
+
+
+// GeoJSONWriter without output dimensions set to 2 ignores Z and M values
+template<>
+template<>
+void object::test<28>
+()
+{
+    GeomPtr geom(wktreader.read("POINT ZM (-117 33 10 2)"));
+    geojsonwriter.setOutputDimension(2);
+    std::string result = geojsonwriter.write(geom.get());
+    ensure_equals(result, "{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]}");
+}
 
 }

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

Summary of changes:
 include/geos/io/GeoJSONWriter.h     |  26 +++++++++-
 src/io/GeoJSONReader.cpp            |  22 +++++---
 src/io/GeoJSONWriter.cpp            |  34 ++++++++----
 tests/unit/io/GeoJSONReaderTest.cpp | 100 +++++++++++++++++++++++++++++++++--
 tests/unit/io/GeoJSONWriterTest.cpp | 101 ++++++++++++++++++++++++++++++++++++
 5 files changed, 259 insertions(+), 24 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list