[GTL] clipping and merge of general polygons with attributes

I am currently porting pre-existing internal code for performing scanline based merge of arbitrary-angle planar polygons into GTL. The algorithm carries generic property values associated with the input polygons through to the output. That means that if polygon A has property X and polygon B has property Y the output of merge would be potentially three types of polygons, those with property X, those with property Y and those with property {X, Y}. This is essentially the 2D version of the interval set operations proposed by Joachim Faulhaber. Obviously, it can be used as the foundation for general Boolean operations: e.g. the intersection between all polygons with property X and all polygons with property Y is simply the portion of the output with property {X, Y}. I wanted to ask Bruno and Barend to comment on whether they think this algorithm would be useful and solicit the boost community in general to comment on my progress designing the interfaces to it. I looked at Barend's library and could only find rectangle clipping, did I miss it, or is a comparable capability not provided by Barend's geometry library? I also looked at the documentation for Mateusz Loskot's geos library, but was only able to find linestring intersection algorithms, and the geometry container and polygon container types didn't obviously support Boolean operations, though I could have missed it. I would have expected that it would. I have added arbitrary angle polygon data types and concepts to GTL to support the API the algorithm will provide. These are different from those proposed by Barend, though the concepts should be compatible with his data types. My concepts for polygons center around the idea that they should be able to provide iterator range semantics for getting and setting their contents. I have concepts for the following struct polygon_90_concept {...}; struct polygon_90_with_holes_concept : polygon_90_concept {...}; struct polygon_45_concept : polygon_90_concept {...}; struct polygon_45_with_holes_concept : virtual polygon_45_concept, polygon_90_with_holes_concept {...}; struct polygon_concept : polygon_45_concept {...}; struct polygon_with_holes_concept : virtual polygon_concept, polygon_45_with_holes_concept {...}; A polygon_90 is a polygon that is restricted to axis-parallel edges, and has angles that are all multiple of 90-degrees. A polygon_45 is a polygon that has angles that are all multiple of 45-degrees. A polygon is a polygon with arbitrary angles. The concepts inherit from each other to share some implementation, such as the holes, and to allow some measure of polymorphism of polygon types. For instance, the result of a Boolean operation on 90-degree geometry could be stored to arbitrary angle polygons at the output with the same generic function call as would be used to store to 90-degree polygons because an arbitrary angle polygon can represent 90-degree angle data. The high level overview of the API proposal is that generic free functions detect the conceptual type of geometry objects that are called on and redirect the call to the function of the same name defined within the appropriate geometry concept, sometimes with tag dispatching to select between different versions of the function within the concept. For example contains: template <typename geometry_type_1, typename geometry_type_2> bool contains(const geometry_type_1& geometry_object, const geometry_type_2& contained_geometry_object, bool consider_touch = true) { return geometry_concept<geometry_type_1>::type::contains(geometry_object, contained_geometry_object, consider_touch, typename geometry_concept<geometry_type_2>::type()); } might be called on a rectangle and a point, a polygon and a rectangle, or any other sensible combination of geometry objects, provided that the associated concept structs define the algorithm required conforming to the syntax expected. For example, the rectangle contains point function is defined as follows: template <typename rectangle_type, typename point_type> static bool contains(const rectangle_type& rectangle, const point_type point_contained, bool consider_touch, point_concept tag) { return interval_concept::contains(get<HORIZONTAL>(rectangle), point_concept::get<HORIZONTAL>(point_contained), consider_touch, no_type()) && interval_concept::contains(get<VERTICAL>(rectangle), point_concept::get<VERTICAL>(point_contained), consider_touch, no_type()); } This indirection leads to some welcome benefits to usability. It leads to very simple and straightforward syntax errors in incorrectly formed user code, for instance, say the user calls vertical(user_point_object), which shouldn't work because vertical expects a rectangle or prism, the compiler generates the fairly readable error: gtl.hpp: In function typename component_type<geometry_type>::type vertical(const geometry_type&) [with geometry_type = point_data<int>]: main.cpp:14: instantiated from here gtl.hpp:125: error: vertical is not a member of point_concept , telling the user only where their mistake was (main.cpp:14) and what is wrong (vertical is not a member of point_concept), which is exactly the information the user needs to fix their error and not more. Other syntax errors may result in substitution failure such as if the user calls center(user_point_object), which shouldn't work because a point doesn't have a center, the compiler generates a substitution failure error instead: main.cpp:14: instantiated from here post_geometry_traits_definitions.hpp:24: error: no type named center_type in struct point_concept::registration<point_data<int> > main.cpp: In function void foo(): main.cpp:14: error: no matching function for call to center(point_data<int>&) main.cpp: In function bool testRectangle(): , informing the user that point doesn't provide a center_type and giving them the very clear error that there is no function center() which takes their data type. In the case that the user has not registered their type with the library the error will look like: ... gtl.hpp:125: error: <function name> is not a member of no_type I could change the name of no_type to undefined_concept to make it a little more obvious what is going on. I am currently considering adding a coordinate_concept and coordinate_traits to the library because for 32-bit integer we would like to use 64-bit integer for area types and double for Euclidean distance, but for floating point coordinates we would just use the coordinate type for those. With the need for numerical robustness of arbitrary geometry algorithms looming the use of more advanced coordinate data types looks likely, which might necessitate traits. As always, I welcome any feedback that will help me improve the library and make it conform to boost standards/expectations for eventual review when I have completed the re-write. You can find the up-to-date re-write of the library in sandbox/gtl. Thanks, Luke

Hi Luke,
I wanted to ask Bruno and Barend to comment on whether they think this algorithm would be useful and solicit the boost community in general to comment on my progress designing the interfaces to it. I looked at Barend's library and could only find rectangle clipping, did I miss it, or is a comparable capability not provided by Barend's geometry library?
For now we're mostly busy with design questions, how to represent data, how the user integrates his own into the library, and adapting the library in that direction. Once this will be done, we'll see more clearly and be able to add any type of computation to the proposed set of algorithms. Your API based on tag dispatching looks good to me. Could you just be more precise on the polygon concepts? Which are the different valid expressions required by each of them? For now Barend's library doesn't use concepts to represent polygons but a provided class, due to the complexity of manipulating a polygon (compared to a linestring for example, that can be summarized by a mere pair of iterators). So I'm curious to see what could be a polygon concept... One other question: at the beginning your library was supposed to me limited to 90 and 45 degrees computations (if I understood well), but you're now talking about arbitrary angles. Has the scope of your library changed? Did I miss something? Regards Bruno

Bruno Lalande wrote:
Your API based on tag dispatching looks good to me. Could you just be more precise on the polygon concepts? Which are the different valid expressions required by each of them? For now Barend's library doesn't use concepts to represent polygons but a provided class, due to the complexity of manipulating a polygon (compared to a linestring for example, that can be summarized by a mere pair of iterators). So I'm curious to see what could be a polygon concept...
I'm glad to hear that you approve of the tag dispatching based generic API. I have been assuming that the lack of strong opposition to the idea on the boost list meant that people generally approved. :) I noticed that Barend's library provided a heavily templated polygon class. Unfortunately providing a new polygon data type does not benefit most applications, which already have their own polygon if one was needed. The primary benefit that generic programming can provide in such an environment is to ease and improve the quality of integration of new library algorithms with application data types, such as polygon. To be very specific, a polygon must be able to get and set its outer shell using iterator semantics over polygon vertices. It should also be default/copy constructible and provide an assignment operator such that it can be an element of a std container. Additionally, to support efficient axis-parallel polygon data crunching it should provide the "compact" iterator interface to get and set its shell based upon ranges of non-redundant coordinates (one per edge). I provide lazy iterator algorithms for up and down converting from iterator ranges of points to compact coordinates and visa-versa. It is expected that getting compact iterator range would throw en exception if the polygon were not axis-parallel. Any of these requirements need not be satisfied if the templates that use them are not instantiated, and I would provide separate checks, instead of is_polygon. This is because some polygon objects may be read-only, not default constructable, or otherwise crippled, but still compatible with a useful subset of the polygon concept related behaviors in my library. I specifically do not require polygons to conform to a winding orientation convention or open/closed (redundant last vertex) convention because these choices are made arbitrarily by authors of polygon data types, and it is not particularly hard to write loops that are invariant to such considerations, though it does increase validation effort. Because a legacy polygon data type will provide its own legacy vertex type I must also provide a vertex concept in my library to be able to consume an iterator range over the legacy vertex type.
One other question: at the beginning your library was supposed to me limited to 90 and 45 degrees computations (if I understood well), but you're now talking about arbitrary angles. Has the scope of your library changed? Did I miss something?
Yes, and no. At the beginning I said that we have an internal code base of arbitrary angle algorithms that may be ported into my library at some future time. We have two independent implementations of scanline on arbitrary angle polygons. I have been scheduled to port one of these into my library within the next several weeks. I didn't have a concrete plan for when this work would happen before, and now I do, so that has changed. Thanks, Luke
participants (2)
-
Bruno Lalande
-
Simonson, Lucanus J