Skip to content

Commit

Permalink
Merge branch 'extensions/add-geojson-writer' into BAREND_COMBINED_DEC_21
Browse files Browse the repository at this point in the history
  • Loading branch information
barendgehrels-tomtom committed Dec 21, 2024
2 parents 82ef735 + e7ae131 commit 5279c88
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 326 deletions.
179 changes: 179 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,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 <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<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<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

0 comments on commit 5279c88

Please sign in to comment.