From b3415ca77a8f177493bd36e7d47dc04f5a266096 Mon Sep 17 00:00:00 2001 From: Kunlin Yu Date: Thu, 23 Nov 2023 00:36:40 +0800 Subject: [PATCH] Support id for GeoJSONFeature (#994) * Support id for GeoJSONFeature 1. Add id and getter for GeoJSONFeature 2. Modify the copy constructor of GeoJSONFeature 3. Check "id" in GeoJSONReader 4. Check "id" in GeoJSONWriter 5. Modify unit test for id * Support floating-point id Signed-off-by: Kunlin Yu --- include/geos/io/GeoJSON.h | 11 ++++++++++ src/io/GeoJSON.cpp | 20 +++++++++++++++---- src/io/GeoJSONReader.cpp | 9 ++++++++- src/io/GeoJSONWriter.cpp | 4 ++++ tests/unit/io/GeoJSONReaderTest.cpp | 31 ++++++++++++++++++++++++++++- tests/unit/io/GeoJSONWriterTest.cpp | 6 +++--- 6 files changed, 72 insertions(+), 9 deletions(-) diff --git a/include/geos/io/GeoJSON.h b/include/geos/io/GeoJSON.h index 0fd045bdb2..8281786bb3 100644 --- a/include/geos/io/GeoJSON.h +++ b/include/geos/io/GeoJSON.h @@ -98,6 +98,13 @@ class GEOS_DLL GeoJSONFeature { GeoJSONFeature(std::unique_ptr g, std::map&& p); + GeoJSONFeature(std::unique_ptr g, + const std::map& p, + const std::string& id); + + GeoJSONFeature(std::unique_ptr g, + std::map&& p, std::string id); + GeoJSONFeature(GeoJSONFeature const& other); GeoJSONFeature(GeoJSONFeature&& other); @@ -110,12 +117,16 @@ class GEOS_DLL GeoJSONFeature { const std::map& getProperties() const; + const std::string& getId() const; + private: std::unique_ptr geometry; std::map properties; + std::string id; + }; class GEOS_DLL GeoJSONFeatureCollection { diff --git a/src/io/GeoJSON.cpp b/src/io/GeoJSON.cpp index cfb28ea13f..32e0f6c336 100644 --- a/src/io/GeoJSON.cpp +++ b/src/io/GeoJSON.cpp @@ -224,16 +224,26 @@ bool GeoJSONValue::isArray() const // GeoJSONFeature GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, - const std::map& p) : geometry(std::move(g)), properties(p) {} + const std::map& p) : geometry(std::move(g)), properties(p), id("") {} GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, - std::map&& p) : geometry(std::move(g)), properties(std::move(p)) {} + std::map&& p) : geometry(std::move(g)), properties(std::move(p)), id("") {} + +GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, + const std::map& p, + const std::string& i) + : geometry(std::move(g)), properties(p), id(i) {} + +GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, + std::map&& p, + std::string i) + : geometry(std::move(g)), properties(std::move(p)), id(std::move(i)) {} GeoJSONFeature::GeoJSONFeature(GeoJSONFeature const& other) : geometry(other.geometry->clone()), - properties(other.properties) {} + properties(other.properties), id(other.id) {} GeoJSONFeature::GeoJSONFeature(GeoJSONFeature&& other) : geometry(std::move(other.geometry)), - properties(std::move(other.properties)) {} + properties(std::move(other.properties)), id(std::move(other.id)) {} GeoJSONFeature& GeoJSONFeature::operator=(const GeoJSONFeature& other) { @@ -262,6 +272,8 @@ const std::map& GeoJSONFeature::getProperties() const return properties; } +const std::string& GeoJSONFeature::getId() const { return id; } + // GeoJSONFeatureCollection GeoJSONFeatureCollection::GeoJSONFeatureCollection(const std::vector& f) : features(f) {} diff --git a/src/io/GeoJSONReader.cpp b/src/io/GeoJSONReader.cpp index 444905b0c4..9daa5ac150 100644 --- a/src/io/GeoJSONReader.cpp +++ b/src/io/GeoJSONReader.cpp @@ -98,7 +98,14 @@ GeoJSONFeature GeoJSONReader::readFeature(const geos_nlohmann::json& j) const { const auto& geometryJson = j.at("geometry"); const auto& properties = j.at("properties"); - return GeoJSONFeature{readGeometry(geometryJson), readProperties(properties)}; + + std::string id = ""; + if (j.contains("id") && !j.at("id").is_null()) { + if (j.at("id").is_string()) id = j.at("id").get(); + if (j.at("id").is_number()) id = j.at("id").dump(); + } + + return GeoJSONFeature{readGeometry(geometryJson), readProperties(properties), id}; } std::map GeoJSONReader::readProperties( diff --git a/src/io/GeoJSONWriter.cpp b/src/io/GeoJSONWriter.cpp index 6e7b196b89..154b8266d2 100644 --- a/src/io/GeoJSONWriter.cpp +++ b/src/io/GeoJSONWriter.cpp @@ -125,9 +125,13 @@ std::string GeoJSONWriter::write(const GeoJSONFeatureCollection& features) void GeoJSONWriter::encodeFeature(const GeoJSONFeature& feature, geos_nlohmann::ordered_json& j) { j["type"] = "Feature"; + + if (feature.getId().size() > 0) j["id"] = feature.getId(); + json geometryJson; encodeGeometry(feature.getGeometry(), geometryJson); j["geometry"] = geometryJson; + json propertiesJson = json::object(); for (auto const& property : feature.getProperties()) { std::string key = property.first; diff --git a/tests/unit/io/GeoJSONReaderTest.cpp b/tests/unit/io/GeoJSONReaderTest.cpp index e1adcf8cab..bcbb9fead4 100644 --- a/tests/unit/io/GeoJSONReaderTest.cpp +++ b/tests/unit/io/GeoJSONReaderTest.cpp @@ -270,13 +270,14 @@ template<> void object::test<19> () { - std::string geojson { "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]}, \"properties\": {\"id\": 1, \"name\": \"one\", \"items\": [1,2,3,4], \"nested\": {\"id\":2, \"name\":\"two\"}}}" }; + std::string geojson { "{\"type\":\"Feature\",\"id\":\"id123\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]}, \"properties\": {\"id\": 1, \"name\": \"one\", \"items\": [1,2,3,4], \"nested\": {\"id\":2, \"name\":\"two\"}}}" }; geos::io::GeoJSONFeatureCollection features(geojsonreader.readFeatures(geojson)); ensure_equals(features.getFeatures().size(), static_cast(1)); ensure_equals(static_cast(features.getFeatures()[0].getGeometry()->getCoordinateDimension()), 2u); ensure_equals(features.getFeatures()[0].getGeometry()->toText(), "POINT (-117 33)"); ensure_equals(features.getFeatures()[0].getProperties().at("id").getNumber(), 1.0); ensure_equals(features.getFeatures()[0].getProperties().at("name").getString(), "one"); + ensure_equals(features.getFeatures()[0].getId(), "id123"); std::vector values = features.getFeatures()[0].getProperties().at("items").getArray(); ensure_equals(values.size(), static_cast(4)); ensure_equals(values[0].getNumber(), 1.0); @@ -476,5 +477,33 @@ void object::test<30> ensure(errorMessage.find("ParseException: Error parsing JSON") != std::string::npos); } +// Read a GeoJSON FeatureCollection with multiple Features with id +template<> +template<> +void object::test<31> +() +{ + std::string geojson { "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\", \"id\":\"123\", \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": 123, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": 123.0, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": 123.000, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": 123.9, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": null, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": {}, \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"id\": [\"123\"], \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}," + "{\"type\":\"Feature\", \"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}, \"properties\":{}}]}" }; + geos::io::GeoJSONFeatureCollection features(geojsonreader.readFeatures(geojson)); + ensure_equals(features.getFeatures().size(), static_cast(9)); + ensure_equals(features.getFeatures()[0].getId(), "123"); + ensure_equals(features.getFeatures()[1].getId(), "123"); + ensure_equals(features.getFeatures()[2].getId(), "123.0"); + ensure_equals(features.getFeatures()[3].getId(), "123.0"); + ensure_equals(features.getFeatures()[4].getId(), "123.9"); + ensure_equals(features.getFeatures()[5].getId(), ""); + ensure_equals(features.getFeatures()[6].getId(), ""); + ensure_equals(features.getFeatures()[7].getId(), ""); + ensure_equals(features.getFeatures()[8].getId(), ""); +} + } diff --git a/tests/unit/io/GeoJSONWriterTest.cpp b/tests/unit/io/GeoJSONWriterTest.cpp index 30862b168e..26a2d85204 100644 --- a/tests/unit/io/GeoJSONWriterTest.cpp +++ b/tests/unit/io/GeoJSONWriterTest.cpp @@ -240,15 +240,15 @@ void object::test<14> geos::io::GeoJSONFeatureCollection features {{ geos::io::GeoJSONFeature { wktreader.read("POINT(-117 33)"), std::map { {"id", geos::io::GeoJSONValue(1.0) }, - {"name", geos::io::GeoJSONValue(std::string{"One"}) }, - }}, + {"name", geos::io::GeoJSONValue(std::string{"One"})}}, + "id123"}, geos::io::GeoJSONFeature { wktreader.read("POINT(-127 53)"), std::map { {"id", geos::io::GeoJSONValue(2.0) }, {"name", geos::io::GeoJSONValue(std::string{"Two"}) }, }} }}; std::string result = geojsonwriter.write(features); - ensure_equals(result, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]},\"properties\":{\"id\":1.0,\"name\":\"One\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-127.0,53.0]},\"properties\":{\"id\":2.0,\"name\":\"Two\"}}]}"); + ensure_equals(result, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"id\":\"id123\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-117.0,33.0]},\"properties\":{\"id\":1.0,\"name\":\"One\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-127.0,53.0]},\"properties\":{\"id\":2.0,\"name\":\"Two\"}}]}"); } // Write an empty point