2

I'm currently trying to extend boost::geometry polygons with some additional information. However the compiler starts

#include <boost/geometry.hpp>
namespace bg = boost::geometry;

using point_t = bg::model::d2::point_xy<double>;

using polygon_t = bg::model::polygon<point_t>;
using mpolygon_t = bg::model::multi_polygon<polygon_t>;

using taggedPolygon_t = std::tuple<polygon_t, void*>;
using multiTaggedPolygon_t = bg::model::multi_polygon<taggedPolygon_t>;

void foo()
{
    mpolygon_t poly;               // OK
    taggedPolygon_t taggedPoly;    // OK

    mpolygon_t mpoly;              // OK
    multiTaggedPolygon_t poly;     // Compile error
}

Does anybody have a hint how to get that stuff right? My intent is to store some additional information and attach it to the polygon for later usage.

I also tried to use inheritance instead of std::tuple:

struct taggedPolygon_t : bg::model::polygon<point_t>
{
    void* tag;
};

namespace boost { namespace geometry { namespace traits
{
template<> struct tag<taggedPolygon_t> { typedef polygon_tag type; };
template<> struct ring_const_type<taggedPolygon_t> { typedef const taggedPolygon_t& type; };
template<> struct ring_mutable_type<taggedPolygon_t> { typedef taggedPolygon_t& type; };
template<> struct interior_const_type<taggedPolygon_t> { typedef const taggedPolygon_t type; };
template<> struct interior_mutable_type<taggedPolygon_t> { typedef taggedPolygon_t type; };

template<> struct exterior_ring<taggedPolygon_t> { typedef const taggedPolygon_t type; };
template<> struct interior_rings<taggedPolygon_t> { typedef const taggedPolygon_t type; };
} } } // namespace boost::geometry::traits

But the problem remains.

fhw72
  • 1,066
  • 1
  • 11
  • 19
  • The compilation error is quite (~~big~~) handy when you try to fix this problem. – xtofl Aug 08 '19 at 11:58
  • what is 'the problem'? Can you include e.g. the first few relevant lines of the compiler output? – xtofl Aug 08 '19 at 12:42
  • I out how to do it: I have to provide a traits specialization for my class. Did that... and it works. But many thanks for taking your time! – fhw72 Aug 08 '19 at 13:00

2 Answers2

1

I found out how to do it via inheritance (2nd code snippet):

struct taggedPolygon_t : bg::model::polygon<point_t>
{
    void* tag;
};

namespace boost { namespace geometry { namespace traits
{
    template<> struct tag<taggedPolygon_t> { typedef polygon_tag type; };
    template<> struct ring_const_type<taggedPolygon_t> { typedef const bg::model::polygon<point_t>::ring_type& type; };
    template<> struct ring_mutable_type<taggedPolygon_t> { typedef bg::model::polygon<point_t>::ring_type& type; };
    template<> struct interior_const_type<taggedPolygon_t> { typedef const bg::model::polygon<point_t>::inner_container_type& type; };
    template<> struct interior_mutable_type<taggedPolygon_t> { typedef bg::model::polygon<point_t>::inner_container_type& type; };

    template<> struct exterior_ring<taggedPolygon_t>
    {
        static bg::model::polygon<point_t>::ring_type& get(bg::model::polygon<point_t>& p) {return p.outer(); }
        static bg::model::polygon<point_t>::ring_type const& get(bg::model::polygon<point_t> const& p) {return p.outer(); }
    };

    template<> struct interior_rings<taggedPolygon_t>
    {
        static bg::model::polygon<point_t>::inner_container_type& get(bg::model::polygon<point_t>& p) {return p.inners(); }
        static bg::model::polygon<point_t>::inner_container_type const& get(bg::model::polygon<point_t> const& p) {return p.inners(); }
    };
} } } // namespace boost::geometry::traits
fhw72
  • 1,066
  • 1
  • 11
  • 19
1
taggedPolygon_t taggedPoly;    // OK

Obviously ok. It just declares a tuple object. Tuples pose no restrictions on the template arguments.

 multiTaggedPolygon_t poly;     // Compile error

That's not OK, because it defines a multi_polugon<> instance. That type does pose concept requirements on the template argument type: it must model the Polygon concept.

A tuple does not satisfy those requirements.

Definition

The Polygon Concept is defined as following:

  • there must be a specialization of traits::tag defining polygon_tag as type
  • there must be a specialization of traits::ring_type defining the type of its exterior ring and interior rings as type
  • this type defined by ring_type must fulfill the Ring Concept
  • there must be a specialization of traits::interior_type defining the type of the collection of its interior rings as type; this collection itself must fulfill a Boost.Range Random Access Range Concept
  • there must be a specialization of traits::exterior_ring with two functions named get, returning the exterior ring, one being const, the other being non const
  • there must be a specialization of traits::interior_rings with two functions named get, returning the interior rings, one being const, the other being non const

So let's be quick and dirty here:

Note, the docs seem to be slightly out of sync w.r.t. the mutable/const distinction.

namespace boost::geometry::traits {
    template <typename Underlying, typename Tag>
        struct ring_mutable_type<taggedGeometry<Underlying, Tag> > : ring_mutable_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct ring_const_type<taggedGeometry<Underlying, Tag> > : ring_const_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct interior_mutable_type<taggedGeometry<Underlying, Tag> > : interior_mutable_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct interior_const_type<taggedGeometry<Underlying, Tag> > : interior_const_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct tag<taggedGeometry<Underlying, Tag> > : tag<Underlying> {};
    template <typename Underlying, typename Tag>
        struct exterior_ring<taggedGeometry<Underlying, Tag> > : exterior_ring<Underlying> {};
    template <typename Underlying, typename Tag>
        struct interior_rings<taggedGeometry<Underlying, Tag> > : interior_rings<Underlying> {};
}

Now you can compile your declaration.

Live On Coliru

mpolygon_t mpoly;              // OK
multiTaggedPolygon_t poly;     // OK

static_assert(std::is_same_v<bg::ring_type<mpolygon_t>::type, bg::ring_type<multiTaggedPolygon_t>::type>, "");

Note I said "quick and dirty". Because this isn't enough.

More...

Note I silently changed from std::tuple<> to a custom struct for convenience. If not, you'd have to delegate the using the tuple getter:

template <typename Underlying, typename Tag>
    struct exterior_ring<taggedGeometry<Underlying, Tag> > : exterior_ring<Underlying> {
        using G = taggedGeometry<Underlying, Tag>;
        using base = exterior_ring<Underlying>;
        static decltype(auto) get(G& v)       { return base::get(std::get<0>(v)); }
        static decltype(auto) get(G const& v) { return base::get(std::get<0>(v)); }
    };
template <typename Underlying, typename Tag>
    struct interior_rings<taggedGeometry<Underlying, Tag> > : interior_rings<Underlying> {
        using G = taggedGeometry<Underlying, Tag>;
        using base = interior_rings<Underlying>;
        static decltype(auto) get(G& v)       { return base::get(std::get<0>(v)); }
        static decltype(auto) get(G const& v) { return base::get(std::get<0>(v)); }
    };

That'd also work: Live On Coliru

Demo

Now you can actually use it:

Live On Coliru

int main() {
    multiTaggedPolygon_t poly;
    bg::read_wkt("MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), "
        "((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),"
        "(30 20, 20 15, 20 25, 30 20)))", poly);

    std::string reason;
    if (!bg::is_valid(poly, reason)) {
        std::cout << "Correcting data: " << reason << "\n";
        bg::correct(poly);
    }

    std::cout << bg::wkt(poly) << " has an area of " << bg::area(poly) << "\n";
}

Prints:

Correcting data: Geometry has wrong orientation
MULTIPOLYGON(((40 40,45 30,20 45,40 40)),((20 35,45 20,30 5,10 10,10 30,20 35),(30 20,20 25,20 15,30 20))) has an area of 712.5

CAVEATS

Note that tagged polygons are not "supported" in mutating/producing algorithms.

For example, if you intersect two polygons, the result will be polygons freshly constructed and built with the generic methods the library defines, which means you "lose" the tag info.

Listing

For posterity Live On Coliru

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/algorithms/area.hpp>
#include <iostream>
namespace bg = boost::geometry;

using point_t = bg::model::d2::point_xy<double>;

using polygon_t = bg::model::polygon<point_t>;
using mpolygon_t = bg::model::multi_polygon<polygon_t>;

template <typename Geo, typename Tag = void*>
    using taggedGeometry = std::tuple<Geo, Tag>;

/*
   template <typename Geo, typename Tag = void*>
   struct taggedGeometry : Geo {
       using Geo::Geo;
       Tag _tag_data;
   };
*/

namespace boost::geometry::traits {
    template <typename Underlying, typename Tag>
        struct ring_mutable_type<taggedGeometry<Underlying, Tag> > : ring_mutable_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct ring_const_type<taggedGeometry<Underlying, Tag> > : ring_const_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct interior_mutable_type<taggedGeometry<Underlying, Tag> > : interior_mutable_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct interior_const_type<taggedGeometry<Underlying, Tag> > : interior_const_type<Underlying> {};
    template <typename Underlying, typename Tag>
        struct tag<taggedGeometry<Underlying, Tag> > : tag<Underlying> {};
    template <typename Underlying, typename Tag>
        struct exterior_ring<taggedGeometry<Underlying, Tag> > : exterior_ring<Underlying> {
            using G = taggedGeometry<Underlying, Tag>;
            using base = exterior_ring<Underlying>;
            static decltype(auto) get(G& v)       { return base::get(std::get<0>(v)); }
            static decltype(auto) get(G const& v) { return base::get(std::get<0>(v)); }
        };
    template <typename Underlying, typename Tag>
        struct interior_rings<taggedGeometry<Underlying, Tag> > : interior_rings<Underlying> {
            using G = taggedGeometry<Underlying, Tag>;
            using base = interior_rings<Underlying>;
            static decltype(auto) get(G& v)       { return base::get(std::get<0>(v)); }
            static decltype(auto) get(G const& v) { return base::get(std::get<0>(v)); }
        };
}

using taggedPolygon_t = taggedGeometry<polygon_t>;
using multiTaggedPolygon_t = bg::model::multi_polygon<taggedPolygon_t>;

int main() {
    multiTaggedPolygon_t poly;
    bg::read_wkt("MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), "
        "((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),"
        "(30 20, 20 15, 20 25, 30 20)))", poly);

    std::string reason;
    if (!bg::is_valid(poly, reason)) {
        std::cout << "Correcting data: " << reason << "\n";
        bg::correct(poly);
    }

    std::cout << bg::wkt(poly) << " has an area of " << bg::area(poly) << "\n";
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Wow... yours is most probably the best answer one could give! Thanks! I'm still wondering what to favor w.r.t. performance: inheritance or tuple? – fhw72 Aug 09 '19 at 06:58
  • I'd say there is no difference in optimized builds, unless you make the mistake of using runtime polymorphism somewhere (say, add a virtual method). A tool like https://godbolt.org/ or http://quick-bench.com/ can help – sehe Aug 09 '19 at 11:15