Reputation: 8333
Is it possible to define a boost::geometry
object using a boost::variant
?
This code doesn't compile as it doesn't like the variant object used inside 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;
}
However, if I explicitly define the linestring_type
it works fine
int main() {
std::string wkt = "LINESTRING(0 0, 1 1, 2, 2)";
linestring_type ls;
geom::read_wkt(wkt, ls);
return 0;
}
Upvotes: 0
Views: 645
Reputation: 392893
You're swimming against the design of the library. If you want a runtime-polymorphic geometry type, use one.
Of course you can build one on top of Boost Geometry using variants. So, let's assume you want to do this.
As always with variants, to operate on them generically, you require a visitor to visit the potential element types:
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;
Now you can
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);
}
Printing:
LINESTRING(0 0,1 1,2 0,2 0)
This might not do what you expect. If you had just a default-constructed variant, it would not have worked, because the visitor will visit the current state (which is point_type
) and it would fail.
To actually get a dynamic read method that detects the geometry type from the input, you could:
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);
}
Which works: 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";
}
}
Prints
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))
That's a quite a bit more work, sadly:
#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;
Printing the same output.
Instead of blindly trying to parse WKT until an exception isn't thrown, a better way to deserialize from WKT would be to actually parse the type id first and switch on that.
For that I've drawn up a sample using Boost Spirit X3 to switch the output to the type associated with the leading type keyword.
Parsing becomes simpler:
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);
}
}
The gen_switch
call generates a Trie containing the supportable geometry types only once.
Note this adds some types, validity diagnostics and auto-correction
#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";
}
}
}
Printing
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)))
Upvotes: 5