1 #pragma once
2
3 #include <mapbox/geojson.hpp>
4 #include <mapbox/geojson/rapidjson.hpp>
5
6 #include <rapidjson/document.h>
7 #include <rapidjson/writer.h>
8 #include <rapidjson/stringbuffer.h>
9 #include <rapidjson/error/en.h>
10
11 #include <sstream>
12
13 namespace mapbox {
14 namespace geojson {
15
16 using error = std::runtime_error;
17 using prop_map = std::unordered_map<std::string, value>;
18
19 template <typename T>
20 T convert(const rapidjson_value &json);
21
22 template <>
convert(const rapidjson_value & json)23 point convert<point>(const rapidjson_value &json) {
24 if (json.Size() < 2)
25 throw error("coordinates array must have at least 2 numbers");
26
27 return point{ json[0].GetDouble(), json[1].GetDouble() };
28 }
29
30 template <typename Cont>
convert(const rapidjson_value & json)31 Cont convert(const rapidjson_value &json) {
32 Cont points;
33 auto size = json.Size();
34 points.reserve(size);
35
36 for (auto &element : json.GetArray()) {
37 points.push_back(convert<typename Cont::value_type>(element));
38 }
39 return points;
40 }
41
42 template <>
convert(const rapidjson_value & json)43 geometry convert<geometry>(const rapidjson_value &json) {
44 if (!json.IsObject())
45 throw error("Geometry must be an object");
46
47 const auto &json_end = json.MemberEnd();
48
49 const auto &type_itr = json.FindMember("type");
50 if (type_itr == json_end)
51 throw error("Geometry must have a type property");
52
53 const auto &type = type_itr->value;
54
55 if (type == "GeometryCollection") {
56 const auto &geometries_itr = json.FindMember("geometries");
57 if (geometries_itr == json_end)
58 throw error("GeometryCollection must have a geometries property");
59
60 const auto &json_geometries = geometries_itr->value;
61
62 if (!json_geometries.IsArray())
63 throw error("GeometryCollection geometries property must be an array");
64
65 return geometry{ convert<geometry_collection>(json_geometries) };
66 }
67
68 const auto &coords_itr = json.FindMember("coordinates");
69
70 if (coords_itr == json_end)
71 throw error(std::string(type.GetString()) + " geometry must have a coordinates property");
72
73 const auto &json_coords = coords_itr->value;
74 if (!json_coords.IsArray())
75 throw error("coordinates property must be an array");
76
77 if (type == "Point")
78 return geometry{ convert<point>(json_coords) };
79 if (type == "MultiPoint")
80 return geometry{ convert<multi_point>(json_coords) };
81 if (type == "LineString")
82 return geometry{ convert<line_string>(json_coords) };
83 if (type == "MultiLineString")
84 return geometry{ convert<multi_line_string>(json_coords) };
85 if (type == "Polygon")
86 return geometry{ convert<polygon>(json_coords) };
87 if (type == "MultiPolygon")
88 return geometry{ convert<multi_polygon>(json_coords) };
89
90 throw error(std::string(type.GetString()) + " not yet implemented");
91 }
92
93 template <>
94 value convert<value>(const rapidjson_value &json);
95
96 template <>
convert(const rapidjson_value & json)97 prop_map convert(const rapidjson_value &json) {
98 if (!json.IsObject())
99 throw error("properties must be an object");
100
101 prop_map result;
102 for (auto &member : json.GetObject()) {
103 result.emplace(std::string(member.name.GetString(), member.name.GetStringLength()),
104 convert<value>(member.value));
105 }
106 return result;
107 }
108
109 template <>
convert(const rapidjson_value & json)110 value convert<value>(const rapidjson_value &json) {
111 switch (json.GetType()) {
112 case rapidjson::kNullType:
113 return ::mapbox::geometry::null_value_t{};
114 case rapidjson::kFalseType:
115 return false;
116 case rapidjson::kTrueType:
117 return true;
118 case rapidjson::kObjectType:
119 return convert<prop_map>(json);
120 case rapidjson::kArrayType:
121 return convert<std::vector<value>>(json);
122 case rapidjson::kStringType:
123 return std::string(json.GetString(), json.GetStringLength());
124 default:
125 assert(json.GetType() == rapidjson::kNumberType);
126 if (json.IsUint64())
127 return std::uint64_t(json.GetUint64());
128 if (json.IsInt64())
129 return std::int64_t(json.GetInt64());
130 return json.GetDouble();
131 }
132 }
133
134 template <>
convert(const rapidjson_value & json)135 identifier convert<identifier>(const rapidjson_value &json) {
136 switch (json.GetType()) {
137 case rapidjson::kStringType:
138 return std::string(json.GetString(), json.GetStringLength());
139 case rapidjson::kNumberType:
140 if (json.IsUint64())
141 return std::uint64_t(json.GetUint64());
142 if (json.IsInt64())
143 return std::int64_t(json.GetInt64());
144 return json.GetDouble();
145 default:
146 throw error("Feature id must be a string or number");
147 }
148 }
149
150 template <>
convert(const rapidjson_value & json)151 feature convert<feature>(const rapidjson_value &json) {
152 if (!json.IsObject())
153 throw error("Feature must be an object");
154
155 auto const &json_end = json.MemberEnd();
156 auto const &type_itr = json.FindMember("type");
157
158 if (type_itr == json_end)
159 throw error("Feature must have a type property");
160 if (type_itr->value != "Feature")
161 throw error("Feature type must be Feature");
162
163 auto const &geom_itr = json.FindMember("geometry");
164
165 if (geom_itr == json_end)
166 throw error("Feature must have a geometry property");
167
168 feature result{ convert<geometry>(geom_itr->value) };
169
170 auto const &id_itr = json.FindMember("id");
171 if (id_itr != json_end) {
172 result.id = convert<identifier>(id_itr->value);
173 }
174
175 auto const &prop_itr = json.FindMember("properties");
176 if (prop_itr != json_end) {
177 const auto &json_props = prop_itr->value;
178 if (!json_props.IsNull()) {
179 result.properties = convert<prop_map>(json_props);
180 }
181 }
182
183 return result;
184 }
185
186 template <>
convert(const rapidjson_value & json)187 geojson convert<geojson>(const rapidjson_value &json) {
188 if (!json.IsObject())
189 throw error("GeoJSON must be an object");
190
191 const auto &type_itr = json.FindMember("type");
192 const auto &json_end = json.MemberEnd();
193
194 if (type_itr == json_end)
195 throw error("GeoJSON must have a type property");
196
197 const auto &type = type_itr->value;
198
199 if (type == "FeatureCollection") {
200 const auto &features_itr = json.FindMember("features");
201 if (features_itr == json_end)
202 throw error("FeatureCollection must have features property");
203
204 const auto &json_features = features_itr->value;
205
206 if (!json_features.IsArray())
207 throw error("FeatureCollection features property must be an array");
208
209 feature_collection collection;
210
211 const auto &size = json_features.Size();
212 collection.reserve(size);
213
214 for (auto &feature_obj : json_features.GetArray()) {
215 collection.push_back(convert<feature>(feature_obj));
216 }
217
218 return geojson{ collection };
219 }
220
221 if (type == "Feature")
222 return geojson{ convert<feature>(json) };
223
224 return geojson{ convert<geometry>(json) };
225 }
226
227 template <class T>
parse(const std::string & json)228 T parse(const std::string &json) {
229 rapidjson_document d;
230 d.Parse(json.c_str());
231 if (d.HasParseError()) {
232 std::stringstream message;
233 message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError());
234 throw error(message.str());
235 }
236 return convert<T>(d);
237 }
238
239 template <>
240 geometry parse<geometry>(const std::string &);
241 template <>
242 feature parse<feature>(const std::string &);
243 template <>
244 feature_collection parse<feature_collection>(const std::string &);
245
parse(const std::string & json)246 geojson parse(const std::string &json) {
247 return parse<geojson>(json);
248 }
249
convert(const rapidjson_value & json)250 geojson convert(const rapidjson_value &json) {
251 return convert<geojson>(json);
252 }
253
254 template <>
255 rapidjson_value convert<geometry>(const geometry&, rapidjson_allocator&);
256
257 template <>
258 rapidjson_value convert<feature>(const feature&, rapidjson_allocator&);
259
260 template <>
261 rapidjson_value convert<feature_collection>(const feature_collection&, rapidjson_allocator&);
262
263 struct to_type {
264 public:
operator ()mapbox::geojson::to_type265 const char * operator()(const point&) {
266 return "Point";
267 }
268
operator ()mapbox::geojson::to_type269 const char * operator()(const line_string&) {
270 return "LineString";
271 }
272
operator ()mapbox::geojson::to_type273 const char * operator()(const polygon&) {
274 return "Polygon";
275 }
276
operator ()mapbox::geojson::to_type277 const char * operator()(const multi_point&) {
278 return "MultiPoint";
279 }
280
operator ()mapbox::geojson::to_type281 const char * operator()(const multi_line_string&) {
282 return "MultiLineString";
283 }
284
operator ()mapbox::geojson::to_type285 const char * operator()(const multi_polygon&) {
286 return "MultiPolygon";
287 }
288
operator ()mapbox::geojson::to_type289 const char * operator()(const geometry_collection&) {
290 return "GeometryCollection";
291 }
292 };
293
294 struct to_coordinates_or_geometries {
295 rapidjson_allocator& allocator;
296
297 // Handles line_string, polygon, multi_point, multi_line_string, multi_polygon, and geometry_collection.
298 template <class E>
operator ()mapbox::geojson::to_coordinates_or_geometries299 rapidjson_value operator()(const std::vector<E>& vector) {
300 rapidjson_value result;
301 result.SetArray();
302 for (std::size_t i = 0; i < vector.size(); ++i) {
303 result.PushBack(operator()(vector[i]), allocator);
304 }
305 return result;
306 }
307
operator ()mapbox::geojson::to_coordinates_or_geometries308 rapidjson_value operator()(const point& element) {
309 rapidjson_value result;
310 result.SetArray();
311 result.PushBack(element.x, allocator);
312 result.PushBack(element.y, allocator);
313 return result;
314 }
315
operator ()mapbox::geojson::to_coordinates_or_geometries316 rapidjson_value operator()(const geometry& element) {
317 return convert(element, allocator);
318 }
319 };
320
321 struct to_value {
322 rapidjson_allocator& allocator;
323
operator ()mapbox::geojson::to_value324 rapidjson_value operator()(null_value_t) {
325 rapidjson_value result;
326 result.SetNull();
327 return result;
328 }
329
operator ()mapbox::geojson::to_value330 rapidjson_value operator()(bool t) {
331 rapidjson_value result;
332 result.SetBool(t);
333 return result;
334 }
335
operator ()mapbox::geojson::to_value336 rapidjson_value operator()(int64_t t) {
337 rapidjson_value result;
338 result.SetInt64(t);
339 return result;
340 }
341
operator ()mapbox::geojson::to_value342 rapidjson_value operator()(uint64_t t) {
343 rapidjson_value result;
344 result.SetUint64(t);
345 return result;
346 }
347
operator ()mapbox::geojson::to_value348 rapidjson_value operator()(double t) {
349 rapidjson_value result;
350 result.SetDouble(t);
351 return result;
352 }
353
operator ()mapbox::geojson::to_value354 rapidjson_value operator()(const std::string& t) {
355 rapidjson_value result;
356 result.SetString(t.data(), rapidjson::SizeType(t.size()), allocator);
357 return result;
358 }
359
operator ()mapbox::geojson::to_value360 rapidjson_value operator()(const std::vector<value>& array) {
361 rapidjson_value result;
362 result.SetArray();
363 for (const auto& item : array) {
364 result.PushBack(value::visit(item, *this), allocator);
365 }
366 return result;
367 }
368
operator ()mapbox::geojson::to_value369 rapidjson_value operator()(const std::unordered_map<std::string, value>& map) {
370 rapidjson_value result;
371 result.SetObject();
372 for (const auto& property : map) {
373 result.AddMember(
374 rapidjson::GenericStringRef<char> {
375 property.first.data(),
376 rapidjson::SizeType(property.first.size())
377 },
378 value::visit(property.second, *this),
379 allocator);
380 }
381 return result;
382 }
383 };
384
385 template <>
convert(const geometry & element,rapidjson_allocator & allocator)386 rapidjson_value convert<geometry>(const geometry& element, rapidjson_allocator& allocator) {
387 rapidjson_value result(rapidjson::kObjectType);
388
389 result.AddMember(
390 "type",
391 rapidjson::GenericStringRef<char> { geometry::visit(element, to_type()) },
392 allocator);
393
394 result.AddMember(
395 rapidjson::GenericStringRef<char> { element.is<geometry_collection>() ? "geometries" : "coordinates" },
396 geometry::visit(element, to_coordinates_or_geometries { allocator }),
397 allocator);
398
399 return result;
400 }
401
402 template <>
convert(const feature & element,rapidjson_allocator & allocator)403 rapidjson_value convert<feature>(const feature& element, rapidjson_allocator& allocator) {
404 rapidjson_value result(rapidjson::kObjectType);
405 result.AddMember("type", "Feature", allocator);
406
407 if (element.id) {
408 result.AddMember("id", identifier::visit(*element.id, to_value { allocator }), allocator);
409 }
410
411 result.AddMember("geometry", convert(element.geometry, allocator), allocator);
412 result.AddMember("properties", to_value { allocator }(element.properties), allocator);
413
414 return result;
415 }
416
417 template <>
convert(const feature_collection & collection,rapidjson_allocator & allocator)418 rapidjson_value convert<feature_collection>(const feature_collection& collection, rapidjson_allocator& allocator) {
419 rapidjson_value result(rapidjson::kObjectType);
420 result.AddMember("type", "FeatureCollection", allocator);
421
422 rapidjson_value features(rapidjson::kArrayType);
423 for (const auto& element : collection) {
424 features.PushBack(convert(element, allocator), allocator);
425 }
426 result.AddMember("features", features, allocator);
427
428 return result;
429 }
430
convert(const geojson & element,rapidjson_allocator & allocator)431 rapidjson_value convert(const geojson& element, rapidjson_allocator& allocator) {
432 return geojson::visit(element, [&] (const auto& alternative) {
433 return convert(alternative, allocator);
434 });
435 }
436
437 template <class T>
stringify(const T & t)438 std::string stringify(const T& t) {
439 rapidjson_allocator allocator;
440 rapidjson::StringBuffer buffer;
441 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
442 convert(t, allocator).Accept(writer);
443 return buffer.GetString();
444 }
445
stringify(const geojson & element)446 std::string stringify(const geojson& element) {
447 return geojson::visit(element, [] (const auto& alternative) {
448 return stringify(alternative);
449 });
450 }
451
452 } // namespace geojson
453 } // namespace mapbox
454