The
split-and-merge or
(Ramer-)Douglas-Peucker algorithm simplifies a list of points, removing those deemed redundant with respect to a given tolerance. You can read more about it
here. I used it to reduce the input of the mouse cursor to a manageable set of points in a drawing application. Next is an
implementation in C# in the form of an extension that acts on a
List<Point>:
The entry method:
internal static List<Point> SplitAndMerge(this List<Point> points, double tolerance) {
if (points == null || points.Count < 3) {
return points;
}
else {
List<int> indices = new List<int>();
indices.Add(0);
indices.Add(points.Count - 1);
points.SplitAndMerge(0, points.Count - 1, tolerance, ref indices);
List<point> result = new List<Point>();
indices.Sort();
foreach (int i in indices)
result.Add(points[i]);
return result;
}
}
The private submethod that handles the recursive calls:
private static void SplitAndMerge(this List<Point> points, int indexFirst, int indexLast, double tolerance, ref List<int> indices) {
double maxDist = 0d;
int indexMaxDist = 0;
double dist = 0d;
for (int i = indexFirst; i < indexLast; i++) {
dist = points[i].PerpendicularDistanceFrom(points[indexFirst], points[indexLast]);
if (dist > maxDist) {
maxDist = dist;
indexMaxDist = i;
}
}
if (maxDist > tolerance && indexMaxDist > 0) {
indices.Add(indexMaxDist);
points.SplitAndMerge(indexFirst, indexMaxDist, tolerance, ref indices);
points.SplitAndMerge(indexMaxDist, indexLast, tolerance, ref indices);
}
}
A helper method that calculates the distance between a point p to the line between a and b:
internal static double PerpendicularDistanceFrom(this Point p, Point a, Point b) {
double area = Math.Abs(0.5d * (a.X * b.Y + b.X * p.Y + p.X * a.Y - b.X * a.Y - p.X * b.Y - a.X * p.Y));
double bottom = Math.Sqrt((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y));
return area / bottom * 2d;
}
No comments:
Post a Comment