I’ve been working with this example code over on the old forum, which reduces the number of points in a polygon using the Ramer-Douglas-Peucker
algorithm. It works great for my purposes, but for one weird thing: it creates duplicates of all the points
Here’s my demo code; simplification functions below with a few notes:
int numPoints = 12; // reduce to this many points
float[][] pointsArray = {
{ 1687, 1667 }, { 1684, 1708 }, { 1663, 1948 }, { 1662, 1955 }, { 1642, 2030 }, { 1640, 2037 }, { 1639, 2040 }, { 1572, 2225 }, { 1570, 2230 }, { 1457, 2472 }, { 1456, 2474 }, { 1444, 2497 }, { 1378, 2623 }, { 1359, 2656 }, { 1355, 2662 }, { 1332, 2694 }, { 1307, 2717 }, { 1297, 2725 }, { 1283, 2736 }, { 1280, 2738 }, { 1265, 2747 }, { 1261, 2749 }, { 1235, 2756 }, { 1172, 2759 }, { 1110, 2759 }, { 1104, 2756 }, { 1096, 2751 }, { 1095, 2750 }, { 796, 2213 }, { 772, 2164 }, { 753, 2118 }, { 749, 2108 }, { 695, 1960 }, { 674, 1899 }, { 672, 1891 }, { 622, 1631 }, { 610, 1567 }, { 588, 1414 }, { 588, 1350 }, { 591, 1292 }, { 595, 1224 }, { 603, 1134 }, { 628, 964 }, { 643, 911 }, { 660, 863 }, { 664, 854 }, { 768, 670 }, { 1039, 207 }, { 1099, 207 }, { 1101, 209 }, { 1386, 634 }, { 1431, 711 }, { 1522, 870 }, { 1597, 1055 }, { 1600, 1063 }, { 1602, 1069 }, { 1648, 1217 }, { 1650, 1224 }, { 1651, 1228 }, { 1669, 1302 }, { 1670, 1312 }, { 1687, 1495 }
};
void setup() {
size(800, 600);
background(255);
// scale points to fit screen
ArrayList<PVector> points = new ArrayList<PVector>();
for (int i=0; i<pointsArray.length; i++) {
float x = pointsArray[i][0] * 0.2;
float y = pointsArray[i][1] * 0.2;
points.add( new PVector(x,y) );
}
println("original pts:\t\t" + points.size());
// draw original polygon
noFill();
stroke(0, 150, 255);
strokeWeight(2);
beginShape();
for (PVector point : points) {
vertex(point.x, point.y);
}
endShape(CLOSE);
fill(0, 100);
noStroke();
for (PVector point : points) {
ellipse(point.x, point.y, 15, 15);
}
// reduce the number of points in the polygon until it reaches the desired number
int epsilon = 0;
ArrayList<PVector> smoothedPoints = douglasPeucker(points, epsilon);
while (true) {
epsilon += 1;
smoothedPoints = douglasPeucker(points, epsilon);
println("- epsilon " + epsilon + ":\t\t" + smoothedPoints.size());
if (smoothedPoints.size() <= numPoints) {
break;
}
}
// draw the result
translate(width/2, 0);
noFill();
stroke(0, 150, 255);
strokeWeight(2);
beginShape();
for (PVector point : smoothedPoints) {
vertex(point.x, point.y);
}
endShape(CLOSE);
fill(0, 100);
noStroke();
for (PVector point : smoothedPoints) {
ellipse(point.x, point.y, 15, 15);
}
}
And here’s the reduction code – my messy solution is noted about halfway down, where basically I just return the first half of the array list.
ArrayList<PVector> douglasPeucker(ArrayList<PVector> points, float epsilon) {
return douglasPeucker(points, 0, points.size()-1, epsilon);
}
ArrayList<PVector> douglasPeucker(ArrayList<PVector> points, int startIndex, int endIndexInc, float epsilon) {
// find point with max dist
float dMax = 0;
int index = -1;
for (int i = startIndex+1; i <= endIndexInc; i++) {
PVector current = points.get(i);
PVector start = points.get(startIndex);
PVector end = points.get(endIndexInc);
float d = distToSegmentSquared(current.x, current.y, start.x, start.y, end.x, end.y);
if (d > dMax) {
index = i;
dMax = d;
}
}
dMax = sqrt(dMax);
// if it's greater we simplify
if (dMax > epsilon) {
ArrayList<PVector> resultFront = douglasPeucker(points, startIndex, index, epsilon);
ArrayList<PVector> resultBack = douglasPeucker(points, index, endIndexInc, epsilon);
// combine
ArrayList<PVector> result = new ArrayList<PVector>(resultFront);
result.addAll(resultBack);
return result;
}
else {
ArrayList<PVector> result = new ArrayList<PVector>();
result.add( points.get(startIndex) );
result.add( points.get(endIndexInc) );
// ***** PROBLEM HERE!!!! ******
// for some reason gives us duplicates of all the points,
// so we only return every other one
ArrayList<PVector> out = new ArrayList<PVector>();
for (int i=0; i<result.size()/2; i++) {
out.add(result.get(i));
}
return out;
}
}
float dist2(float x1, float y1, float x2, float y2) {
return sq(x1 - x2) + sq(y1 - y2);
}
float distToSegment(float px, float py, float lx1, float ly1, float lx2, float ly2) {
return sqrt(distToSegmentSquared(px, py, lx1, ly1, lx2, ly2));
}
// inspired by stackoverflow.com/a/1501725/1022707
float distToSegmentSquared(float px, float py, float lx1, float ly1, float lx2, float ly2) {
float lineDist = dist2(lx1, ly1, lx2, ly2);
if (lineDist == 0) return dist2(px, py, lx1, ly1);
float t = ((px - lx1) * (lx2 - lx1) + (py - ly1) * (ly2 - ly1)) / lineDist;
if (t < 0) return dist2(px, py, lx1, ly1);
if (t > 1) return dist2(px, py, lx2, ly2);
return dist2(px, py, lx1 + t * (lx2 - lx1), ly1 + t * (ly2 - ly1));
}
I’m pretty lost as to how this is working, so any help would be much appreciated!