Как создать геометрию, используя вариант
Можно ли определить boost::geometry
объект с помощью boost::variant
?
Этот код не компилируется, так как ему не нравится вариант объекта, используемый внутри geom::read_wkt()
,
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant.hpp>
namespace geom = boost::geometry;
typedef geom::model::d2::point_xy<double> point_type;
typedef geom::model::linestring<point_type> linestring_type;
typedef geom::model::polygon<point_type> polygon_type;
typedef boost::variant
<
point_type,
linestring_type,
polygon_type,
>
geometryVariant;
int main() {
std::string wkt = "LINESTRING(0 0, 1 1, 2, 2)";
geometryVariant gv;
geom::read_wkt(wkt, gv);
return 0;
}
Однако, если я явно определю linestring_type
работает нормально
int main() {
std::string wkt = "LINESTRING(0 0, 1 1, 2, 2)";
linestring_type ls;
geom::read_wkt(wkt, ls);
return 0;
}
1 ответ
Ты плывешь против дизайна библиотеки. Если вы хотите полиморфный тип времени выполнения, используйте его.
Конечно, вы можете построить один поверх Boost Geometry, используя варианты. Итак, давайте предположим, что вы хотите сделать это.
Как всегда с вариантами, чтобы работать с ними в общем, вам нужно, чтобы посетитель посетил потенциальные типы элементов:
struct {
using result_type = bool;
template <typename... T>
bool operator()(std::string const& wkt, boost::variant<T...>& geo) const {
return boost::apply_visitor(boost::bind(*this, boost::ref(wkt), _1), geo);
}
template <typename Geo>
bool operator()(std::string const& wkt, Geo& geo) const {
try {
geom::read_wkt(wkt, geo);
return true;
} catch(geom::read_wkt_exception const& cant) {
return false;
}
}
} read_wkt;
Теперь вы можете
int main() {
std::string wkt = "LINESTRING(0 0, 1 1, 2, 2)";
geometryVariant gv = linestring_type{};
if (read_wkt(wkt, gv))
std::cout << geom::wkt(gv);
}
Печать:
LINESTRING(0 0,1 1,2 0,2 0)
Дальнейшие мысли
Это может не делать то, что вы ожидаете. Если бы у вас был только вариант, созданный по умолчанию, он бы не работал, потому что посетитель посетит текущее состояние (которое point_type
) и это не удастся.
Чтобы получить метод динамического чтения, который определяет тип геометрии по входным данным, вы можете:
geometryVariant read_any_wkt(std::string const& wkt) {
{ linestring_type tmp; if (read_wkt(wkt, tmp)) return tmp; }
{ point_type tmp; if (read_wkt(wkt, tmp)) return tmp; }
{ polygon_type tmp; if (read_wkt(wkt, tmp)) return tmp; }
throw geom::read_wkt_exception("read_any_wkt failed", wkt);
}
Который работает: Live On Coliru
int main() {
for (std::string const wkt : {
"LINESTRING(0 0, 1 1, 2 2)",
"POINT(0 0)",
"POLYGON((0 0, 1 1, 2 2, 0 0))", })
{
{
geometryVariant gv;
if (read_wkt(wkt, gv))
std::cout << "read_wkt: " << geom::wkt(gv) << "\n";
}
auto any = read_any_wkt(wkt);
std::cout << "read_any_wkt: " << geom::wkt(any) << "\n";
}
}
Печать
read_any_wkt: LINESTRING(0 0,1 1,2 2)
read_wkt: POINT(0 0)
read_any_wkt: POINT(0 0)
read_any_wkt: POLYGON((0 0,1 1,2 2,0 0))
Бонус: сделай это универсальным
Это немного больше работы, к сожалению:
Live On Coliru (используется C++14)
#include <boost/fusion/include/accumulate.hpp>
#include <boost/fusion/include/vector.hpp>
struct read_any_wkt_t {
geometryVariant operator()(std::string const& wkt) const {
geometryVariant output;
call_impl(wkt, output);
return output;
}
private:
template <typename... T>
static void call_impl(std::string const& wkt, boost::variant<T...>& output) {
boost::fusion::vector<T...> candidates;
bool success = boost::fusion::accumulate(candidates, false, [&wkt, &output](bool success, auto candidate) {
if (!success && read_wkt(wkt, candidate)) {
output = candidate;
return true;
}
return success;
});
if (!success) throw geom::read_wkt_exception("read_any_wkt failed", wkt);
}
} read_any_wkt;
Печать того же вывода.
БОНУС БОНУС
Вместо того, чтобы слепо пытаться анализировать WKT до тех пор, пока не будет выдано исключение, лучшим способом десериализации из WKT было бы на самом деле сначала проанализировать id типа и включить его.
Для этого я разработал пример, используя Boost Spirit X3 для переключения вывода на тип, связанный с ключевым словом ведущего типа.
Разбор становится проще:
template <typename... T>
static void call_impl(std::string const& wkt, boost::variant<T...>& output) {
static auto const switch_ = gen_switch(output);
if (parse(wkt.begin(), wkt.end(), switch_, output)) {
boost::apply_visitor(boost::bind(read_any_helper{}, boost::ref(wkt), _1), output);
} else {
throw geom::read_wkt_exception("Unregistered type", wkt);
}
}
gen_switch
call генерирует Trie, содержащий поддерживаемые типы геометрии, только один раз.
Обратите внимание, что это добавляет некоторые типы, диагностика достоверности и автокоррекция
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_linestring.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>
#include <iostream>
namespace geom = boost::geometry;
namespace bgm = geom::model;
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/bind.hpp>
#include <boost/variant.hpp>
#include <boost/spirit/home/x3.hpp>
namespace detail {
template <typename Variant> struct read_any_helper {
static Variant call(std::string const& wkt) {
Variant output;
call_impl(wkt, output);
return output;
}
using result_type = void;
template <typename Geo> result_type operator()(std::string const& wkt, Geo& output) const {
geom::read_wkt(wkt, output);
}
private:
template <typename... T>
static void call_impl(std::string const& wkt, boost::variant<T...>& output) {
static auto const switch_ = gen_switch(output);
if (parse(wkt.begin(), wkt.end(), switch_, output)) {
boost::apply_visitor(boost::bind(read_any_helper{}, boost::ref(wkt), _1), output);
} else {
throw geom::read_wkt_exception("Unregistered type", wkt);
}
}
template <typename... T>
static auto gen_switch(boost::variant<T...> const&) {
namespace x3 = boost::spirit::x3;
x3::symbols<Variant> result;
boost::fusion::for_each(boost::fusion::vector<T...>{}, [&result](auto&& seed) {
auto const serialized = boost::lexical_cast<std::string>(geom::wkt(seed));
std::string keyword;
if (x3::parse(serialized.begin(), serialized.end(), +x3::alpha, keyword))
result.add(keyword, std::forward<decltype(seed)>(seed));
else
throw std::logic_error(std::string("registering WKT for ") + typeid(seed).name());
});
result.for_each([](auto& key, auto&&...) {
std::cout << "DEBUG: statically registered support for " << key << " type\n";
});
return result;
}
};
}
using point_type = bgm::d2::point_xy<double>;
typedef boost::variant<
point_type,
bgm::linestring<point_type>,
bgm::multi_linestring<bgm::linestring<point_type> >,
bgm::polygon<point_type>,
bgm::multi_polygon<bgm::polygon<point_type> >
> AnyGeo;
template <typename Variant = AnyGeo>
Variant read_any_wkt(std::string const& wkt) {
return detail::read_any_helper<Variant>::call(wkt);
}
int main() {
for (auto wkt : {
"LINESTRING(0 0, 1 1, 2 2)",
"POINT(0 0)",
"POLYGON((0 0, 1 1, 2 2))",
"POLYGON((0 0, 1 1, 2 2, 0 0))",
"MULTIPOLYGON(((0 0, 1 1, 2 2, 1 2, 0 0)))",
}) {
AnyGeo any = read_any_wkt(wkt);
std::cout << "read_any_wkt: " << geom::wkt(any) << "\n";
std::string reason;
if (!geom::is_valid(any, reason)) {
std::cout << reason << "\n";
geom::correct(any);
std::cout << " -- attempted correction: " << geom::wkt(any) << "\n";
}
}
}
печать
DEBUG: statically registered support for LINESTRING type
DEBUG: statically registered support for MULTILINESTRING type
DEBUG: statically registered support for MULTIPOLYGON type
DEBUG: statically registered support for POINT type
DEBUG: statically registered support for POLYGON type
read_any_wkt: LINESTRING(0 0,1 1,2 2)
read_any_wkt: POINT(0 0)
read_any_wkt: POLYGON((0 0,1 1,2 2,0 0))
Geometry has too few points
-- attempted correction: POLYGON((0 0,1 1,2 2,0 0))
read_any_wkt: POLYGON((0 0,1 1,2 2,0 0))
Geometry has spikes. A spike point was found with apex at (2, 2)
-- attempted correction: POLYGON((0 0,1 1,2 2,0 0))
read_any_wkt: MULTIPOLYGON(((0 0,1 1,2 2,1 2,0 0)))
Geometry has wrong orientation
-- attempted correction: MULTIPOLYGON(((0 0,1 2,2 2,1 1,0 0)))