-
Notifications
You must be signed in to change notification settings - Fork 697
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
Avoid generating self-intersecting isochrone polygons that would otherwise result from ::Generalize() #3026
Conversation
…tion test cases that were self intersecting. Lots of cleanup.
…tions to have new results without self-intersections. Much tidying.
@ktatterso Can we get some before and after screen shots of the results. |
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 first round of review
I added some examples. thanks. |
as not to overflow the underlying Tile id (int32_t).
*/ | ||
class PointTileIndex { | ||
// This guy can tell us which tile a points belongs to. | ||
std::unique_ptr<Tiles<PointLL>> tiles; |
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.
Switched to using the Tiles
class for creating a gridded space over the given points-of-interest. Works well and performs equal to my previous solution.
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.
LGTM
@@ -6,6 +6,7 @@ | |||
#include <valhalla/midgard/point2.h> | |||
#include <valhalla/midgard/pointll.h> | |||
|
|||
#include <list> |
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.
nit: unused
for (auto iter = polyline.begin(); iter != polyline.end(); iter++) { | ||
const PointLL& p = *iter; |
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.
nit:
for (auto iter = polyline.begin(); iter != polyline.end(); iter++) { | |
const PointLL& p = *iter; | |
for (const auto& p : polyline) { |
if (p.lat() < min_lat) | ||
min_lat = p.lat(); | ||
if (p.lat() > max_lat) | ||
max_lat = p.lat(); | ||
if (p.lng() < min_lng) | ||
min_lng = p.lng(); | ||
if (p.lng() > max_lng) | ||
max_lng = p.lng(); |
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.
nit
if (p.lat() < min_lat) | |
min_lat = p.lat(); | |
if (p.lat() > max_lat) | |
max_lat = p.lat(); | |
if (p.lng() < min_lng) | |
min_lng = p.lng(); | |
if (p.lng() > max_lng) | |
max_lng = p.lng(); | |
min_lat = std::min(min_lat, p.lat()); | |
max_lat = std::min(max_lat, p.lat()); | |
min_lng = std::min(min_lng, p.lng()); | |
max_lng = std::max(max_lng, p.lng()); |
@@ -1,9 +1,11 @@ | |||
file(GLOB headers ${VALHALLA_SOURCE_DIR}/valhalla/midgard/*.h) | |||
|
|||
set_source_files_properties( | |||
if ((UNIX OR APPLE) AND ENABLE_SINGLE_FILES_WERROR) |
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 believe APPLE is unix https://cmake.org/cmake/help/v3.6/variable/UNIX.html
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.
Re-approve
Issue
When generating isochrones, customers can specify a numeric value,
generalize
orgen_factor
, which simplifies the returned polygon. This is particularly useful for reducing the size of the polygon, and hence, the returned payload.The polyline/polygon simplification method employed by Valhalla is the Douglas-Peucker algorithm. On its own, it has no facilities that would prevent it from generating a self-intersecting polygon during its simplification step. Based on my studies, using a
gen_factor=55
, auto costing, and a 30 min contour - the probability of us returning a self-intersecting polygon is quite high, I'd estimate 80-90% (urban areas).This is problematic for customers/users who take these isochrone polygons and input them to other GIS systems for analysis - only to find their analysis tools don't work with self-intersecting polygons.
My solution to avoid generating self-intersections all boils down to this question: will simplifying this polyline result in a self-intersection? To determine the answer to that question poses a spatial problem: are there any polygon points in the "vicinity" of the polyline that would cause a self-intersection with the polyline I might simplify?
To answer the question "are there any points near this polyline I'm about to simplify?" I have created the
PointTileIndex
class. This class can discretize the lat/lon space into whatever width is desired - in this case,gen_factor
determines the width. ThePointTileIndex
constructor will "bin" all the given points into the index. Then, within the Douglas-Peucker algorithm, we can use thePointTileIndex
to answer that very question: "what points are near this polyline?"Armed with a set of "points near my polyline", we can very accurately ask, "will any of these nearby points cause a self-intersection if I simplify?" Geometrically, you can think of line simplification as the geometric collapse of many triangles. The triangles in question are formed by the start position, the end position, and each point along the polyline. Hence, if a stray polygon point is found in any of the triangles we're about to collapse, we would cause a self-intersection if we simplified.
Example 1
Location: 47.412426,9.334023
Costing: Auto
Contour minutes: 30
Generalize: 0 (no self-intersection)
Generalize: 55 (without fix, self-intersects)
Generalize: 55 (after fix, no self-intersection)
Example 2
Location: 34.061657,-118.268494
Costing: Auto
Contour minutes: 30
Generalize: 0 (no self-intersection)
Generalize: 55 (without fix, self-intersects)
Generalize: 55 (after fix, no self-intersection)
Performance
The
::tile()
method is O(n). Asking for nearby points is k*O(1) where k is the number of nearby tiles. Fortunately, most segments considered for simplification aren't very long, so k is small.For example, the 30-min auto isochrone (
gen_factor=55
) for Orlando previously took 560 ms (not including serialization). The::Generalize()
step took 6 ms of that 560 ms. After my changes,::Generalize()
takes 22 ms, increasing the total time to 578 ms.Thoughts & other ideas
I tried using Boost's
boost::geometry::buffer()
method with adistance_strategy=0.0
, thinking if I gave it a self-intersecting polygon it would fix it. Unfortunately, it did not. Given a self-intersecting polygon it would return an empty result. In contrast, it worked fine for non-self-intersecting polygons.I also wondered about other routines that might heal/repair/fix self-intersecting polygons. In the end, I decided that this approach would keep with the spirit of the original algorithm, only avoiding self-intersections as needed.
Tasklist