如何使用变体创建几何体

时间:2017-12-31 06:47:37

标签: c++ c++11 boost boost-geometry

是否可以使用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 个答案:

答案 0 :(得分:4)

你正在游泳图书馆的设计。如果需要运行时多态几何类型,请使用一个。

当然,您可以使用变体构建一个在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;

现在你可以

Live On Coliru

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)和it would fail

要实际获取从输入中检测几何类型的动态读取方法,您可以:

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))

Bonus:Make That Generic

这是一项相当多的工作,遗憾的是:

<强> Live On Coliru (c++14 used)

#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;

打印相同的输出。

BONUS BONUS

不是盲目地尝试解析WKT直到没有抛出异常,而是从WKT反序列化的更好方法是首先实际解析类型id并启用它。

为此,我使用Boost Spirit X3绘制了一个示例,将输出切换为与leading类型关键字相关联的类型。

解析变得更简单:

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调用仅生成包含可支持几何类型的Trie一次。

<强> Live On Coliru

请注意,这会添加一些类型,有效性诊断和自动更正

#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)))