Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine non-convex hull of collection of line segments

I have a computational geometry problem that I feel should have a relatively simple solution, but I can't quite figure it out.

I need to determine the non-convex outline of a region defined by several line segments.

I'm aware of various non-convex hull algorithms (e.g. alpha shapes), but I don't need a fully general algorithm, as the line segments define a unique solution in most cases.


As @Jean-FrançoisCorbett has pointed out, there are cases where there are multiple solutions. I clearly need to think more about my definition.

However, what I'm trying to do is reverse-engineer and use a proprietary file format so that I can run basic analyses on data collected by myself and others. The file format is simple enough, but determining the algorithm they use to define the boundary is considerably harder.

Putting in many of the edge cases that would result in a non-unique solution causes the software in question to either crash without warning or silently fail to read the file.

Therefore, when there are multiple solutions, either generating one of the acceptable solutions or being able to determine that there are multiple solutions would be acceptable.


Problem Definition:

The polygon's outline should never cross any of the segments and should be formed of lines joining all of the segments' endpoints. All segments must lie entirely inside or along the boundary of the polygon. No endpoint may be used more than once in the outline (Ignoring "closing" the polygon by adding the first point at the end for software libraries that require polygons to close.).

In cases where there are multiple solutions that meet this criteria, any one of those solutions would be acceptable. (It would be nice to be able to determine when the solution is non-unique, but this isn't strictly necessary.)


Examples:

As an example, I have something along these lines: Segments Defining the Area

And I'd like to delineate the following area: Desired Outline

It also should work for non-intersecting segments. E.g.

enter image description hereenter image description here

I think (?) there's a unique solution in either case, subject to the criteria outline earlier. (Edit: There isn't a unique solution in general, as @Jean-FrançoisCorbett pointed out. However, I'm still interested in an algorithm that would either generate one of the acceptable solutions.)

Test Cases

For a test case, here's the code to generate the above figures. I'm using python here, but the question is language-agnostic.

import matplotlib.pyplot as plt  def main():     test1()     test2()     plt.show()  def test1():     """Intersecting segments."""     segments = [[(1, 1), (1, 3)],                 [(3.7, 1), (2, 4)],                 [(2, 0), (3.7, 3)],                 [(4, 0), (4, 4)],                 [(4.3, 1), (4.3, 3)],                 [(0, 2), (6, 3)]]      desired_outline = [segments[0][0], segments[5][0], segments[0][1],                         segments[1][1], segments[2][1], segments[3][1],                        segments[4][1], segments[5][1], segments[4][0],                        segments[3][0], segments[1][0], segments[2][0],                        segments[0][0]]      plot(segments, desired_outline)  def test2():     """Non-intersecting segments."""     segments = [[(0, 1), (0, 3)],                 [(1, 0), (1, 4)],                 [(2, 1), (2, 3)],                 [(3, 0), (3, 4)]]      desired_outline = [segments[0][0], segments[0][1], segments[1][1],                        segments[2][1], segments[3][1], segments[3][0],                         segments[2][0], segments[1][0], segments[0][0]]      plot(segments, desired_outline)   def plot(segments, desired_outline):     fig, ax = plt.subplots()     plot_segments(ax, segments)     ax.set_title('Segments')      fig, ax = plt.subplots()     ax.fill(*zip(*desired_outline), facecolor='gray')     plot_segments(ax, segments)     ax.set_title('Desired Outline')  def plot_segments(ax, segments):     for segment in segments:         ax.plot(*zip(*segment), marker='o', linestyle='-')     xmin, xmax, ymin, ymax = ax.axis()     ax.axis([xmin - 0.5, xmax + 0.5, ymin - 0.5, ymax + 0.5])  if __name__ == '__main__':     main() 

Any ideas?

I'm beginning to suspect that the software whose results I'm trying to reproduce uses a radial-sweep algorithm in some sort of "internal" coordinate system (e.g. A coordinate system with x-prime and y-prime scaled and rotated along the principal axes defined by the spread of points. This makes the problem more "circular".) However, this produces solutions where the outline intersects line segments in many cases. It's easy enough to detect this and brute force it from there, but surely there's a better way?

like image 852
Joe Kington Avatar asked Mar 22 '12 20:03

Joe Kington


People also ask

What is convex hull with example?

One might think of the points as being nails sticking out of a wooden board: then the convex hull is the shape formed by a tight rubber band that surrounds all the nails. A vertex is a corner of a polygon. For example, the highest, lowest, leftmost and rightmost points are all vertices of the convex hull.

How do you find the convex hull of a set?

Put P0 at first position in output hull. 2) Consider the remaining n-1 points and sort them by polar angle in counterclockwise order around points[0]. If the polar angle of two points is the same, then put the nearest point first. 3 After sorting, check if two or more points have the same angle.


1 Answers

  1. Pick a safe starting point. Can be e.g. the endpoint with maximum x.
  2. March along the line segment.
  3. Upon encountering any intersection, always turn left and march along this new segment.
  4. Upon encountering an endpoint, record it. Goto 2.
  5. Stop when you have returned to your starting point. Your list of recorded endpoints now makes up the ordered list of vertices of your concave hull.

NB: This will fail if there is a "free-floating" outlying line segment that does not intersect any other line segment. However, you specify that "the bars uniquely define a solution", which rules out this fail condition. (Outlying segments make possible two distinct solutions.)

EDIT ... or rather, outlying segments can make two distinct solutions possible -- depending on the exact layout. Proof: Below is an example where the yellow segment that I added makes two solutions possible (blue and grey horribly hand-drawn lines). Were the yellow segment orientated perpendicular to the way it's drawn now, only one solution would be possible. Sounds like your problem is poorly defined.

enter image description here

EDIT Actually this can also fail if your segment collection is "very concave", i.e. if there are endpoints tucked away in recluse corners of your pile of segments. In the figure below I added a black segment. My algorithm would illegally join its endpoint to another endpoint (dashed grey line). I'll leave my answer be in case others are inclined to build upon it.

EDIT after giving this some more thought: Even in the "very concave" case, this solution will definitely give you all of the points of your concave hull in the proper order, but they may be interspersed with extra, inappropriate points such as the black one. So there may be too many points.

The answer is then, of course, to do some pruning. It would be fairly complicated pruning especially if you can have multiple, consecutive "recluse points" like the black one, so I don't have a smart algorithm in mind. But even blind, brute force could be feasible. Each point can be either accepted or rejected (boolean), so if you have N properly ordered candidate points in your concave hull, then there are only 2^N possibilities to check. This is way, way fewer possibilities than brute force for your original problem of permutations, which would have SUM of (n!/(n-k)!) for k=1:(n-1) possibilities (pardon my notation). So this algorithm narrows down your problem significantly.

I think this is the way to go.

enter image description here

like image 131
Jean-François Corbett Avatar answered Sep 28 '22 11:09

Jean-François Corbett