5

Given a path, I want to optimize it so that all verticies that are straight on a line can be removed.

For example: Path:

*******
*      *
*        *
***********

Could be optimized to:

*-----*
|      \
|        \
*---------*

However I want to have control over the deviation from the slope so that it doesnt have to be exactly on the slope.

What sort of algorithm can do this?

Thanks

jmasterx
  • 52,639
  • 96
  • 311
  • 557
  • Actually thinking about this, your problem might have multiple solutions, if deviation is allowed. Take for example points lying on a circle at a fixed angle so that you can remove every other node and will be left with a polygon that only has the desired deviation. However it is not clear which points to remove and which need to be picked for the polygon, so you are left with two possible solutions. If you are fine with both there is not problem and you just select any solution. But if the solution needs to be unique across runs, you are in trouble. – LiKao Aug 11 '11 at 20:43

7 Answers7

7

I believe that you can do this with a simple iterative walk across the points on the path. Keep track, at each point, of the last three points you've encountered. If all three of them are collinear, then remove the middle point from the path, since taking a straight-line path from the first to the third node will pass through the middle node. You could control how much of a deviation there is by having some term that controls how close to collinear the points would have to be.

This can be implemented in O(n) time with a simple pass over the data if you have the points stored in a data structure like a doubly-linked list.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • 2
    If I understand this correctly this does not work as expected when a deviation is involved, since multiple "smoothings" that each are admissible might lead to an overall smoothing that is not admissible anymore. Something like this: points q r s t form one segment of the path, some kind of arc with r being the furthest on the arc. You test q r s an this triplet is collinear within the bound, so r get's removed. Then you test q s t and this is also within the bound so you remove s and are left with q t. But still the distance of r to the line q t might be more than the limit for the smoothing. – LiKao Aug 11 '11 at 20:38
  • Yes, that absolutely is a potential problem with this algorithm. @Milo- Can you give more details about what you're doing? Would this edge case be a problem? – templatetypedef Aug 11 '11 at 20:39
  • This should work, though right now it only optimizes the bottom portion of the path. Maybe I'm not doing isColinear properly, how would you give this a threshold? – jmasterx Aug 11 '11 at 20:52
  • @Milo- You can compute two vectors - one from the first point to the second and one from the second to the third - and then normalize them. Their dot product will then be the cosine of the angle between the two vectors, which will be 1 if they're perfectly collinear and 0 if they're perfectly parallel. You could do something like checking if the dot product is at least 1-epsilon for some epsilon. Also, this should work for all sides, not just the bottom; how are you currently checking for collinearity? – templatetypedef Aug 11 '11 at 20:54
  • Actually looking at this algorithm if all points lie on a circle it might reduce it to a straight line between two points. You could correct this if you leave the middle point(s) in and taking the maximum of all deviations. Then once you you overstep you remove all that are still admissible and go on to the next point. However this might lead to suboptimal solutions (not the maximum number of points dropped). Also this might smooth out vertices of the polygon. – LiKao Aug 11 '11 at 21:17
3

You should use the convex hull algorithm (it depends on how is your polygon stocked in memory) and then clean the points with a min angle on both neighbour point. Then you'll have a polygon with only the point at the extremity.

Here it is: http://en.wikipedia.org/wiki/Convex_hull

They are many possible implementation.It depends on what language you're programing in, and the data you play with..

Edit: I didn't know at time that you had already the points in data.. Just iterate thrue the points and calculate the angle between the one you're on, the prev and the next. if the angle is ~= 180 erase the current point.

GrandMarquis
  • 1,913
  • 1
  • 18
  • 30
  • @jDourlens- Couldn't a convex hull algorithm just hand back the same set of points that you started with if they're all on the convex hull? Also, wouldn't this not work for concave paths (for example, a path around Pac Mac?) Finally, won't this not allow for a "fudge term" that lets you deviate slightly from linear? – templatetypedef Aug 11 '11 at 19:30
  • Ya i didn't saw the "they are stored in order in a std vector of Vec2". I'm editing.. – GrandMarquis Aug 11 '11 at 19:32
  • In fact your answer is what i meant in the second part of mine but you told it more clearly ;) – GrandMarquis Aug 11 '11 at 19:33
3

This is going to be a bit of an abstracted view since I'm not much of a C++ person, but here goes...

Let's take a look at one point right now:

*******
*      *
*        *<- this one, lets call it X
***********

What you're going to do is slowly decide if each point is necessary. To decide if a point is valid, other points must be used, the points immediately before and immediately after:

*******
*      *<- A
*        *
***********<- B

If the angle from A to X is the same (or within an error you deem accurate enough) as the angle from X to B, then X is unnecessary.

This will NOT result in the same outcome as the Convex Hull algorithm. This will simply reduce the resolution of the path. You can get side affects if your allowed error is too great such as this:

     *        *
    *         |
   *          |
    *    ->   |
     *        |
    *         |
   *          *

Or if you're error is too small you may not change the path at all.

Also note that convex hull can greatly change the path, Example:

  *   *            *---*
 * * * *          /     \
*   *   *   ->   *       *
*       *        |       |
*********        *-------*
Corey Ogburn
  • 24,072
  • 31
  • 113
  • 188
0

I've implemented @templatetypedef's solution in C++, for a closed polygonal chain, described by two x,y vectors. I walk the polygon, and if a point is collinear with the previous and the next point, I delete it:

template<class T> void del_collinear_vanilla(std::vector<T> &x,
        std::vector<T> &y) {
    assert(x.size() == y.size());
    size_t i = x.size();
    size_t im1, ip1 = 0;
    do {
        i--;
        im1 = i ? i - 1 : x.size() - 1;
        if (are_collinear(x[ip1], y[ip1], x[i], y[i], x[im1], y[im1])) {
            x.erase(x.begin() + i);
            y.erase(y.begin() + i);
        }
        ip1 = i;
    } while (i != 0);
}

where the implementation depends on a macro/template are_collinear(x0,y0,x1,y1,x2,y2).

However, in some cases I still had some collinear points in the output. This is a sample input with which the algorithm fails:

enter image description here

In the example, P5 coincides with P0 and P4 has the same ordinate of P0 and P1; I changed a little their coordinates to show all the segments. The algorithm should return only a rectangle with vertices P1,P2,P3,P4.

Above, P6 is collinear with P5 and P0. Then, once P6 is eliminated, P5 and P0 coincide, and they are both collinear with P4 and P1.

It turns out that a simple loop over each point, deleting a point if it is collinear with the previous and the next point, does not provide the correct result.

(In the example, let's say you start with P0, and you find that it is not collinear with the point before P6 and the point after P1. Then you move to P1,P2,... until you reach P6. P6 is collinear, you delete it, and the loop is finished. But now P0 is collinear with P4 and P1, and it should have been deleted!)

The same flaw exists for an open path. The algorithm works fine as long as the input path has not collapsed on itself, in a way.

The solution is to take a step back every time you delete a point, to verify if the previous point has now become collinear:

template<class T> void del_collinear(std::vector<T> &x, std::vector<T> &y) {
    assert(x.size() == y.size());
    size_t target = x.size() - 1;
    size_t i = x.size() - 1;
    do {
        size_t im1 = i ? i - 1 : x.size() - 1;
        size_t ip1 = (i == x.size() - 1) ? 0 : i + 1;
        if (are_collinear(x[ip1], y[ip1], x[i], y[i], x[im1], y[im1])) {
            x.erase(x.begin() + i);
            y.erase(y.begin() + i);
            // I do not decrease i in this case, as the the previous (alread
            // processed) point may now be a collinear point that must be
            // deleted. I mod it because i may now exceed x.size()
            i = i % x.size();
            //Increment the target as well.
            target = (i + 1 + x.size()) % x.size();
        } else
            //go for the next point.
            i = i ? i - 1 : x.size() - 1;
    } while (i != target);
}
gg349
  • 21,996
  • 5
  • 54
  • 64
0
set `D` to a maximum deviance of 10 degrees or so.  
set `P` to the first point.
set `Q` to the point after `P`. 
set `A` to the angle from `P` to `Q`.
while `Q` is not that last point in the list
    if the angle from `P` to `Q` is within of `A` plus or minus `D`
        remove `Q`
    else
        set `P` to `Q`
        set `A` to the angle from `P` to `Q`.
    set `Q` to the point after `P`

This is slightly more complicated than the templatetypedef's answer, but has the advantage that it forms a better fit on large curves.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
0

A more complicated solution would involve techniques from image processing. You could try a Hough transform that allows deviations. Deviations can be included by "bluring" the parameter space. However the algorithm is not simple. Also I don't know how well it handles large number of lines, when the number of points on each line is very different. Since your points are ordered you could try to have a look at the parameter space and remove all points that have produced a match. If you select best matches first, you will probably be left with a good solution.

LiKao
  • 10,408
  • 6
  • 53
  • 91
0

I think this page should help you: Simplyfing Polygons (and I also recommend the book).

Igor Brejc
  • 18,714
  • 13
  • 76
  • 95