blob: 48d81e41141c3bdc786b4cb199c1288d207fa439 [file] [log] [blame]
#pragma once
#include <mapbox/geojson.hpp>
#include <mapbox/geojson/rapidjson.hpp>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/error/en.h>
#include <sstream>
namespace mapbox {
namespace geojson {
using error = std::runtime_error;
using prop_map = std::unordered_map<std::string, value>;
template <typename T>
T convert(const rapidjson_value &json);
template <>
point convert<point>(const rapidjson_value &json) {
if (json.Size() < 2)
throw error("coordinates array must have at least 2 numbers");
return point{ json[0].GetDouble(), json[1].GetDouble() };
}
template <typename Cont>
Cont convert(const rapidjson_value &json) {
Cont points;
auto size = json.Size();
points.reserve(size);
for (auto &element : json.GetArray()) {
points.push_back(convert<typename Cont::value_type>(element));
}
return points;
}
template <>
geometry convert<geometry>(const rapidjson_value &json) {
if (!json.IsObject())
throw error("Geometry must be an object");
const auto &json_end = json.MemberEnd();
const auto &type_itr = json.FindMember("type");
if (type_itr == json_end)
throw error("Geometry must have a type property");
const auto &type = type_itr->value;
if (type == "GeometryCollection") {
const auto &geometries_itr = json.FindMember("geometries");
if (geometries_itr == json_end)
throw error("GeometryCollection must have a geometries property");
const auto &json_geometries = geometries_itr->value;
if (!json_geometries.IsArray())
throw error("GeometryCollection geometries property must be an array");
return geometry{ convert<geometry_collection>(json_geometries) };
}
const auto &coords_itr = json.FindMember("coordinates");
if (coords_itr == json_end)
throw error(std::string(type.GetString()) + " geometry must have a coordinates property");
const auto &json_coords = coords_itr->value;
if (!json_coords.IsArray())
throw error("coordinates property must be an array");
if (type == "Point")
return geometry{ convert<point>(json_coords) };
if (type == "MultiPoint")
return geometry{ convert<multi_point>(json_coords) };
if (type == "LineString")
return geometry{ convert<line_string>(json_coords) };
if (type == "MultiLineString")
return geometry{ convert<multi_line_string>(json_coords) };
if (type == "Polygon")
return geometry{ convert<polygon>(json_coords) };
if (type == "MultiPolygon")
return geometry{ convert<multi_polygon>(json_coords) };
throw error(std::string(type.GetString()) + " not yet implemented");
}
template <>
value convert<value>(const rapidjson_value &json);
template <>
prop_map convert(const rapidjson_value &json) {
if (!json.IsObject())
throw error("properties must be an object");
prop_map result;
for (auto &member : json.GetObject()) {
result.emplace(std::string(member.name.GetString(), member.name.GetStringLength()),
convert<value>(member.value));
}
return result;
}
template <>
value convert<value>(const rapidjson_value &json) {
switch (json.GetType()) {
case rapidjson::kNullType:
return ::mapbox::geometry::null_value_t{};
case rapidjson::kFalseType:
return false;
case rapidjson::kTrueType:
return true;
case rapidjson::kObjectType:
return convert<prop_map>(json);
case rapidjson::kArrayType:
return convert<std::vector<value>>(json);
case rapidjson::kStringType:
return std::string(json.GetString(), json.GetStringLength());
default:
assert(json.GetType() == rapidjson::kNumberType);
if (json.IsUint64())
return std::uint64_t(json.GetUint64());
if (json.IsInt64())
return std::int64_t(json.GetInt64());
return json.GetDouble();
}
}
template <>
identifier convert<identifier>(const rapidjson_value &json) {
switch (json.GetType()) {
case rapidjson::kStringType:
return std::string(json.GetString(), json.GetStringLength());
case rapidjson::kNumberType:
if (json.IsUint64())
return std::uint64_t(json.GetUint64());
if (json.IsInt64())
return std::int64_t(json.GetInt64());
return json.GetDouble();
default:
throw error("Feature id must be a string or number");
}
}
template <>
feature convert<feature>(const rapidjson_value &json) {
if (!json.IsObject())
throw error("Feature must be an object");
auto const &json_end = json.MemberEnd();
auto const &type_itr = json.FindMember("type");
if (type_itr == json_end)
throw error("Feature must have a type property");
if (type_itr->value != "Feature")
throw error("Feature type must be Feature");
auto const &geom_itr = json.FindMember("geometry");
if (geom_itr == json_end)
throw error("Feature must have a geometry property");
feature result{ convert<geometry>(geom_itr->value) };
auto const &id_itr = json.FindMember("id");
if (id_itr != json_end) {
result.id = convert<identifier>(id_itr->value);
}
auto const &prop_itr = json.FindMember("properties");
if (prop_itr != json_end) {
const auto &json_props = prop_itr->value;
if (!json_props.IsNull()) {
result.properties = convert<prop_map>(json_props);
}
}
return result;
}
template <>
geojson convert<geojson>(const rapidjson_value &json) {
if (!json.IsObject())
throw error("GeoJSON must be an object");
const auto &type_itr = json.FindMember("type");
const auto &json_end = json.MemberEnd();
if (type_itr == json_end)
throw error("GeoJSON must have a type property");
const auto &type = type_itr->value;
if (type == "FeatureCollection") {
const auto &features_itr = json.FindMember("features");
if (features_itr == json_end)
throw error("FeatureCollection must have features property");
const auto &json_features = features_itr->value;
if (!json_features.IsArray())
throw error("FeatureCollection features property must be an array");
feature_collection collection;
const auto &size = json_features.Size();
collection.reserve(size);
for (auto &feature_obj : json_features.GetArray()) {
collection.push_back(convert<feature>(feature_obj));
}
return geojson{ collection };
}
if (type == "Feature")
return geojson{ convert<feature>(json) };
return geojson{ convert<geometry>(json) };
}
template <class T>
T parse(const std::string &json) {
rapidjson_document d;
d.Parse(json.c_str());
if (d.HasParseError()) {
std::stringstream message;
message << d.GetErrorOffset() << " - " << rapidjson::GetParseError_En(d.GetParseError());
throw error(message.str());
}
return convert<T>(d);
}
template <>
geometry parse<geometry>(const std::string &);
template <>
feature parse<feature>(const std::string &);
template <>
feature_collection parse<feature_collection>(const std::string &);
geojson parse(const std::string &json) {
return parse<geojson>(json);
}
geojson convert(const rapidjson_value &json) {
return convert<geojson>(json);
}
template <>
rapidjson_value convert<geometry>(const geometry&, rapidjson_allocator&);
template <>
rapidjson_value convert<feature>(const feature&, rapidjson_allocator&);
template <>
rapidjson_value convert<feature_collection>(const feature_collection&, rapidjson_allocator&);
struct to_type {
public:
const char * operator()(const point&) {
return "Point";
}
const char * operator()(const line_string&) {
return "LineString";
}
const char * operator()(const polygon&) {
return "Polygon";
}
const char * operator()(const multi_point&) {
return "MultiPoint";
}
const char * operator()(const multi_line_string&) {
return "MultiLineString";
}
const char * operator()(const multi_polygon&) {
return "MultiPolygon";
}
const char * operator()(const geometry_collection&) {
return "GeometryCollection";
}
};
struct to_coordinates_or_geometries {
rapidjson_allocator& allocator;
// Handles line_string, polygon, multi_point, multi_line_string, multi_polygon, and geometry_collection.
template <class E>
rapidjson_value operator()(const std::vector<E>& vector) {
rapidjson_value result;
result.SetArray();
for (std::size_t i = 0; i < vector.size(); ++i) {
result.PushBack(operator()(vector[i]), allocator);
}
return result;
}
rapidjson_value operator()(const point& element) {
rapidjson_value result;
result.SetArray();
result.PushBack(element.x, allocator);
result.PushBack(element.y, allocator);
return result;
}
rapidjson_value operator()(const geometry& element) {
return convert(element, allocator);
}
};
struct to_value {
rapidjson_allocator& allocator;
rapidjson_value operator()(null_value_t) {
rapidjson_value result;
result.SetNull();
return result;
}
rapidjson_value operator()(bool t) {
rapidjson_value result;
result.SetBool(t);
return result;
}
rapidjson_value operator()(int64_t t) {
rapidjson_value result;
result.SetInt64(t);
return result;
}
rapidjson_value operator()(uint64_t t) {
rapidjson_value result;
result.SetUint64(t);
return result;
}
rapidjson_value operator()(double t) {
rapidjson_value result;
result.SetDouble(t);
return result;
}
rapidjson_value operator()(const std::string& t) {
rapidjson_value result;
result.SetString(t.data(), rapidjson::SizeType(t.size()), allocator);
return result;
}
rapidjson_value operator()(const std::vector<value>& array) {
rapidjson_value result;
result.SetArray();
for (const auto& item : array) {
result.PushBack(value::visit(item, *this), allocator);
}
return result;
}
rapidjson_value operator()(const std::unordered_map<std::string, value>& map) {
rapidjson_value result;
result.SetObject();
for (const auto& property : map) {
result.AddMember(
rapidjson::GenericStringRef<char> {
property.first.data(),
rapidjson::SizeType(property.first.size())
},
value::visit(property.second, *this),
allocator);
}
return result;
}
};
template <>
rapidjson_value convert<geometry>(const geometry& element, rapidjson_allocator& allocator) {
rapidjson_value result(rapidjson::kObjectType);
result.AddMember(
"type",
rapidjson::GenericStringRef<char> { geometry::visit(element, to_type()) },
allocator);
result.AddMember(
rapidjson::GenericStringRef<char> { element.is<geometry_collection>() ? "geometries" : "coordinates" },
geometry::visit(element, to_coordinates_or_geometries { allocator }),
allocator);
return result;
}
template <>
rapidjson_value convert<feature>(const feature& element, rapidjson_allocator& allocator) {
rapidjson_value result(rapidjson::kObjectType);
result.AddMember("type", "Feature", allocator);
if (element.id) {
result.AddMember("id", identifier::visit(*element.id, to_value { allocator }), allocator);
}
result.AddMember("geometry", convert(element.geometry, allocator), allocator);
result.AddMember("properties", to_value { allocator }(element.properties), allocator);
return result;
}
template <>
rapidjson_value convert<feature_collection>(const feature_collection& collection, rapidjson_allocator& allocator) {
rapidjson_value result(rapidjson::kObjectType);
result.AddMember("type", "FeatureCollection", allocator);
rapidjson_value features(rapidjson::kArrayType);
for (const auto& element : collection) {
features.PushBack(convert(element, allocator), allocator);
}
result.AddMember("features", features, allocator);
return result;
}
rapidjson_value convert(const geojson& element, rapidjson_allocator& allocator) {
return geojson::visit(element, [&] (const auto& alternative) {
return convert(alternative, allocator);
});
}
template <class T>
std::string stringify(const T& t) {
rapidjson_allocator allocator;
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
convert(t, allocator).Accept(writer);
return buffer.GetString();
}
std::string stringify(const geojson& element) {
return geojson::visit(element, [] (const auto& alternative) {
return stringify(alternative);
});
}
} // namespace geojson
} // namespace mapbox