Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensions/add geojson writer #1351

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions include/boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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 <boost/geometry/core/access.hpp>
#include <boost/geometry/io/dsv/write.hpp>
#include <boost/geometry/core/static_assert.hpp>
#include <boost/geometry/util/constexpr.hpp>

#include <iomanip>
#include <ostream>
#include <string>
#include <type_traits>


namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename GeometryTag>
struct geojson_feature_type
{
BOOST_GEOMETRY_STATIC_ASSERT_FALSE("Not or not yet implemented for this Geometry type.",
GeometryTag);
};

template <>
struct geojson_feature_type<point_tag> { static inline const char* apply() { return "Point"; } };

template <>
struct geojson_feature_type<multi_point_tag> { static inline const char* apply() { return "MultiPoint"; } };

template <>
struct geojson_feature_type<segment_tag> { static inline const char* apply() { return "LineString"; } };

template <>
struct geojson_feature_type<ring_tag> { static inline const char* apply() { return "Polygon"; } };

template <>
struct geojson_feature_type<linestring_tag> { static inline const char* apply() { return "LineString"; } };

template <>
struct geojson_feature_type<multi_linestring_tag> { static inline const char* apply() { return "MultiLineString"; } };

template <>
struct geojson_feature_type<polygon_tag> { static inline const char* apply() { return "Polygon"; } };

template <>
struct geojson_feature_type<multi_polygon_tag> { 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 <typename T>
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 <typename T>
void add_property(std::string const& name, T const& value)
{
constexpr bool needs_quotes
= std::is_same<T, std::string>::value
|| std::is_same<typename std::remove_extent<T>::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 <typename Geometry>
void feature(Geometry const& geometry)
{
using tag_t = typename tag<Geometry>::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<tag_t>::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<tag_t, ring_tag>::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<Geometry>::value == geometry::open;

m_out << geometry::dsv(geometry, ", ", "[", "]", ", ", list_open,
list_close, ",", close) << "}\n";
}

};

}} // namespace boost::geometry

#endif
39 changes: 28 additions & 11 deletions include/boost/geometry/io/dsv/write.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/util/condition.hpp>

namespace boost { namespace geometry
{

Expand All @@ -53,6 +55,7 @@ struct dsv_settings
std::string list_open;
std::string list_close;
std::string list_separator;
bool close_rings{false};

dsv_settings(std::string const& sep
, std::string const& open
Expand All @@ -61,6 +64,7 @@ struct dsv_settings
, std::string const& lopen
, std::string const& lclose
, std::string const& lsep
, bool cr = false
)
: coordinate_separator(sep)
, point_open(open)
Expand All @@ -69,6 +73,7 @@ struct dsv_settings
, list_open(lopen)
, list_close(lclose)
, list_separator(lsep)
, close_rings(cr)
{}
};

Expand Down Expand Up @@ -161,7 +166,7 @@ struct dsv_point
\brief Stream ranges as DSV
\note policy is used to stream prefix/postfix, enabling derived classes to override this
*/
template <typename Range>
template <typename Range, bool Areal>
struct dsv_range
{
template <typename Char, typename Traits>
Expand All @@ -173,20 +178,31 @@ struct dsv_range

os << settings.list_open;

for (auto it = boost::begin(range); it != boost::end(range); ++it)
auto stream_point = [&os, &settings](std::string const& sep, auto const& point)
{
os << (first ? "" : settings.point_separator)
<< settings.point_open;

os << sep << settings.point_open;
stream_coordinate
<
point_type, 0, dimension<point_type>::type::value
>::apply(os, *it, settings);
>::apply(os, point, settings);
os << settings.point_close;
};

for (auto it = boost::begin(range); it != boost::end(range); ++it)
{
stream_point(first ? "" : settings.point_separator, *it);
first = false;
}

if (BOOST_GEOMETRY_CONDITION(Areal))
{
if (settings.close_rings && boost::size(range) > 0)
{
// Close it explicitly
stream_point(settings.point_separator, *boost::begin(range));
}
}

os << settings.list_close;
}

Expand All @@ -211,13 +227,13 @@ struct dsv_poly

os << settings.list_open;

dsv_range<ring_t>::apply(os, exterior_ring(poly), settings);
dsv_range<ring_t, true>::apply(os, exterior_ring(poly), settings);

auto const& rings = interior_rings(poly);
for (auto it = boost::begin(rings); it != boost::end(rings); ++it)
{
os << settings.list_separator;
dsv_range<ring_t>::apply(os, *it, settings);
dsv_range<ring_t, true>::apply(os, *it, settings);
}
os << settings.list_close;
}
Expand Down Expand Up @@ -277,7 +293,7 @@ struct dsv<point_tag, Point>

template <typename Linestring>
struct dsv<linestring_tag, Linestring>
: detail::dsv::dsv_range<Linestring>
: detail::dsv::dsv_range<Linestring, false>
{};

template <typename Box>
Expand All @@ -292,7 +308,7 @@ struct dsv<segment_tag, Segment>

template <typename Ring>
struct dsv<ring_tag, Ring>
: detail::dsv::dsv_range<Ring>
: detail::dsv::dsv_range<Ring, true>
{};

template <typename Polygon>
Expand Down Expand Up @@ -404,14 +420,15 @@ inline detail::dsv::dsv_manipulator<Geometry> dsv(Geometry const& geometry
, std::string const& list_open = "("
, std::string const& list_close = ")"
, std::string const& list_separator = ", "
, bool close_rings = false
)
{
concepts::check<Geometry const>();

return detail::dsv::dsv_manipulator<Geometry>(geometry,
detail::dsv::dsv_settings(coordinate_separator,
point_open, point_close, point_separator,
list_open, list_close, list_separator));
list_open, list_close, list_separator, close_rings));
}

}} // namespace boost::geometry
Expand Down
Loading
Loading