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 <> |
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> |
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 <> |
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(name: "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(name: "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: json_geometries) }; |
66 | } |
67 | |
68 | const auto &coords_itr = json.FindMember(name: "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: json_coords) }; |
79 | if (type == "MultiPoint" ) |
80 | return geometry{ convert<multi_point>(json: json_coords) }; |
81 | if (type == "LineString" ) |
82 | return geometry{ convert<line_string>(json: json_coords) }; |
83 | if (type == "MultiLineString" ) |
84 | return geometry{ convert<multi_line_string>(json: json_coords) }; |
85 | if (type == "Polygon" ) |
86 | return geometry{ convert<polygon>(json: json_coords) }; |
87 | if (type == "MultiPolygon" ) |
88 | return geometry{ convert<multi_polygon>(json: 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 <> |
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(args: std::string(member.name.GetString(), member.name.GetStringLength()), |
104 | args: convert<value>(json: member.value)); |
105 | } |
106 | return result; |
107 | } |
108 | |
109 | template <> |
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 <> |
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 <> |
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(name: "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(name: "geometry" ); |
164 | |
165 | if (geom_itr == json_end) |
166 | throw error("Feature must have a geometry property" ); |
167 | |
168 | feature result{ convert<geometry>(json: geom_itr->value) }; |
169 | |
170 | auto const &id_itr = json.FindMember(name: "id" ); |
171 | if (id_itr != json_end) { |
172 | result.id = convert<identifier>(json: id_itr->value); |
173 | } |
174 | |
175 | auto const &prop_itr = json.FindMember(name: "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: json_props); |
180 | } |
181 | } |
182 | |
183 | return result; |
184 | } |
185 | |
186 | template <> |
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(name: "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(name: "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(n: size); |
213 | |
214 | for (auto &feature_obj : json_features.GetArray()) { |
215 | collection.push_back(x: convert<feature>(json: 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> |
228 | T parse(const std::string &json) { |
229 | rapidjson_document d; |
230 | d.Parse(str: json.c_str()); |
231 | if (d.HasParseError()) { |
232 | std::stringstream message; |
233 | message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(parseErrorCode: 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 | |
246 | geojson parse(const std::string &json) { |
247 | return parse<geojson>(json); |
248 | } |
249 | |
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: |
265 | const char * operator()(const point&) { |
266 | return "Point" ; |
267 | } |
268 | |
269 | const char * operator()(const line_string&) { |
270 | return "LineString" ; |
271 | } |
272 | |
273 | const char * operator()(const polygon&) { |
274 | return "Polygon" ; |
275 | } |
276 | |
277 | const char * operator()(const multi_point&) { |
278 | return "MultiPoint" ; |
279 | } |
280 | |
281 | const char * operator()(const multi_line_string&) { |
282 | return "MultiLineString" ; |
283 | } |
284 | |
285 | const char * operator()(const multi_polygon&) { |
286 | return "MultiPolygon" ; |
287 | } |
288 | |
289 | 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> |
299 | 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 | |
308 | rapidjson_value operator()(const point& element) { |
309 | rapidjson_value result; |
310 | result.SetArray(); |
311 | result.PushBack(value: element.x, allocator); |
312 | result.PushBack(value: element.y, allocator); |
313 | return result; |
314 | } |
315 | |
316 | rapidjson_value operator()(const geometry& element) { |
317 | return convert(element, allocator); |
318 | } |
319 | }; |
320 | |
321 | struct to_value { |
322 | rapidjson_allocator& allocator; |
323 | |
324 | rapidjson_value operator()(null_value_t) { |
325 | rapidjson_value result; |
326 | result.SetNull(); |
327 | return result; |
328 | } |
329 | |
330 | rapidjson_value operator()(bool t) { |
331 | rapidjson_value result; |
332 | result.SetBool(t); |
333 | return result; |
334 | } |
335 | |
336 | rapidjson_value operator()(int64_t t) { |
337 | rapidjson_value result; |
338 | result.SetInt64(t); |
339 | return result; |
340 | } |
341 | |
342 | rapidjson_value operator()(uint64_t t) { |
343 | rapidjson_value result; |
344 | result.SetUint64(t); |
345 | return result; |
346 | } |
347 | |
348 | rapidjson_value operator()(double t) { |
349 | rapidjson_value result; |
350 | result.SetDouble(t); |
351 | return result; |
352 | } |
353 | |
354 | rapidjson_value operator()(const std::string& t) { |
355 | rapidjson_value result; |
356 | result.SetString(s: t.data(), length: rapidjson::SizeType(t.size()), allocator); |
357 | return result; |
358 | } |
359 | |
360 | 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: value::visit(v: item, f&: *this), allocator); |
365 | } |
366 | return result; |
367 | } |
368 | |
369 | 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 | name: rapidjson::GenericStringRef<char> { |
375 | property.first.data(), |
376 | rapidjson::SizeType(property.first.size()) |
377 | }, |
378 | value: value::visit(v: property.second, f&: *this), |
379 | allocator); |
380 | } |
381 | return result; |
382 | } |
383 | }; |
384 | |
385 | template <> |
386 | rapidjson_value convert<geometry>(const geometry& element, rapidjson_allocator& allocator) { |
387 | rapidjson_value result(rapidjson::kObjectType); |
388 | |
389 | result.AddMember( |
390 | name: "type" , |
391 | value: rapidjson::GenericStringRef<char> { geometry::visit(v: element, f: to_type()) }, |
392 | allocator); |
393 | |
394 | result.AddMember( |
395 | name: rapidjson::GenericStringRef<char> { element.is<geometry_collection>() ? "geometries" : "coordinates" }, |
396 | value: geometry::visit(v: element, f: to_coordinates_or_geometries { .allocator: allocator }), |
397 | allocator); |
398 | |
399 | return result; |
400 | } |
401 | |
402 | template <> |
403 | rapidjson_value convert<feature>(const feature& element, rapidjson_allocator& allocator) { |
404 | rapidjson_value result(rapidjson::kObjectType); |
405 | result.AddMember(name: "type" , value: "Feature" , allocator); |
406 | |
407 | if (element.id) { |
408 | result.AddMember(name: "id" , value: identifier::visit(v: *element.id, f: to_value { .allocator: allocator }), allocator); |
409 | } |
410 | |
411 | result.AddMember(name: "geometry" , value: convert(element: element.geometry, allocator), allocator); |
412 | result.AddMember(name: "properties" , value: to_value { .allocator: allocator }(element.properties), allocator); |
413 | |
414 | return result; |
415 | } |
416 | |
417 | template <> |
418 | rapidjson_value convert<feature_collection>(const feature_collection& collection, rapidjson_allocator& allocator) { |
419 | rapidjson_value result(rapidjson::kObjectType); |
420 | result.AddMember(name: "type" , value: "FeatureCollection" , allocator); |
421 | |
422 | rapidjson_value features(rapidjson::kArrayType); |
423 | for (const auto& element : collection) { |
424 | features.PushBack(value: convert(element, allocator), allocator); |
425 | } |
426 | result.AddMember(name: "features" , value&: features, allocator); |
427 | |
428 | return result; |
429 | } |
430 | |
431 | rapidjson_value convert(const geojson& element, rapidjson_allocator& allocator) { |
432 | return geojson::visit(v: element, f: [&] (const auto& alternative) { |
433 | return convert(alternative, allocator); |
434 | }); |
435 | } |
436 | |
437 | template <class 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 | |
446 | std::string stringify(const geojson& element) { |
447 | return geojson::visit(v: element, f: [] (const auto& alternative) { |
448 | return stringify(alternative); |
449 | }); |
450 | } |
451 | |
452 | } // namespace geojson |
453 | } // namespace mapbox |
454 | |