-
Notifications
You must be signed in to change notification settings - Fork 219
Define CS-specific behavior of algorithms only with strategy (part 1). #533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Remove is_radian and units typedef from non-cartesian cs structs.
…expand to strategies.
…equals to strategies.
- use CS-specific normalization strategies instead of algorithm. - add lower-level strategies getters (point in point, envelope, expand). - pass strategy to equals_point_point()
…artesian-only hack. If using this parameter changes the result it means that a different method should be used. Using it does not solve the real problem, it only hides it. Consider a polygon in another, touching at the first vertex. Checking the midpoint of the first segment of contained polygon could result in finding out that the polygon is inside. However if the segment was collinear to the containing polygon's segment or this polygon had vertex exactly at the checked midpoint the result would be the same as using the first point of the contained polygon.
- use equals P/P in equals_point_point extracted from strategy passed to algorithm - remove Midpoint of point_on_border (this exposes an error in is_valid) - use envelope and expand strategies extracted from strategy passed to algorithm - change union's default strategy (relate v.s. intersection).
…order with relate).
…trategies and use these strategies in algorithms.
Shadowing of template parameters, missing typename keywords, missing includes.
…om algorithms details to strategies. This fixes circular dependencies.
…his also makes template keywords unnecessary.
…egies. Change P/B within strategy concept (no struct template parameters).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot. I went to most of it and it looks consistent and a lot cleaner. Looks like a lot of code could be deleted.
I'm OK with merging. We both touched a little bit of the same areas so I don't hope that there are too many conflicts. As far as I could judge, mostly the touched lines are different so I think there are not many.
But... is it OK if I merge first?
@@ -310,23 +317,27 @@ class multi_point_multi_geometry | |||
} | |||
}; | |||
|
|||
template <typename DisjointPointBoxStrategy> | |||
struct overlaps_box_point | |||
{ | |||
template <typename Box, typename Point> | |||
static inline bool apply(Box const& box, Point const& point) | |||
{ | |||
// The default strategy is enough for Point/Box |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this comment can be removed now (is it still the default strategy? It can now be specified)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should have been removed.
@@ -134,7 +148,8 @@ struct disjoint_segment_box_sphere_or_spheroid | |||
|
|||
if (lon1 > lon2) | |||
{ | |||
swap(lon1, lat1, lon2, lat2); | |||
std::swap(lon1, lon2); | |||
std::swap(lat1, lat2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more clear indeed
} | ||
} | ||
} | ||
|
||
template <typename Range, typename Box, typename Strategy> | ||
static inline void apply(Range const& range, Box& mbr, Strategy const& strategy) | ||
{ | ||
return apply(boost::begin(range), boost::end(range), mbr, strategy); | ||
return apply(Strategy::begin(range), Strategy::end(range), mbr, strategy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is surprising. What is this for kind of strategy? So it should implement range like features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with envelope is that in cartesian we can process points but in spherical and geographic we have to analyse segments because geodesic's vertex may have different latitude (higher or lower on northern or southern hemisphere respectively) than the endpoints. So the strategy feeds the algorithm either with points or with segments. Previously the dispatching by CS was done in the algorithm. I decided to do it this way to allow the strategy to define the smallest Cs-specific portions of the algorithm. Otherwise I'd have to implement the whole calculation of envelope of a range as strategy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
@@ -306,6 +306,17 @@ struct stateful_range_appender<Geometry, open> | |||
} | |||
|
|||
private: | |||
static inline bool disjoint(point_type const& p1, point_type const& p2) | |||
{ | |||
// TODO: pass strategy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK for me, PR is large enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I wanted to postpone it because I didn't want to force the user to pass within point strategy into read_wkt()
. We should probably have separate wkt strategy for that.
{ | ||
typedef geometry::degree units; | ||
}; | ||
|
||
template <> | ||
struct coordinate_system_units<geometry::radian> | ||
struct define_angular_units<geometry::radian> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I see. We don't get backward compatibility issues with this?
OK, I see that it is part of core_detail - then it should be no problem
>::units units; | ||
}; | ||
: core_detail::define_angular_units<DegreeOrRadian> | ||
{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason to derive instead of defining it inside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less lines of code. AFAIU it's a typical way of defining MPL-like meta-functions.
\brief Undefined coordinate system | ||
\ingroup cs | ||
*/ | ||
struct undefined {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
typename Iterator, typename RangeIterator, | ||
typename Section, | ||
typename IntersectionStrategy, typename RobustPolicy | ||
> | ||
static inline void advance_to_non_duplicate_next(Iterator& next, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this. So this will give a merge conflict later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, I'll resolve them.
No problem with merging your PR first. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Adam, it makes more sense now and much clearer. I reviewed it and added some minor comments. In general I am ok with merging and looking forward to part II.
Additionally, since it seems the right thread to ask: if strategies define the CS-specific computation of an algorithm what is an agnostic strategy? Should just be part of the corresponding algorithm?
} | ||
} | ||
} | ||
|
||
template <typename Range, typename Box, typename Strategy> | ||
static inline void apply(Range const& range, Box& mbr, Strategy const& strategy) | ||
{ | ||
return apply(boost::begin(range), boost::end(range), mbr, strategy); | ||
return apply(Strategy::begin(range), Strategy::end(range), mbr, strategy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
{ | ||
return ! detail::disjoint::disjoint_point_point(point1, point2); | ||
return Strategy::apply(point1, point2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need a special strategy for checking if 2 points are equal? Is it the same for all CS? Unless if you consider normalization, but in this case how normalization strategy is passes here? What am I missing...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general yes, this could be done the same for all coordinate systems. For this we'd have to agree what's the valid range of coordinates, e.g. if lon_deg = -180+eps/2
is valid, since this would be true: bg::math::equals(lon_deg + 360, 180)
, this coordinate would be ambiguous and cartesian-like comparison would fail. Currently we have this normalization in place which even if the user represents the coordinates ambiguously (-180
vs 180
or even as something outside valid range) the relation operations still works. In order for this to work we need separate strategies for various CSes even for points.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have a strong opinion here, since I cannot predict the user needs, but what about fixing a range e.g. (-180,180]
, then if the user compare invalid coordinates the result is "unexpected" as with invalid geometries. In any case the user could be use normalization before comparing and have an always correct result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In any case, this should be done in a new PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the normalization could also be a part of correct
. However I think that assuming that -180 is correct is common practice and if we changed the existing behavior users could get unexpected results.
@@ -482,11 +492,13 @@ class get_turns_generic | |||
|
|||
typename IntersectionStrategy::envelope_strategy_type const | |||
envelope_strategy = intersection_strategy.get_envelope_strategy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it needed for intersection strategy to contain both envelope and expand strategies? Why not passing them independently as in the strategies in disjoint_segment_box
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sectionalize
uses 2 policies. And even if we wanted to calculate boxes and expand spherical boxes by other boxes instead of segments, spherical boxes are expanded in different way than cartesian ones so we'd also need second strategy here (expand box/box).
@@ -70,13 +70,7 @@ struct within | |||
Geometry2 const& geometry2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here. why no dispatching as in area and distance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I answered that before but also here, dispatching based on function templates doesn't force us to explicitly state the types. I've done it there only because I was forced to do it, not because I wanted to do it. ;)
@@ -1,6 +1,6 @@ | |||
// Boost.Geometry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now there is strategy named envelope_segment
the name of this file should also change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
Merged develop with recently merged #531 and #540. As a reminder, note that some
@vissarion I'm not sure. Maybe it indeed should simply be a different algorithm with distinguished name, like |
In Boost.Geometry if a strategy is passed into the algorithm the coordinate system of geometries are ignored. The CS-specific behavior of a strategy is used. At least this is the case at the "highest-level". Internally still in many places the information about the CS defined by strategy is not propagated, default strategies are created from geometry types and algorithms dispatched by CS in algorithms instead of in strategies. To fix this:
This PR is the first part of changes addressing the above problems. I decided to propose it because it is big enough already and I want to know if the direction I'm moving in is correct.
In order to find places where change is needed semi-automatically I added new coordinate system
cs::undefined
for which default strategy is not defined nor any algorithm dispatched (besides places where some CS is assumed by default which has to be found manually). Next I modified the following:namespace strategy::normalize
expand
andenvelope
to strategies, replacing algorithm dispatched by CS with proper strategies,equals
/disjoint
for P/P clean, i.e. strategy now contains CS-specific code and algorithm calls this strategy (internal utilityequals_point_point
anddisjoint_point_point
requires strategy now). Also create CS-specific ones. Keep the agnostic one using one of the CS specific default strategies based on geometries.disjoint
for Box/Box into strategies, create proper strategy etc.disjoint
for Segment/Box into strategies.within
Point/Box strategy from agnostic strategies to CS-specific, keep agnostic strategy using default CS-specific strategy based on CS defined by geometries.point_on_border
Midpoint
parameter is removed because it was cartesian-only and only was hiding problems (if there was a problem in algorithm andMidpoint
used then one could have create rings touching at midpoint and get the same result as in the case of rings touching at endpoint but withoutMidpoint
)wkt
now internally creates defaultdisjoint
strategy, I was reluctant to also addStrategy
argument towkt
because I thought that stream manipulators should have only 1 argument butdsv
has more. Still passingdisjoint
strategy towkt
feels wierd.is_valid
.The removal of
Midpoint
inpoint_on_border
exposed a bug inis_valid
erroreously returning a false-positive reported here: #515. So at this point severalsym_difference
tests fail, which is correct.This work is also why I was postponing fixing #466. I'm going bottom-up with the changes and only now approaching the level of envelope for Polygon. Fixing this issue requires to have a different algorithm for cartesian and non-cartesian Rings and Single-Geometries contained in Multi-Geometries. So at least parts of the algorithm would have to be moved to strategies.