diff --git a/include/geos/io/GeoJSONWriter.h b/include/geos/io/GeoJSONWriter.h
index 8bb7e663ba..002a6f5018 100644
--- a/include/geos/io/GeoJSONWriter.h
+++ b/include/geos/io/GeoJSONWriter.h
@@ -74,11 +74,33 @@ class GEOS_DLL GeoJSONWriter {
std::string write(const GeoJSONFeatureCollection& features);
+ /*
+ * \brief
+ * Returns the output dimension used by the
+ * GeoJSONWriter
.
+ */
+ int
+ getOutputDimension() const
+ {
+ return defaultOutputDimension;
+ }
+
+ /*
+ * Sets the output dimension used by the GeoJSONWriter
.
+ *
+ * @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 convertCoordinate(const geom::CoordinateXY* c);
+ std::vector convertCoordinate(const geom::Coordinate* c);
- std::vector> convertCoordinateSequence(const geom::CoordinateSequence* c);
+ std::vector> 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 9daa5ac150..fe53b63c75 100644
--- a/src/io/GeoJSONReader.cpp
+++ b/src/io/GeoJSONReader.cpp
@@ -210,13 +210,16 @@ geom::Coordinate GeoJSONReader::readCoordinate(
const std::vector& 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 GeoJSONReader::readPoint(
{
const auto& coords = j.at("coordinates").get>();
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 GeoJSONReader::readLineString(
const geos_nlohmann::json& j) const
{
const auto& coords = j.at("coordinates").get>>();
- auto coordinates = detail::make_unique(0u, 2u);
+ bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; });
+ auto coordinates = detail::make_unique(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 GeoJSONReader::readPolygon(
std::vector> rings;
rings.reserve(polygonCoords.size());
for (const auto& ring : polygonCoords) {
- auto coordinates = detail::make_unique(0u, 2u);
+ bool has_z = std::any_of(ring.begin(), ring.end(), [](auto v) { return v.size() > 2; });
+ auto coordinates = detail::make_unique(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 GeoJSONReader::readMultiLineString(
std::vector> lines;
lines.reserve(listOfCoords.size());
for (const auto& coords : listOfCoords) {
- auto coordinates = detail::make_unique(0u, 2u);
+ bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; });
+ auto coordinates = detail::make_unique(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 0fd4bc297c..bf3826d49a 100644
--- a/src/io/GeoJSONWriter.cpp
+++ b/src/io/GeoJSONWriter.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#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>> rings;
+ std::vector>> 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>> lines;
+ std::vector>> 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>>> polygons;
+ std::vector>>> polygons;
polygons.reserve(multiPolygon->getNumGeometries());
for (size_t i = 0; i < multiPolygon->getNumGeometries(); i++) {
const Polygon* polygon = multiPolygon->getGeometryN(i);
- std::vector>> rings;
+ std::vector>> 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 GeoJSONWriter::convertCoordinate(const CoordinateXY* c)
+std::vector GeoJSONWriter::convertCoordinate(const Coordinate* c)
{
- return std::make_pair(c->x, c->y);
+ if (std::isnan(c->z) || defaultOutputDimension == 2) {
+ return std::vector { c->x, c->y };
+ }
+ return std::vector { c->x, c->y, c->z };
}
-std::vector> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence*
+std::vector> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence*
coordinateSequence)
{
- std::vector> coordinates;
+ std::vector> coordinates;
coordinates.reserve(coordinateSequence->size());
for (size_t i = 0; isize(); i++) {
const geom::Coordinate& c = coordinateSequence->getAt(i);
diff --git a/tests/unit/io/GeoJSONReaderTest.cpp b/tests/unit/io/GeoJSONReaderTest.cpp
index bcbb9fead4..2a8e7c0309 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(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(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(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(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(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(geom->getCoordinateDimension()), 3u);
}
+}
diff --git a/tests/unit/io/GeoJSONWriterTest.cpp b/tests/unit/io/GeoJSONWriterTest.cpp
index 26a2d85204..53d6e609ad 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]}");
+}
}