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?
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:
Additional info:
THE SOLUTION
An example of what I'm proposing from user's perspective:
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.:
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):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_ifand 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:So with the above, it is possible to use all of it like that:
What do you think?