diff --git a/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp new file mode 100644 index 0000000000..51414afc88 --- /dev/null +++ b/include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp @@ -0,0 +1,179 @@ +// Boost.Geometry + +// Copyright (c) 2024 Barend Gehrels, Amsterdam, the Netherlands. + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP +#define BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DISPATCH +namespace dispatch +{ + +template +struct geojson_feature_type +{ + BOOST_GEOMETRY_STATIC_ASSERT_FALSE("Not or not yet implemented for this Geometry type.", + GeometryTag); +}; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Point"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "LineString"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "Polygon"; } }; + +template <> +struct geojson_feature_type { static inline const char* apply() { return "MultiPolygon"; } }; + +} // namespace dispatch +#endif + +/*! +\brief Helper class to create geojson output +*/ +struct geojson_writer +{ + + private: + std::ostream& m_out; + + std::size_t feature_count = 0; + std::size_t property_count = 0; + + template + void stream_quoted(T const& entry) + { + m_out << '"' << entry << '"'; + } + + void start_feature() + { + end_properties(); + end_feature(); + + m_out << (feature_count > 0 ? ",\n" : "") << R"({"type": "Feature")"; + feature_count++; + } + + void start_property() + { + // Write a comma, either after the "geometry" tag, or after the previous property + // If there are no properties yet, start the list of properties + m_out << "," << (property_count == 0 ? R"("properties": {)" : "") << '\n'; + property_count++; + } + + void end_properties() + { + if (property_count > 0) + { + m_out << "}\n"; + property_count = 0; + } + } + void end_feature() + { + if (feature_count > 0) + { + m_out << "}\n"; + } + } + + public: + explicit geojson_writer(std::ostream& out) : m_out(out) + { + m_out << R"({"type": "FeatureCollection", "features": [)" << '\n'; + } + + ~geojson_writer() + { + end_properties(); + end_feature(); + + m_out << "]}"; + } + + // Set a property. It is expected that a feature is already started. + template + void add_property(std::string const& name, T const& value) + { + constexpr bool needs_quotes + = std::is_same::value + || std::is_same::type, char>::value; + + start_property(); + stream_quoted(name); + m_out << ": "; + if BOOST_GEOMETRY_CONSTEXPR(needs_quotes) + { + stream_quoted(value); + } + else + { + m_out << std::boolalpha << value; + } + } + + // The method "feature" should be called to start a feature. + // After that, properties can be added, until a new "feature" is called, + // or the instance is destructed + template + void feature(Geometry const& geometry) + { + using tag_t = typename tag::type; + + start_feature(); + + // Write the comma after either the "Feature" tag + m_out << ",\n"; + + // Write the geometry + m_out << R"("geometry": {"type":)"; + stream_quoted(dispatch::geojson_feature_type::apply()); + m_out << R"(, "coordinates": )"; + + // A ring is modelled as a geojson polygon, and therefore the opening and closing + // list marks should be duplicated to indicate empty interior rings. + constexpr bool dup = std::is_same::value; + const char* list_open = dup ? "[[" : "["; + const char* list_close = dup ? "]]" : "]"; + + // Indicate that dsv should close any ring automatically if its model is open + constexpr bool close = geometry::closure::value == geometry::open; + + m_out << geometry::dsv(geometry, ", ", "[", "]", ", ", list_open, + list_close, ",", close) << "}\n"; + } + +}; + +}} // namespace boost::geometry + +#endif