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