diff --git a/rdp/rdp.go b/rdp/rdp.go index 759309e..86be545 100644 --- a/rdp/rdp.go +++ b/rdp/rdp.go @@ -15,7 +15,7 @@ type Point struct { // Note: The resulting slice is a reslice of given points (it shares the same underlying array) for efficiency. // It works similar to append, so the input points should not be used after this call, use only the returned value. func Simplify(points []Point, epsilon float64) []Point { - if len(points) < 2 { + if len(points) <= 2 { return points } @@ -38,19 +38,31 @@ func Simplify(points []Point, epsilon float64) []Point { return append(points[:0], first, last) } - firstHalf, lastHalf := points[:index], points[index:] + // Move index to avoids infinite recursive as slice input + // for next operation is never changed if we keep it as is. + if index == 0 || index == len(points) { + index++ + } + + left, right := points[:index], points[index:] - return append(Simplify(firstHalf, epsilon), Simplify(lastHalf, epsilon)...) + return append(Simplify(left, epsilon), Simplify(right, epsilon)...) } // perpendicularDistance calculates the perpendicular distance from a point to a line segment func perpendicularDistance(p, start, end Point) float64 { if start.X == end.X && start.Y == end.Y { - return euclidean(start, end) + // Find distance between p and (start or end) + return euclidean(p, start) } - numerator := math.Abs((end.Y-start.Y)*p.X - (end.X-start.X)*p.Y + end.X*start.Y - end.Y*start.X) - denominator := euclidean(start, end) - return numerator / denominator + + // Standard Form: Ax + Bx + C = 0 + A := end.Y - start.Y + B := start.X - end.X + C := (end.X * start.Y) - (start.X * end.Y) + + // d = | Ax + By + C = 0 | / ✓(A²+B²) + return math.Abs(A*p.X+B*p.Y+C) / math.Sqrt(A*A+B*B) } // euclidean calculates the distance between two points. diff --git a/rdp/rdp_test.go b/rdp/rdp_test.go index 4df9db8..b0404a5 100644 --- a/rdp/rdp_test.go +++ b/rdp/rdp_test.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "io" + "math" "math/rand" "os" "strconv" @@ -85,6 +86,45 @@ func TestSimplify(t *testing.T) { } } +func TestPerpendicularDistance(t *testing.T) { + tt := []struct { + name string + p Point + start Point + end Point + d float64 + prec float64 + }{ + { + name: "valid result", + p: Point{X: 5, Y: 6}, + start: Point{X: 0, Y: -1.3333333333333333}, + end: Point{X: 2, Y: 0}, + d: 3.328, + prec: 1000, + }, + { + name: "zero result", + p: Point{X: 5, Y: 6}, + start: Point{X: 2, Y: 0}, + end: Point{X: 2, Y: 0}, + d: 6.708, // euclidean((5,6), (2,0)) + prec: 1000, + }, + } + + for i, tc := range tt { + t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) { + d := perpendicularDistance(tc.p, tc.start, tc.end) + d = math.Round(d*tc.prec) / tc.prec + tc.d = math.Round(tc.d*tc.prec) / tc.prec + if d != tc.d { + t.Fatalf("expected: %g, got: %g", tc.d, d) + } + }) + } +} + var update = flag.Bool("update", false, "update the test file") func BenchmarkSimplify(b *testing.B) {