Skip to content

Commit 9096bf5

Browse files
committed
extension: add geojson writer
1 parent 1986906 commit 9096bf5

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Boost.Geometry
2+
3+
// Copyright (c) 2024 Barend Gehrels, Amsterdam, the Netherlands.
4+
5+
// Use, modification and distribution is subject to the Boost Software License,
6+
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
7+
// http://www.boost.org/LICENSE_1_0.txt)
8+
9+
#ifndef BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP
10+
#define BOOST_GEOMETRY_EXTENSIONS_GIS_IO_GEOJSON_WRITER_HPP
11+
12+
#include <boost/geometry/core/access.hpp>
13+
#include <boost/geometry/io/dsv/write.hpp>
14+
#include <boost/geometry/core/static_assert.hpp>
15+
#include <boost/geometry/util/constexpr.hpp>
16+
17+
#include <iomanip>
18+
#include <ostream>
19+
#include <string>
20+
#include <type_traits>
21+
22+
23+
namespace boost { namespace geometry
24+
{
25+
26+
#ifndef DOXYGEN_NO_DISPATCH
27+
namespace dispatch
28+
{
29+
30+
template <typename GeometryTag>
31+
struct geojson_feature_type
32+
{
33+
BOOST_GEOMETRY_STATIC_ASSERT_FALSE("Not or not yet implemented for this Geometry type.",
34+
GeometryTag);
35+
};
36+
37+
template <>
38+
struct geojson_feature_type<point_tag> { static inline const char* apply() { return "Point"; } };
39+
40+
template <>
41+
struct geojson_feature_type<multi_point_tag> { static inline const char* apply() { return "MultiPoint"; } };
42+
43+
template <>
44+
struct geojson_feature_type<segment_tag> { static inline const char* apply() { return "LineString"; } };
45+
46+
template <>
47+
struct geojson_feature_type<ring_tag> { static inline const char* apply() { return "Polygon"; } };
48+
49+
template <>
50+
struct geojson_feature_type<linestring_tag> { static inline const char* apply() { return "LineString"; } };
51+
52+
template <>
53+
struct geojson_feature_type<multi_linestring_tag> { static inline const char* apply() { return "MultiLineString"; } };
54+
55+
template <>
56+
struct geojson_feature_type<polygon_tag> { static inline const char* apply() { return "Polygon"; } };
57+
58+
template <>
59+
struct geojson_feature_type<multi_polygon_tag> { static inline const char* apply() { return "MultiPolygon"; } };
60+
61+
} // namespace dispatch
62+
#endif
63+
64+
/*!
65+
\brief Helper class to create geojson output
66+
*/
67+
struct geojson_writer
68+
{
69+
70+
private:
71+
std::ostream& m_out;
72+
73+
std::size_t feature_count = 0;
74+
std::size_t property_count = 0;
75+
76+
template <typename T>
77+
void stream_quoted(T const& entry)
78+
{
79+
m_out << '"' << entry << '"';
80+
}
81+
82+
void start_feature()
83+
{
84+
end_properties();
85+
end_feature();
86+
87+
m_out << (feature_count > 0 ? ",\n" : "") << R"({"type": "Feature")";
88+
feature_count++;
89+
}
90+
91+
void start_property()
92+
{
93+
// Write a comma, either after the "geometry" tag, or after the previous property
94+
// If there are no properties yet, start the list of properties
95+
m_out << "," << (property_count == 0 ? R"("properties": {)" : "") << '\n';
96+
property_count++;
97+
}
98+
99+
void end_properties()
100+
{
101+
if (property_count > 0)
102+
{
103+
m_out << "}\n";
104+
property_count = 0;
105+
}
106+
}
107+
void end_feature()
108+
{
109+
if (feature_count > 0)
110+
{
111+
m_out << "}\n";
112+
}
113+
}
114+
115+
public:
116+
explicit geojson_writer(std::ostream& out) : m_out(out)
117+
{
118+
m_out << R"({"type": "FeatureCollection", "features": [)" << '\n';
119+
}
120+
121+
~geojson_writer()
122+
{
123+
end_properties();
124+
end_feature();
125+
126+
m_out << "]}";
127+
}
128+
129+
// Set a property. It is expected that a feature is already started.
130+
template <typename T>
131+
void add_property(std::string const& name, T const& value)
132+
{
133+
constexpr bool needs_quotes
134+
= std::is_same<T, std::string>::value
135+
|| std::is_same<typename std::remove_extent<T>::type, char>::value;
136+
137+
start_property();
138+
stream_quoted(name);
139+
m_out << ": ";
140+
if BOOST_GEOMETRY_CONSTEXPR(needs_quotes)
141+
{
142+
stream_quoted(value);
143+
}
144+
else
145+
{
146+
m_out << std::boolalpha << value;
147+
}
148+
}
149+
150+
// The method "feature" should be called to start a feature.
151+
// After that, properties can be added, until a new "feature" is called,
152+
// or the instance is destructed
153+
template <typename Geometry>
154+
void feature(Geometry const& geometry)
155+
{
156+
using tag_t = typename tag<Geometry>::type;
157+
158+
start_feature();
159+
160+
// Write the comma after either the "Feature" tag
161+
m_out << ",\n";
162+
163+
// Write the geometry
164+
m_out << R"("geometry": {"type":)";
165+
stream_quoted(dispatch::geojson_feature_type<tag_t>::apply());
166+
m_out << R"(, "coordinates": )";
167+
168+
// A ring is modelled as a geojson polygon, and therefore the opening and closing
169+
// list marks should be duplicated to indicate empty interior rings.
170+
constexpr bool dup = std::is_same<tag_t, ring_tag>::value;
171+
const char* list_open = dup ? "[[" : "[";
172+
const char* list_close = dup ? "]]" : "]";
173+
174+
// Indicate that dsv should close any ring automatically if its model is open
175+
constexpr bool close = geometry::closure<Geometry>::value == geometry::open;
176+
177+
m_out << geometry::dsv(geometry, ", ", "[", "]", ", ", list_open,
178+
list_close, ",", close) << "}\n";
179+
}
180+
181+
};
182+
183+
}} // namespace boost::geometry
184+
185+
#endif

0 commit comments

Comments
 (0)