2

I would like to apply boost::geometry algorithms for the following immutable 2D model consisting of a point, polygon (open or closed), and polygonal domain class (with arbitrary number of holes) class, respectively, as shown below:

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/register/linestring.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/range.hpp>
#include <memory>
#include <vector>

namespace NGeometry {
   class Point2D {
   public:
      Point2D( double x, double y ) : m_x( x ), m_y( y ) {}
      double getX() const { return m_x; }
      double getY() const { return m_y; }
   private:
      double m_x, m_y;
   };
   using TyPoints2D = std::vector< Point2D >;

   // Either open (first point != last point) or closed (first point == last point) polygon
   class Polygon2D {
   public:
      Polygon2D( TyPoints2D points ) : m_points( std::move( points ) ) {}
      const TyPoints2D& getPoints() const { return m_points; }
   private:
      TyPoints2D m_points;
   };
   using TyPolygon2D = std::shared_ptr< Polygon2D >;
   using TyPolygons2D = std::vector< TyPolygon2D >;

   // Polygonal domain with outer cw oriented closed polygon and >= 0 ccw oriented inner polygons
   class PolygonalDomain2D {
   public:
      PolygonalDomain2D( TyPolygon2D outer, TyPolygons2D inners )
         : m_outer( std::move( outer ) ), m_inners( std::move( inners ) ) {}
      const TyPolygon2D& getOuter() const { return m_outer; }
      const TyPolygons2D& getInners() const { return m_inners; }
   private:
      TyPolygon2D m_outer;
      TyPolygons2D m_inners;
   };
} // namespace NGeometry


// Provide read only Boost.Range for Polygon2D
namespace boost {
   template<>
   struct range_const_iterator< NGeometry::Polygon2D >
   {
      typedef std::vector< NGeometry::Point2D >::const_iterator type;
   };
   template<>
   struct range_value< NGeometry::Polygon2D >
   {
      typedef NGeometry::Point2D type;
   };
} // namespace boost
inline std::vector< NGeometry::Point2D >::const_iterator range_begin( const NGeometry::Polygon2D polygon ) {
   return polygon.getPoints().cbegin();
}
inline std::vector< NGeometry::Point2D >::const_iterator range_end( const NGeometry::Polygon2D polygon ) {
   return polygon.getPoints().cend();
}

BOOST_GEOMETRY_REGISTER_POINT_2D_CONST( NGeometry::Point2D, double, cs::cartesian, getX(), getY() );
BOOST_GEOMETRY_REGISTER_LINESTRING( NGeometry::Polygon2D );
// How to register PolygonalDomain2D?

int main( int argc, char* argv[] )
{
   NGeometry::Point2D point( 0.0, 0.0 );
   auto outerPolygonSP = std::make_shared< NGeometry::Polygon2D >(
      NGeometry::TyPoints2D { { -2.0, -2.0 }, { -2.0, 2.0 }, { 2.0, 2.0 }, { 2.0, -2.0 }, { -2.0, -2.0 } } );
   auto innerPolygonSP = std::make_shared< NGeometry::Polygon2D >(
      NGeometry::TyPoints2D { { -1.0, -1.0 }, { 1.0, -1.0 }, { 1.0, 1.0 }, { -1.0, 1.0 }, { -1.0, -1.0 } } );
   NGeometry::PolygonalDomain2D domain( outerPolygonSP, { innerPolygonSP } );

   double length = boost::geometry::length( *outerPolygonSP );
   double area = boost::geometry::area( domain );
   bool isInside = boost::geometry::within( point, domain );

   return 0;
}

Compared to the first steps described in Problems in adapting a geometry object model using boost geometry classes have been revised and moved to the NGeometry namespace, which results in compiler errors about missing begin and end for NGeometry::Polygon2D. Furthermore, I do not know how to adapt the domain. I would be very grateful for any help.

g++ compiler output:

In file included from /usr/include/boost/geometry/core/closure.hpp:22,
                 from /usr/include/boost/geometry/geometry.hpp:25,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/geometry/core/point_type.hpp: In instantiation of ‘struct boost::geometry::traits::point_type<NGeometry::PolygonalDomain2D>’:
/usr/include/boost/geometry/core/point_type.hpp:66:17:   required from ‘struct boost::geometry::core_dispatch::point_type<void, NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/core/coordinate_system.hpp:58:59:   required from ‘struct boost::geometry::core_dispatch::coordinate_system<void, NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/core/coordinate_system.hpp:93:17:   required from ‘struct boost::geometry::coordinate_system<NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/core/cs.hpp:244:17:   required from ‘struct boost::geometry::cs_tag<NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/strategies/area_result.hpp:59:8:   required from ‘struct boost::geometry::area_result<NGeometry::PolygonalDomain2D, boost::geometry::default_strategy>’
/usr/include/boost/geometry/algorithms/area.hpp:320:1:   required by substitution of ‘template<class Geometry> typename boost::geometry::area_result<Geometry, boost::geometry::default_strategy>::type boost::geometry::area(const Geometry&) [with Geometry = NGeometry::PolygonalDomain2D]’
main.cpp:78:48:   required from here
/usr/include/boost/geometry/core/point_type.hpp:45:5: error: could not convert ‘boost::geometry::traits::point_type<NGeometry::PolygonalDomain2D>::NOT_IMPLEMENTED_FOR_THIS_POINT_TYPE45::assert_arg()’ from ‘mpl_::failed************ (boost::geometry::traits::point_type<NGeometry::PolygonalDomain2D>::NOT_IMPLEMENTED_FOR_THIS_POINT_TYPE::************)(mpl_::assert_::types<NGeometry::PolygonalDomain2D, mpl_::na, mpl_::na, mpl_::na>)’ to ‘mpl_::assert<false>::type’ {aka ‘mpl_::assert<false>’}
   45 |     BOOST_MPL_ASSERT_MSG
      |     ^
      |     |
      |     mpl_::failed************ (boost::geometry::traits::point_type<NGeometry::PolygonalDomain2D>::NOT_IMPLEMENTED_FOR_THIS_POINT_TYPE::************)(mpl_::assert_::types<NGeometry::PolygonalDomain2D, mpl_::na, mpl_::na, mpl_::na>)
In file included from /usr/include/boost/geometry/core/coordinate_dimension.hpp:23,
                 from /usr/include/boost/geometry/geometry.hpp:26,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/geometry/core/point_type.hpp: In instantiation of ‘struct boost::geometry::core_dispatch::point_type<void, NGeometry::PolygonalDomain2D>’:
/usr/include/boost/geometry/core/coordinate_system.hpp:58:59:   required from ‘struct boost::geometry::core_dispatch::coordinate_system<void, NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/core/coordinate_system.hpp:93:17:   required from ‘struct boost::geometry::coordinate_system<NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/core/cs.hpp:244:17:   required from ‘struct boost::geometry::cs_tag<NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/strategies/area_result.hpp:59:8:   required from ‘struct boost::geometry::area_result<NGeometry::PolygonalDomain2D, boost::geometry::default_strategy>’
/usr/include/boost/geometry/algorithms/area.hpp:320:1:   required by substitution of ‘template<class Geometry> typename boost::geometry::area_result<Geometry, boost::geometry::default_strategy>::type boost::geometry::area(const Geometry&) [with Geometry = NGeometry::PolygonalDomain2D]’
main.cpp:78:48:   required from here
/usr/include/boost/geometry/core/point_type.hpp:66:17: error: no type named ‘type’ in ‘struct boost::geometry::traits::point_type<NGeometry::PolygonalDomain2D>’
   66 |         >::type type;
      |                 ^~~~
main.cpp: In function ‘int main(int, char**)’:
main.cpp:78:48: error: no matching function for call to ‘area(NGeometry::PolygonalDomain2D&)’
   78 |    double area = boost::geometry::area( domain );
      |                                                ^
In file included from /usr/include/boost/geometry/geometry.hpp:52,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/geometry/algorithms/area.hpp:320:1: note: candidate: ‘template<class Geometry> typename boost::geometry::area_result<Geometry, boost::geometry::default_strategy>::type boost::geometry::area(const Geometry&)’
  320 | area(Geometry const& geometry)
      | ^~~~
/usr/include/boost/geometry/algorithms/area.hpp:320:1: note:   substitution of deduced template arguments resulted in errors seen above
/usr/include/boost/geometry/algorithms/area.hpp:356:1: note: candidate: ‘template<class Geometry, class Strategy> typename boost::geometry::area_result<Geometry, Strategy>::type boost::geometry::area(const Geometry&, const Strategy&)’
  356 | area(Geometry const& geometry, Strategy const& strategy)
      | ^~~~
/usr/include/boost/geometry/algorithms/area.hpp:356:1: note:   template argument deduction/substitution failed:
main.cpp:78:48: note:   candidate expects 2 arguments, 1 provided
   78 |    double area = boost::geometry::area( domain );
      |                                                ^
In file included from /usr/include/boost/geometry/core/closure.hpp:22,
                 from /usr/include/boost/geometry/geometry.hpp:25,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/geometry/algorithms/not_implemented.hpp: In instantiation of ‘struct boost::geometry::nyi::not_implemented_error<void, void, void>’:
/usr/include/boost/geometry/algorithms/not_implemented.hpp:108:8:   required from ‘struct boost::geometry::not_implemented<void, void, void>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:69:8:   required from ‘struct boost::geometry::dispatch::check<const NGeometry::PolygonalDomain2D, void, true>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:196:8:   required from ‘struct boost::geometry::concepts::detail::checker<const NGeometry::PolygonalDomain2D>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:219:31:   required from ‘void boost::geometry::concepts::check() [with Geometry = const NGeometry::PolygonalDomain2D]’
/usr/include/boost/geometry/algorithms/detail/within/interface.hpp:108:41:   required from ‘static bool boost::geometry::resolve_variant::within<Geometry1, Geometry2>::apply(const Geometry1&, const Geometry2&, const Strategy&) [with Strategy = boost::geometry::default_strategy; Geometry1 = NGeometry::Point2D; Geometry2 = NGeometry::PolygonalDomain2D]’
/usr/include/boost/geometry/algorithms/detail/within/interface.hpp:255:17:   required from ‘bool boost::geometry::within(const Geometry1&, const Geometry2&) [with Geometry1 = NGeometry::Point2D; Geometry2 = NGeometry::PolygonalDomain2D]’
main.cpp:79:59:   required from here
/usr/include/boost/geometry/algorithms/not_implemented.hpp:69:5: error: could not convert ‘boost::geometry::nyi::not_implemented_error<void, void, void>::THIS_OPERATION_IS_NOT_OR_NOT_YET_IMPLEMENTED69::assert_arg()’ from ‘mpl_::failed************ (boost::geometry::nyi::not_implemented_error<void, void, void>::THIS_OPERATION_IS_NOT_OR_NOT_YET_IMPLEMENTED::************)(mpl_::assert_::types<void, void, void, mpl_::na>)’ to ‘mpl_::assert<false>::type’ {aka ‘mpl_::assert<false>’}
   69 |     BOOST_MPL_ASSERT_MSG
      |     ^
      |     |
      |     mpl_::failed************ (boost::geometry::nyi::not_implemented_error<void, void, void>::THIS_OPERATION_IS_NOT_OR_NOT_YET_IMPLEMENTED::************)(mpl_::assert_::types<void, void, void, mpl_::na>)
In file included from /usr/include/boost/geometry/core/coordinate_dimension.hpp:21,
                 from /usr/include/boost/geometry/geometry.hpp:26,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/geometry/core/coordinate_dimension.hpp: In instantiation of ‘void boost::geometry::assert_dimension_equal() [with G1 = NGeometry::Point2D; G2 = NGeometry::PolygonalDomain2D]’:
/usr/include/boost/geometry/algorithms/detail/within/interface.hpp:109:53:   required from ‘static bool boost::geometry::resolve_variant::within<Geometry1, Geometry2>::apply(const Geometry1&, const Geometry2&, const Strategy&) [with Strategy = boost::geometry::default_strategy; Geometry1 = NGeometry::Point2D; Geometry2 = NGeometry::PolygonalDomain2D]’
/usr/include/boost/geometry/algorithms/detail/within/interface.hpp:255:17:   required from ‘bool boost::geometry::within(const Geometry1&, const Geometry2&) [with Geometry1 = NGeometry::Point2D; Geometry2 = NGeometry::PolygonalDomain2D]’
main.cpp:79:59:   required from here
/usr/include/boost/geometry/core/coordinate_dimension.hpp:122:5: error: no type named ‘type’ in ‘struct boost::geometry::dimension<NGeometry::PolygonalDomain2D>’
  122 |     BOOST_STATIC_ASSERT(( static_cast<size_t>(dimension<G1>::type::value) == static_cast<size_t>(dimension<G2>::type::value) ));
      |     ^~~~~~~~~~~~~~~~~~~
In file included from /usr/include/boost/range/functions.hpp:18,
                 from /usr/include/boost/range/iterator_range_core.hpp:38,
                 from /usr/include/boost/lexical_cast.hpp:30,
                 from /usr/include/boost/math/tools/convert_from_string.hpp:15,
                 from /usr/include/boost/math/constants/constants.hpp:13,
                 from /usr/include/boost/geometry/util/math.hpp:29,
                 from /usr/include/boost/geometry/core/radian_access.hpp:33,
                 from /usr/include/boost/geometry/geometry.hpp:42,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/range/begin.hpp: In instantiation of ‘constexpr typename boost::range_iterator<T>::type boost::range_detail::range_begin(C&) [with C = const NGeometry::Polygon2D; typename boost::range_iterator<T>::type = __gnu_cxx::__normal_iterator<const NGeometry::Point2D*, std::vector<NGeometry::Point2D> >]’:
/usr/include/boost/range/begin.hpp:119:23:   required from ‘void boost::SinglePassRangeConcept<T>::const_constraints(const Rng&) [with T = const NGeometry::Polygon2D; boost::SinglePassRangeConcept<T>::Rng = const NGeometry::Polygon2D]’
/usr/include/boost/range/concepts.hpp:295:13:   required from ‘boost::SinglePassRangeConcept<T>::~SinglePassRangeConcept() [with T = const NGeometry::Polygon2D]’
/usr/include/boost/range/concepts.hpp:318:12:   required from ‘static void boost::concepts::requirement<boost::concepts::failed************ Model::************>::failed() [with Model = boost::ForwardRangeConcept<const NGeometry::Polygon2D>]’
/usr/include/boost/geometry/geometries/concepts/linestring_concept.hpp:111:5:   required from ‘class boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D>’
/usr/include/boost/concept/detail/has_constraints.hpp:32:62:   required by substitution of ‘template<class Model> boost::concepts::detail::yes boost::concepts::detail::has_constraints_(Model*, boost::concepts::detail::wrap_constraints<Model, (& Model::constraints)>*) [with Model = boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D>]’
/usr/include/boost/concept/detail/has_constraints.hpp:42:5:   [ skipping 3 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/usr/include/boost/concept_check.hpp:50:7:   required from ‘class boost::geometry::detail::concept_check::check<boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D> >’
/usr/include/boost/geometry/geometries/concepts/check.hpp:86:8:   required from ‘struct boost::geometry::dispatch::check<const NGeometry::Polygon2D, boost::geometry::linestring_tag, true>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:196:8:   required from ‘struct boost::geometry::concepts::detail::checker<const NGeometry::Polygon2D>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:219:31:   required from ‘void boost::geometry::concepts::check() [with Geometry = const NGeometry::Polygon2D]’
/usr/include/boost/geometry/algorithms/length.hpp:282:36:   required from ‘typename boost::geometry::default_length_result<Geometry>::type boost::geometry::length(const Geometry&) [with Geometry = NGeometry::Polygon2D; typename boost::geometry::default_length_result<Geometry>::type = long double]’
main.cpp:77:61:   required from here
/usr/include/boost/range/concepts.hpp:301:46:   in ‘constexpr’ expansion of ‘boost::range_adl_barrier::begin<NGeometry::Polygon2D>((* & const_range))’
/usr/include/boost/range/begin.hpp:49:18: error: ‘const class NGeometry::Polygon2D’ has no member named ‘begin’
   49 |         return c.begin();
      |                ~~^~~~~
In file included from /usr/include/boost/range/functions.hpp:19,
                 from /usr/include/boost/range/iterator_range_core.hpp:38,
                 from /usr/include/boost/lexical_cast.hpp:30,
                 from /usr/include/boost/math/tools/convert_from_string.hpp:15,
                 from /usr/include/boost/math/constants/constants.hpp:13,
                 from /usr/include/boost/geometry/util/math.hpp:29,
                 from /usr/include/boost/geometry/core/radian_access.hpp:33,
                 from /usr/include/boost/geometry/geometry.hpp:42,
                 from /usr/include/boost/geometry.hpp:17,
                 from main.cpp:1:
/usr/include/boost/range/end.hpp: In instantiation of ‘constexpr typename boost::range_iterator<T>::type boost::range_detail::range_end(C&) [with C = const NGeometry::Polygon2D; typename boost::range_iterator<T>::type = __gnu_cxx::__normal_iterator<const NGeometry::Point2D*, std::vector<NGeometry::Point2D> >]’:
/usr/include/boost/range/end.hpp:113:21:   required from ‘void boost::SinglePassRangeConcept<T>::const_constraints(const Rng&) [with T = const NGeometry::Polygon2D; boost::SinglePassRangeConcept<T>::Rng = const NGeometry::Polygon2D]’
/usr/include/boost/range/concepts.hpp:295:13:   required from ‘boost::SinglePassRangeConcept<T>::~SinglePassRangeConcept() [with T = const NGeometry::Polygon2D]’
/usr/include/boost/range/concepts.hpp:318:12:   required from ‘static void boost::concepts::requirement<boost::concepts::failed************ Model::************>::failed() [with Model = boost::ForwardRangeConcept<const NGeometry::Polygon2D>]’
/usr/include/boost/geometry/geometries/concepts/linestring_concept.hpp:111:5:   required from ‘class boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D>’
/usr/include/boost/concept/detail/has_constraints.hpp:32:62:   required by substitution of ‘template<class Model> boost::concepts::detail::yes boost::concepts::detail::has_constraints_(Model*, boost::concepts::detail::wrap_constraints<Model, (& Model::constraints)>*) [with Model = boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D>]’
/usr/include/boost/concept/detail/has_constraints.hpp:42:5:   [ skipping 3 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/usr/include/boost/concept_check.hpp:50:7:   required from ‘class boost::geometry::detail::concept_check::check<boost::geometry::concepts::ConstLinestring<const NGeometry::Polygon2D> >’
/usr/include/boost/geometry/geometries/concepts/check.hpp:86:8:   required from ‘struct boost::geometry::dispatch::check<const NGeometry::Polygon2D, boost::geometry::linestring_tag, true>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:196:8:   required from ‘struct boost::geometry::concepts::detail::checker<const NGeometry::Polygon2D>’
/usr/include/boost/geometry/geometries/concepts/check.hpp:219:31:   required from ‘void boost::geometry::concepts::check() [with Geometry = const NGeometry::Polygon2D]’
/usr/include/boost/geometry/algorithms/length.hpp:282:36:   required from ‘typename boost::geometry::default_length_result<Geometry>::type boost::geometry::length(const Geometry&) [with Geometry = NGeometry::Polygon2D; typename boost::geometry::default_length_result<Geometry>::type = long double]’
main.cpp:77:61:   required from here
/usr/include/boost/range/concepts.hpp:302:44:   in ‘constexpr’ expansion of ‘boost::range_adl_barrier::end<NGeometry::Polygon2D>((* & const_range))’
/usr/include/boost/range/end.hpp:50:22: error: ‘const class NGeometry::Polygon2D’ has no member named ‘end’
   50 |             return c.end();
      |                    ~~^~~

1 Answers1

1

First off, let's fix the Undefined Behaviour because you took the arguments by value in range_begin and range_end. This meant that, by definition, you were returning iterators into temporaries that were gone before you'd be using them.

Also, enable ADL as per Boost Range design, by putting these overloads in the declaring namespace of their range type:

namespace NGeometry {
    inline std::vector<NGeometry::Point2D>::const_iterator
    range_begin(NGeometry::Polygon2D const& polygon) {
        return polygon.getPoints().cbegin();
    }
    inline std::vector<NGeometry::Point2D>::const_iterator
    range_end(NGeometry::Polygon2D const& polygon) {
        return polygon.getPoints().cend();
    }
}

Okay, whew. Now it won't crash as soon as we run anything (or worse, don't crash and result in large legal fees).

Ring Concept

Next up, you registered your polygon type as a... LINESTRING. That doesn't work, because it is not what is required for a polygon concept: Ring and Polygon.

Fixing that

BOOST_GEOMETRY_REGISTER_RING(NGeometry::Polygon2D)

Allows you to get a little further:

namespace bg = boost::geometry;
auto outer =
    std::make_shared<NGeometry::Polygon2D>(NGeometry::TyPoints2D{
        {-2.0, -2.0}, {-2.0, 2.0}, {2.0, 2.0}, {2.0, -2.0}, {-2.0, -2.0}});

std::cout << bg::wkt(*outer) << "\n";
std::cout << bg::dsv(*outer) << "\n";
std::cout << "Length: " << bg::length(*outer) << "\n";
std::cout << "Area: " << bg::area(*outer) << "\n";

Prints

POLYGON((-2 -2,-2 2,2 2,2 -2,-2 -2))
((-2, -2), (-2, 2), (2, 2), (2, -2), (-2, -2))
Length: 0
Area: 16

Expanding to PolygonalDomain2D

Your "domain" is what OGC knows as a Polygon. It has an outer ring and optionally a number of inner rings. You're in luck that the OGC standard also requires the point order to be reversed for the interior rings.

However, you have made a slight complication by aggregating the rings not directly but by shared_ptr. I think you might go through the trouble of also adapting the shared_ptr as a proper ring. I'll show you what I've done instead below.

How To Register A Polygon

There's no "REGISTER_XXX" facility for it. You have to go by the documented Concept Requirements.

The documentation is not 100% in sync with reality, I've figured this out a while ago: (How to) Create own polygon type in boost geometry and use multi_polygon type with it?

If you go and write out the relevant traits:

template <> struct tag<NGeometry::PolygonalDomain2D> {
    using type = polygon_tag;
};
template <> struct ring_mutable_type<NGeometry::PolygonalDomain2D> {
    using type = NGeometry::Polygon2D;
};
template <> struct ring_const_type<NGeometry::PolygonalDomain2D> {
    using type = NGeometry::Polygon2D const;
};
template <> struct exterior_ring<NGeometry::PolygonalDomain2D> {
    static decltype(auto) get(NGeometry::PolygonalDomain2D &v) {
        return *v.getOuter();
    }
    static decltype(auto) get(NGeometry::PolygonalDomain2D const &v) {
        return *v.getOuter();
    }
};

Now it's getting spicy, because we have reached your interior rings which aren't stored as a simple container of Ring models, but rather a container of shared pointers to them.

I'm going to stand on the shoulders of giants and use boost::adaptors::indirected to synthesize a range that hides that layer of indirection.

template <> struct interior_rings<NGeometry::PolygonalDomain2D> {
    static decltype(auto) get(NGeometry::PolygonalDomain2D &v) {
        return v.getInners() | boost::adaptors::indirected;
    }
    static decltype(auto) get(NGeometry::PolygonalDomain2D const &v) {
        return v.getInners() | boost::adaptors::indirected;
    }
};

Next, I've reordered the type traits to come after that implementation so I can use type deduction instead of spelling out the implementing type names:

template <> struct interior_mutable_type<NGeometry::PolygonalDomain2D> {
    using type = decltype(interior_rings<NGeometry::PolygonalDomain2D>::get(
        std::declval<NGeometry::PolygonalDomain2D>()));
};
template <> struct interior_const_type<NGeometry::PolygonalDomain2D> {
    using type = decltype(interior_rings<NGeometry::PolygonalDomain2D>::get(
        std::declval<NGeometry::PolygonalDomain2D>())) const;
};

With this trick in place, we can test:

int main() {
    auto report = [](auto heading, auto &g) {
        std::cout << " == " << heading << " ========================\n";
        check_poly(g);
        std::cout << "WKT:       " << bg::wkt(g)       << "\n";
        std::cout << "DSV:       " << bg::dsv(g)       << "\n";
        std::cout << "Area:      " << bg::area(g)      << "\n";
        std::cout << "Perimeter: " << bg::perimeter(g) << "\n";

        if constexpr (std::is_same_v<bg::polygon_tag,
                                     typename bg::traits::tag<
                                         std::decay_t<decltype(g)>>::type>)
        {
            std::cout << "Outer Perimeter: "
                      << bg::perimeter(bg::exterior_ring(g)) << "\n";
        }
    };

    auto outer =
        std::make_shared<NGeometry::Polygon2D>(NGeometry::TyPoints2D{
            {-3.0, -3.0}, {-3.0, 3.0}, {3.0, 3.0}, {3.0, -3.0}, {-3.0, -3.0}});
    auto inner =
        std::make_shared<NGeometry::Polygon2D>(NGeometry::TyPoints2D{
            {-2.0, -2.0}, {2.0, -2.0}, {2.0, 2.0}, {-2.0, 2.0}, {-2.0, -2.0}});

    NGeometry::PolygonalDomain2D domain(outer, {inner});

    report("Outer", *outer);
    report("Inner", *inner);
    report("Domain", domain);

    std::cout << " == Within Check ========================\n";
    std::cout << "Point (0,0) within domain? " << std::boolalpha
              << bg::within(NGeometry::Point2D(0.0, 0.0), domain) << "\n";
}

Which prints

 == Outer ========================
WKT:       POLYGON((-3 -3,-3 3,3 3,3 -3,-3 -3))
DSV:       ((-3, -3), (-3, 3), (3, 3), (3, -3), (-3, -3))
Area:      36
Perimeter: 24
 == Inner ========================
Warning: Geometry has wrong orientation
WKT:       POLYGON((-2 -2,2 -2,2 2,-2 2,-2 -2))
DSV:       ((-2, -2), (2, -2), (2, 2), (-2, 2), (-2, -2))
Area:      -16
Perimeter: 16
 == Domain ========================
WKT:       POLYGON((-3 -3,-3 3,3 3,3 -3,-3 -3),(-2 -2,2 -2,2 2,-2 2,-2 -2))
DSV:       (((-3, -3), (-3, 3), (3, 3), (3, -3), (-3, -3)), ((-2, -2), (2, -2), (2, 2), (-2, 2
), (-2, -2)))
Area:      20
Perimeter: 40
Outer Perimeter: 24
 == Within Check ========================
Point (0,0) within domain? false

Note

The Warning: Geometry has wrong orientation is exactly what you want due the concept requirements that interior rings have opposite point order. So, the check confirms what we wanted to see.

Full Demo Live On Coliru

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/register/ring.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/range.hpp>
#include <boost/range/adaptors.hpp>
#include <memory>
#include <vector>
#include <iostream>

namespace NGeometry {
class Point2D {
  public:
    Point2D(double x, double y) : m_x(x) , m_y(y) { }
    [[nodiscard]] double getX() const { return m_x; }
    [[nodiscard]] double getY() const { return m_y; }

  private:
    double m_x, m_y;
};
using TyPoints2D = std::vector<Point2D>;

// Either open (first point != last point) or closed (first point == last
// point) polygon
class Polygon2D {
  public:
    Polygon2D(TyPoints2D points) : m_points(std::move(points)) {}
    [[nodiscard]] const TyPoints2D &getPoints() const { return m_points; }

  private:
    TyPoints2D m_points;
};

using SharedPolygon2D = std::shared_ptr<Polygon2D>;
using TyPolygons2D = std::vector<SharedPolygon2D>;

// Polygonal domain with outer cw oriented closed
// polygon and >= 0 ccw oriented inner polygons
class PolygonalDomain2D {
  public:
    PolygonalDomain2D(SharedPolygon2D outer, TyPolygons2D inners)
        : m_outer(std::move(outer)), m_inners(std::move(inners)) {}
    [[nodiscard]] const SharedPolygon2D &getOuter() const { return m_outer; }
    [[nodiscard]] const TyPolygons2D &getInners() const { return m_inners; }

  private:
    SharedPolygon2D m_outer;
    TyPolygons2D m_inners;
};
} // namespace NGeometry

// Provide read only Boost.Range for Polygon2D
namespace NGeometry {
    inline std::vector<NGeometry::Point2D>::const_iterator
    range_begin(NGeometry::Polygon2D const& polygon) {
        return polygon.getPoints().cbegin();
    }
    inline std::vector<NGeometry::Point2D>::const_iterator
    range_end(NGeometry::Polygon2D const& polygon) {
        return polygon.getPoints().cend();
    }

    inline std::vector<NGeometry::Point2D>::const_iterator
    range_begin(NGeometry::Polygon2D& polygon) {
        return polygon.getPoints().cbegin();
    }
    inline std::vector<NGeometry::Point2D>::const_iterator
    range_end(NGeometry::Polygon2D& polygon) {
        return polygon.getPoints().cend();
    }
}

namespace boost {
    template <> struct range_iterator<NGeometry::Polygon2D> {
        using type = std::vector<NGeometry::Point2D>::const_iterator;
    };
    template <> struct range_const_iterator<NGeometry::Polygon2D> {
        using type = std::vector<NGeometry::Point2D>::const_iterator;
    };
    template <> struct range_value<NGeometry::Polygon2D> {
        using type = NGeometry::Point2D;
    };
} // namespace boost

BOOST_GEOMETRY_REGISTER_POINT_2D_CONST(NGeometry::Point2D, double,
                                       cs::cartesian, getX(), getY())
BOOST_GEOMETRY_REGISTER_RING(NGeometry::Polygon2D)

// How to register PolygonalDomain2D?
namespace boost::geometry::traits {
    template <> struct tag<NGeometry::PolygonalDomain2D> {
        using type = polygon_tag;
    };
    template <> struct ring_mutable_type<NGeometry::PolygonalDomain2D> {
        using type = NGeometry::Polygon2D;
    };
    template <> struct ring_const_type<NGeometry::PolygonalDomain2D> {
        using type = NGeometry::Polygon2D const;
    };
    template <> struct exterior_ring<NGeometry::PolygonalDomain2D> {
        static decltype(auto) get(NGeometry::PolygonalDomain2D &v) {
            return *v.getOuter();
        }
        static decltype(auto) get(NGeometry::PolygonalDomain2D const &v) {
            return *v.getOuter();
        }
    };
    template <> struct interior_rings<NGeometry::PolygonalDomain2D> {
        static decltype(auto) get(NGeometry::PolygonalDomain2D &v) {
            return v.getInners() | boost::adaptors::indirected;
        }
        static decltype(auto) get(NGeometry::PolygonalDomain2D const &v) {
            return v.getInners() | boost::adaptors::indirected;
        }
    };
    template <> struct interior_mutable_type<NGeometry::PolygonalDomain2D> {
        using type = decltype(interior_rings<NGeometry::PolygonalDomain2D>::get(
            std::declval<NGeometry::PolygonalDomain2D>()));
    };
    template <> struct interior_const_type<NGeometry::PolygonalDomain2D> {
        using type = decltype(interior_rings<NGeometry::PolygonalDomain2D>::get(
            std::declval<NGeometry::PolygonalDomain2D>())) const;
    };
}

namespace bg = boost::geometry;

template <typename G>
void check_poly(G& g) {
    if constexpr (1) {
        bg::model::polygon<bg::model::d2::point_xy<double>> copy;
        bg::convert(g, copy);
        std::string reason;
        while (!bg::is_valid(copy, reason)) {
            std::cout << "Warning: " << reason << "\n";
            bg::correct(copy);
        }
    }
}

int main() {
    auto report = [](auto heading, auto &g) {
        std::cout << " == " << heading << " ========================\n";
        check_poly(g);
        std::cout << "WKT:       " << bg::wkt(g)       << "\n";
        std::cout << "DSV:       " << bg::dsv(g)       << "\n";
        std::cout << "Area:      " << bg::area(g)      << "\n";
        std::cout << "Perimeter: " << bg::perimeter(g) << "\n";

        if constexpr (std::is_same_v<bg::polygon_tag,
                                     typename bg::traits::tag<
                                         std::decay_t<decltype(g)>>::type>)
        {
            std::cout << "Outer Perimeter: "
                      << bg::perimeter(bg::exterior_ring(g)) << "\n";
        }
    };

    auto outer =
        std::make_shared<NGeometry::Polygon2D>(NGeometry::TyPoints2D{
            {-3.0, -3.0}, {-3.0, 3.0}, {3.0, 3.0}, {3.0, -3.0}, {-3.0, -3.0}});
    auto inner =
        std::make_shared<NGeometry::Polygon2D>(NGeometry::TyPoints2D{
            {-2.0, -2.0}, {2.0, -2.0}, {2.0, 2.0}, {-2.0, 2.0}, {-2.0, -2.0}});

    NGeometry::PolygonalDomain2D domain(outer, {inner});

    report("Outer", *outer);
    report("Inner", *inner);
    report("Domain", domain);

    std::cout << " == Within Check ========================\n";
    std::cout << "Point (0,0) within domain? " << std::boolalpha
              << bg::within(NGeometry::Point2D(0.0, 0.0), domain) << "\n";
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you so much for this detailed analysis and step by step description! I wish the boost geometry examples were worked out like this. Just perfect for me to learn a lot und understand all these concepts. – CppBeginner Mar 06 '21 at 04:20
  • Note to other readers: registring Polygon2D as ring focuses on the PolygonalDomain2D use case. In order to compute the length of a polygon one has to use BOOST_GEOMETRY_REGISTER_LINESTRING(NGeometry::Polygon2D). – CppBeginner Mar 06 '21 at 06:23
  • @CppBeginner Please don't do that. A polygon **isn't** line segments, and has no length, by definition. If you mess with the concept classification, you're going to get wrong outcomes or outright [Undefined Behaviour](https://en.wikipedia.org/wiki/Undefined_behavior). Just use `bg::perimeter` instead! See extended `report` function **[Live On Compiler Explorer](https://godbolt.org/z/eMxe3o)** – sehe Mar 07 '21 at 01:59
  • You can of course register the straight vector as a linestring: `BOOST_GEOMETRY_REGISTER_LINESTRING(NGeometry::TyPoints2D)` or just convert to `bg::model::linestring>`: https://godbolt.org/z/Y9nh1E – sehe Mar 07 '21 at 02:39