Skip to content

Umbrella strategies proposal. #717

@awulkiew

Description

@awulkiew

This is a followup of a discussion about umbrella strategies from this PR: #708 and a comment (from somewhere else?) which I'm unable to find right now.

THE PROBLEM

I'd like to propose an interface of umbrella strategies. I had several goals in mind:

  • The interface should be clear for the user and easy to use without the need to know all small details of the algorithms.
  • The naming should be clear.
  • The strategies should be easy to extend, i.e. modular.

Additional info:

  • The purpose of strategies is to define CS-specific parts of an algorithm so the naming has to involve the CS name and the algorithm name.
  • Several strategies can be used by an algorithm, including strategies of the same algorithm but different geometries.
  • In some rare cases there are interdependencies between algorithms, so an algorithm 1 can depend on algorithm 2 and vice versa at the same time.

THE SOLUTION

An example of what I'm proposing from user's perspective:

double d = bg::distance(pt1, pt2, bg::strategies::distance::cartesian<>());
bg::envelope(ls, box, bg::strategies::envelope::cartesian<>());
bool b = bg::intersects(pt1, pt2, bg::strategies::intersection::cartesian<>());

Notice clear naming and also that the scope of these particular umbrella strategies is the whole algorithm, i.e. they can be passed for all combinations of geometries. The design is modular so it is possible to implement an umbrella strategy for only one or several combinations of geometries or to have the same strategy for all algorithms, e.g.:

bg::strategies::cartesian<> cartesian;
double d = bg::distance(pt1, pt2, cartesian);
bg::envelope(ls, box, cartesian);
bool b = bg::intersects(pt1, pt2, cartesian);

I think that we should focus on algorithm-scope and cs-scope umbrella strategies and to not bother with the combinations of geometries.

THE IMPLEMENTATION

We have to go one more level of abstraction above what we currently have as strategies. No matter what other/unrelated strategies are there in the umbrella strategy any algorithm should be able to access whatever subset of strategies it needs and pass the umbrella strategies to whatever other lower-level algorithms that are called internally. This is why I propose to have umbrella strategies with clearly-named member functions returning simple strategies (the ones with apply()). So any algorithm would call a member function of the same name as the algorithm, get the simple strategy and call it, then if needed pass the umbrella strategy to some other algorithms.

Like that (these are combination-specific implementations, so what would typically be in namespace dispatch):

template <typename Point, typename Box, typename UmbrellaStrategy>
inline void envelope_p(Point const& pt, Box & b, UmbrellaStrategy const& umbrella_strategy)
{
    auto strategy = umbrella_strategy.envelope(pt, b);
    strategy.apply(pt, b);
}

template <typename Point1, typename Point2, typename UmbrellaStrategy>
inline bool intersects_p_p(Point1 const& pt1, Point2 const& pt2, UmbrellaStrategy const& umbrella_strategy)
{
    auto strategy = umbrella_strategy.relate(pt1, pt2); // or intersects
    return strategy.apply(pt1, pt2);
}

template <typename Point1, typename Point2, typename UmbrellaStrategy>
inline double distance_p_p(Point1 const& pt1, Point2 const& pt2, UmbrellaStrategy const& umbrella_strategy)
{
    if (intersects_p_p(pt1, pt2, umbrella_strategy)) // note that the original umbrella_strategy is passed
        return 0;
    auto strategy = umbrella_strategy.distance(pt1, pt2);
    return strategy.apply(pt1, pt2);
}

So with this design it is possible to pass any umbrella strategy containing any subset of simple strategy getters as long as the minimal subset needed by a particular algorithm and all other lower level algorithms is available.

Now, the implementation of strategies. The getters for particular combinations are enabled with std::enable_if and for each algorithm there is a convenient base class defining only the simple strategies of a given algorithm. This allows to create compound strategies easily. This probably will have to be replaced by something different in spherical and geographic to avoid storing multiple instances of the earth model in bigger compound strategies. But for the time being, here is how it looks like:

namespace boost { namespace geometry
{

namespace strategies
{

namespace envelope
{

template <typename CalculationType>
struct cartesian_base
{
    template <typename Geometry, typename Box>
    static typename std::enable_if
        <
            std::is_base_of<pointlike_tag, typename tag<Geometry>::type>::value,
            strategy::envelope::cartesian_point
        >::type
    envelope(Geometry const&, Box const&)
    {
        return strategy::envelope::cartesian_point();
    }

    template <typename Geometry, typename Box>
    static typename std::enable_if
        <
            std::is_base_of<linear_tag, typename tag<Geometry>::type>::value,
            strategy::envelope::cartesian_segment<CalculationType>
        >::type
    envelope(Geometry const&, Box const&)
    {
        return strategy::envelope::cartesian_segment<CalculationType>();
    }
};

template <typename CalculationType = void>
struct cartesian : cartesian_base<CalculationType>
{};

} // namespace envelope

namespace relate
{

template <typename CalculationType>
struct cartesian_base
{
    template <typename Geometry1, typename Geometry2>
    static typename std::enable_if
        <
            std::is_base_of<pointlike_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<pointlike_tag, typename tag<Geometry2>::type>::value,
            strategy::within::cartesian_point_point
        >::type
    relate(Geometry1 const&, Geometry2 const&)
    {
        return strategy::within::cartesian_point_point();
    }

    template <typename Geometry1, typename Geometry2>
    static typename std::enable_if
        <
            std::is_base_of<pointlike_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<linear_tag, typename tag<Geometry2>::type>::value
            || std::is_base_of<linear_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<pointlike_tag, typename tag<Geometry2>::type>::value,
            strategy::within::cartesian_winding<void, void, CalculationType>
        >::type
    relate(Geometry1 const&, Geometry2 const&)
    {
        return strategy::within::cartesian_winding<void, void, CalculationType>();
    }
};

template <typename CalculationType = void>
struct cartesian : cartesian_base<CalculationType>
{};

} // namespace relate

namespace distance
{

template <typename CalculationType>
struct cartesian_base
{
    template <typename Geometry1, typename Geometry2>
    static typename std::enable_if
        <
            std::is_base_of<pointlike_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<pointlike_tag, typename tag<Geometry2>::type>::value,
            strategy::distance::pythagoras<CalculationType>
        >::type
    distance(Geometry1 const&, Geometry2 const&)
    {
        return strategy::distance::pythagoras<CalculationType>();
    }

    template <typename Geometry1, typename Geometry2>
    static typename std::enable_if
        <
            std::is_base_of<pointlike_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<linear_tag, typename tag<Geometry2>::type>::value
            || std::is_base_of<linear_tag, typename tag<Geometry1>::type>::value
            && std::is_base_of<pointlike_tag, typename tag<Geometry2>::type>::value,
            strategy::distance::projected_point<CalculationType>
        >::type
    distance(Geometry1 const&, Geometry2 const&)
    {
        return strategy::distance::projected_point<CalculationType>();
    }
};

template <typename CalculationType = void>
struct cartesian : distance::cartesian_base<CalculationType>
                 , relate::cartesian_base<CalculationType>
{};

} // namespace distance

template <typename CalculationType = void>
struct cartesian : distance::cartesian_base<CalculationType>
                 , envelope::cartesian_base<CalculationType>
                 , relate::cartesian_base<CalculationType>
{};

} // namespace strategies

}} // namespace boost::geometry

So with the above, it is possible to use all of it like that:

using namespace boost::geometry;

using point = model::point<double, 2, cs::cartesian>;
using box = model::box<point>;

point pt1{ 0, 0 }, pt2{ 1, 1 };
box b;

// algorithm-scope umbrella strategies
envelope_p(pt1, b, strategies::envelope::cartesian<>());
bool r1 = intersects_p_p(pt1, pt2, strategies::relate::cartesian<>());
double d1 = distance_p_p(pt1, pt2, strategies::distance::cartesian<>());

// cs-scope umbrella strategies
strategies::cartesian<> cartesian;
envelope_p(pt1, b, cartesian);
bool r2 = intersects_p_p(pt1, pt2, cartesian);
double d2 = distance_p_p(pt1, pt2, cartesian);

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions