summaryrefslogtreecommitdiff
path: root/vendor/github.com/golang/geo/s2/point_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/golang/geo/s2/point_test.go')
-rw-r--r--vendor/github.com/golang/geo/s2/point_test.go384
1 files changed, 384 insertions, 0 deletions
diff --git a/vendor/github.com/golang/geo/s2/point_test.go b/vendor/github.com/golang/geo/s2/point_test.go
new file mode 100644
index 0000000..44fdd86
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/point_test.go
@@ -0,0 +1,384 @@
+/*
+Copyright 2014 Google Inc. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package s2
+
+import (
+ "math"
+ "testing"
+
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+func TestOriginPoint(t *testing.T) {
+ if math.Abs(OriginPoint().Norm()-1) > 1e-15 {
+ t.Errorf("Origin point norm = %v, want 1", OriginPoint().Norm())
+ }
+
+ // The point chosen below is about 66km from the north pole towards the East
+ // Siberian Sea. The purpose of the stToUV(2/3) calculation is to keep the
+ // origin as far away as possible from the longitudinal edges of large
+ // Cells. (The line of longitude through the chosen point is always 1/3
+ // or 2/3 of the way across any Cell with longitudinal edges that it
+ // passes through.)
+ p := Point{r3.Vector{-0.01, 0.01 * stToUV(2.0/3), 1}}
+ if !p.ApproxEqual(OriginPoint()) {
+ t.Errorf("Origin point should fall in the Siberian Sea, but does not.")
+ }
+
+ // Check that the origin is not too close to either pole.
+ // The Earth's mean radius in kilometers (according to NASA).
+ const earthRadiusKm = 6371.01
+ if dist := math.Acos(OriginPoint().Z) * earthRadiusKm; dist <= 50 {
+ t.Errorf("Origin point is to close to the North Pole. Got %v, want >= 50km", dist)
+ }
+}
+
+func TestPointCross(t *testing.T) {
+ tests := []struct {
+ p1x, p1y, p1z, p2x, p2y, p2z, norm float64
+ }{
+ {1, 0, 0, 1, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 2},
+ {0, 1, 0, 1, 0, 0, 2},
+ {1, 2, 3, -4, 5, -6, 2 * math.Sqrt(934)},
+ }
+ for _, test := range tests {
+ p1 := Point{r3.Vector{test.p1x, test.p1y, test.p1z}}
+ p2 := Point{r3.Vector{test.p2x, test.p2y, test.p2z}}
+ result := p1.PointCross(p2)
+ if !float64Eq(result.Norm(), test.norm) {
+ t.Errorf("|%v ⨯ %v| = %v, want %v", p1, p2, result.Norm(), test.norm)
+ }
+ if x := result.Dot(p1.Vector); !float64Eq(x, 0) {
+ t.Errorf("|(%v ⨯ %v) · %v| = %v, want 0", p1, p2, p1, x)
+ }
+ if x := result.Dot(p2.Vector); !float64Eq(x, 0) {
+ t.Errorf("|(%v ⨯ %v) · %v| = %v, want 0", p1, p2, p2, x)
+ }
+ }
+}
+
+func TestPointDistance(t *testing.T) {
+ tests := []struct {
+ x1, y1, z1 float64
+ x2, y2, z2 float64
+ want float64 // radians
+ }{
+ {1, 0, 0, 1, 0, 0, 0},
+ {1, 0, 0, 0, 1, 0, math.Pi / 2},
+ {1, 0, 0, 0, 1, 1, math.Pi / 2},
+ {1, 0, 0, -1, 0, 0, math.Pi},
+ {1, 2, 3, 2, 3, -1, 1.2055891055045298},
+ }
+ for _, test := range tests {
+ p1 := Point{r3.Vector{test.x1, test.y1, test.z1}}
+ p2 := Point{r3.Vector{test.x2, test.y2, test.z2}}
+ if a := p1.Distance(p2).Radians(); !float64Eq(a, test.want) {
+ t.Errorf("%v.Distance(%v) = %v, want %v", p1, p2, a, test.want)
+ }
+ if a := p2.Distance(p1).Radians(); !float64Eq(a, test.want) {
+ t.Errorf("%v.Distance(%v) = %v, want %v", p2, p1, a, test.want)
+ }
+ }
+}
+
+func TestChordAngleBetweenPoints(t *testing.T) {
+ for iter := 0; iter < 10; iter++ {
+ m := randomFrame()
+ x := m.col(0)
+ y := m.col(1)
+ z := m.col(2)
+
+ if got := ChordAngleBetweenPoints(z, z).Angle(); got != 0 {
+ t.Errorf("ChordAngleBetweenPoints(%v, %v) = %v, want 0", z, z, got)
+ }
+ if got, want := ChordAngleBetweenPoints(Point{z.Mul(-1)}, z).Angle().Radians(), math.Pi; !float64Near(got, want, 1e-7) {
+ t.Errorf("ChordAngleBetweenPoints(%v, %v) = %v, want %v", z.Mul(-1), z, got, want)
+ }
+ if got, want := ChordAngleBetweenPoints(x, z).Angle().Radians(), math.Pi/2; !float64Eq(got, want) {
+ t.Errorf("ChordAngleBetweenPoints(%v, %v) = %v, want %v", x, z, got, want)
+ }
+ w := Point{y.Add(z.Vector).Normalize()}
+ if got, want := ChordAngleBetweenPoints(w, z).Angle().Radians(), math.Pi/4; !float64Eq(got, want) {
+ t.Errorf("ChordAngleBetweenPoints(%v, %v) = %v, want %v", w, z, got, want)
+ }
+ }
+}
+
+func TestPointApproxEqual(t *testing.T) {
+ tests := []struct {
+ x1, y1, z1 float64
+ x2, y2, z2 float64
+ want bool
+ }{
+ {1, 0, 0, 1, 0, 0, true},
+ {1, 0, 0, 0, 1, 0, false},
+ {1, 0, 0, 0, 1, 1, false},
+ {1, 0, 0, -1, 0, 0, false},
+ {1, 2, 3, 2, 3, -1, false},
+ {1, 0, 0, 1 * (1 + epsilon), 0, 0, true},
+ {1, 0, 0, 1 * (1 - epsilon), 0, 0, true},
+ {1, 0, 0, 1 + epsilon, 0, 0, true},
+ {1, 0, 0, 1 - epsilon, 0, 0, true},
+ {1, 0, 0, 1, epsilon, 0, true},
+ {1, 0, 0, 1, epsilon, epsilon, false},
+ {1, epsilon, 0, 1, -epsilon, epsilon, false},
+ }
+ for _, test := range tests {
+ p1 := Point{r3.Vector{test.x1, test.y1, test.z1}}
+ p2 := Point{r3.Vector{test.x2, test.y2, test.z2}}
+ if got := p1.ApproxEqual(p2); got != test.want {
+ t.Errorf("%v.ApproxEqual(%v), got %v want %v", p1, p2, got, test.want)
+ }
+ }
+}
+
+var (
+ pz = Point{r3.Vector{0, 0, 1}}
+ p000 = Point{r3.Vector{1, 0, 0}}
+ p045 = Point{r3.Vector{1, 1, 0}}
+ p090 = Point{r3.Vector{0, 1, 0}}
+ p180 = Point{r3.Vector{-1, 0, 0}}
+ // Degenerate triangles.
+ pr = Point{r3.Vector{0.257, -0.5723, 0.112}}
+ pq = Point{r3.Vector{-0.747, 0.401, 0.2235}}
+
+ // For testing the Girard area fall through case.
+ g1 = Point{r3.Vector{1, 1, 1}}
+ g2 = Point{g1.Add(pr.Mul(1e-15)).Normalize()}
+ g3 = Point{g1.Add(pq.Mul(1e-15)).Normalize()}
+)
+
+func TestPointArea(t *testing.T) {
+ epsilon := 1e-10
+ tests := []struct {
+ a, b, c Point
+ want float64
+ nearness float64
+ }{
+ {p000, p090, pz, math.Pi / 2.0, 0},
+ // This test case should give 0 as the epsilon, but either Go or C++'s value for Pi,
+ // or the accuracy of the multiplications along the way, cause a difference ~15 decimal
+ // places into the result, so it is not quite a difference of 0.
+ {p045, pz, p180, 3.0 * math.Pi / 4.0, 1e-14},
+ // Make sure that Area has good *relative* accuracy even for very small areas.
+ {Point{r3.Vector{epsilon, 0, 1}}, Point{r3.Vector{0, epsilon, 1}}, pz, 0.5 * epsilon * epsilon, 1e-14},
+ // Make sure that it can handle degenerate triangles.
+ {pr, pr, pr, 0.0, 0},
+ {pr, pq, pr, 0.0, 1e-15},
+ {p000, p045, p090, 0.0, 0},
+ // Try a very long and skinny triangle.
+ {p000, Point{r3.Vector{1, 1, epsilon}}, p090, 5.8578643762690495119753e-11, 1e-9},
+ // TODO(roberts):
+ // C++ includes a 10,000 loop of perterbations to test out the Girard area
+ // computation is less than some noise threshold.
+ // Do we need that many? Will one or two suffice?
+ {g1, g2, g3, 0.0, 1e-15},
+ }
+ for _, test := range tests {
+ if got := PointArea(test.a, test.b, test.c); !float64Near(got, test.want, test.nearness) {
+ t.Errorf("PointArea(%v, %v, %v), got %v want %v", test.a, test.b, test.c, got, test.want)
+ }
+ }
+}
+
+func TestPointAreaQuarterHemisphere(t *testing.T) {
+ tests := []struct {
+ a, b, c, d, e Point
+ want float64
+ }{
+ // Triangles with near-180 degree edges that sum to a quarter-sphere.
+ {Point{r3.Vector{1, 0.1 * epsilon, epsilon}}, p000, p045, p180, pz, math.Pi},
+ // Four other triangles that sum to a quarter-sphere.
+ {Point{r3.Vector{1, 1, epsilon}}, p000, p045, p180, pz, math.Pi},
+ // TODO(roberts):
+ // C++ Includes a loop of 100 perturbations on a hemisphere for more tests.
+ }
+ for _, test := range tests {
+ area := PointArea(test.a, test.b, test.c) +
+ PointArea(test.a, test.c, test.d) +
+ PointArea(test.a, test.d, test.e) +
+ PointArea(test.a, test.e, test.b)
+
+ if !float64Eq(area, test.want) {
+ t.Errorf("Adding up 4 quarter hemispheres with PointArea(), got %v want %v", area, test.want)
+ }
+ }
+}
+
+func TestPointPlanarCentroid(t *testing.T) {
+ tests := []struct {
+ name string
+ p0, p1, p2, want Point
+ }{
+ {
+ name: "xyz axis",
+ p0: Point{r3.Vector{0, 0, 1}},
+ p1: Point{r3.Vector{0, 1, 0}},
+ p2: Point{r3.Vector{1, 0, 0}},
+ want: Point{r3.Vector{1. / 3, 1. / 3, 1. / 3}},
+ },
+ {
+ name: "Same point",
+ p0: Point{r3.Vector{1, 0, 0}},
+ p1: Point{r3.Vector{1, 0, 0}},
+ p2: Point{r3.Vector{1, 0, 0}},
+ want: Point{r3.Vector{1, 0, 0}},
+ },
+ }
+
+ for _, test := range tests {
+ got := PlanarCentroid(test.p0, test.p1, test.p2)
+ if !got.ApproxEqual(test.want) {
+ t.Errorf("%s: PlanarCentroid(%v, %v, %v) = %v, want %v", test.name, test.p0, test.p1, test.p2, got, test.want)
+ }
+ }
+}
+
+func TestPointTrueCentroid(t *testing.T) {
+ // Test TrueCentroid with very small triangles. This test assumes that
+ // the triangle is small enough so that it is nearly planar.
+ // The centroid of a planar triangle is at the intersection of its
+ // medians, which is two-thirds of the way along each median.
+ for i := 0; i < 100; i++ {
+ f := randomFrame()
+ p := f.col(0)
+ x := f.col(1)
+ y := f.col(2)
+ d := 1e-4 * math.Pow(1e-4, randomFloat64())
+
+ // Make a triangle with two equal sides.
+ p0 := Point{p.Sub(x.Mul(d)).Normalize()}
+ p1 := Point{p.Add(x.Mul(d)).Normalize()}
+ p2 := Point{p.Add(y.Mul(d * 3)).Normalize()}
+ want := Point{p.Add(y.Mul(d)).Normalize()}
+
+ got := TrueCentroid(p0, p1, p2).Normalize()
+ if got.Distance(want.Vector) >= 2e-8 {
+ t.Errorf("TrueCentroid(%v, %v, %v).Normalize() = %v, want %v", p0, p1, p2, got, want)
+ }
+
+ // Make a triangle with a right angle.
+ p0 = p
+ p1 = Point{p.Add(x.Mul(d * 3)).Normalize()}
+ p2 = Point{p.Add(y.Mul(d * 6)).Normalize()}
+ want = Point{p.Add(x.Add(y.Mul(2)).Mul(d)).Normalize()}
+
+ got = TrueCentroid(p0, p1, p2).Normalize()
+ if got.Distance(want.Vector) >= 2e-8 {
+ t.Errorf("TrueCentroid(%v, %v, %v).Normalize() = %v, want %v", p0, p1, p2, got, want)
+ }
+ }
+}
+
+func TestPointRegularPoints(t *testing.T) {
+ // Conversion to/from degrees has a little more variability than the default epsilon.
+ const epsilon = 1e-13
+ center := PointFromLatLng(LatLngFromDegrees(80, 135))
+ radius := s1.Degree * 20
+ pts := regularPoints(center, radius, 4)
+
+ if len(pts) != 4 {
+ t.Errorf("regularPoints with 4 vertices should have 4 vertices, got %d", len(pts))
+ }
+
+ lls := []LatLng{
+ LatLngFromPoint(pts[0]),
+ LatLngFromPoint(pts[1]),
+ LatLngFromPoint(pts[2]),
+ LatLngFromPoint(pts[3]),
+ }
+ cll := LatLngFromPoint(center)
+
+ // Make sure that the radius is correct.
+ wantDist := 20.0
+ for i, ll := range lls {
+ if got := cll.Distance(ll).Degrees(); !float64Near(got, wantDist, epsilon) {
+ t.Errorf("Vertex %d distance from center = %v, want %v", i, got, wantDist)
+ }
+ }
+
+ // Make sure the angle between each point is correct.
+ wantAngle := math.Pi / 2
+ for i := 0; i < len(pts); i++ {
+ // Mod the index by 4 to wrap the values at each end.
+ v0, v1, v2 := pts[(4+i+1)%4], pts[(4+i)%4], pts[(4+i-1)%4]
+ if got := float64(v0.Sub(v1.Vector).Angle(v2.Sub(v1.Vector))); !float64Eq(got, wantAngle) {
+ t.Errorf("(%v-%v).Angle(%v-%v) = %v, want %v", v0, v1, v1, v2, got, wantAngle)
+ }
+ }
+
+ // Make sure that all edges of the polygon have the same length.
+ wantLength := 27.990890717782829
+ for i := 0; i < len(lls); i++ {
+ ll1, ll2 := lls[i], lls[(i+1)%4]
+ if got := ll1.Distance(ll2).Degrees(); !float64Near(got, wantLength, epsilon) {
+ t.Errorf("%v.Distance(%v) = %v, want %v", ll1, ll2, got, wantLength)
+ }
+ }
+
+ // Spot check an actual coordinate now that we know the points are spaced
+ // evenly apart at the same angles and radii.
+ if got, want := lls[0].Lat.Degrees(), 62.162880741097204; !float64Near(got, want, epsilon) {
+ t.Errorf("%v.Lat = %v, want %v", lls[0], got, want)
+ }
+ if got, want := lls[0].Lng.Degrees(), 103.11051028343407; !float64Near(got, want, epsilon) {
+ t.Errorf("%v.Lng = %v, want %v", lls[0], got, want)
+ }
+}
+
+func TestPointRegion(t *testing.T) {
+ p := Point{r3.Vector{1, 0, 0}}
+ r := Point{r3.Vector{1, 0, 0}}
+ if !r.Contains(p) {
+ t.Errorf("%v.Contains(%v) = false, want true", r, p)
+ }
+ if !r.Contains(r) {
+ t.Errorf("%v.Contains(%v) = false, want true", r, r)
+ }
+ if s := (Point{r3.Vector{1, 0, 1}}); r.Contains(s) {
+ t.Errorf("%v.Contains(%v) = true, want false", r, s)
+ }
+ if got, want := r.CapBound(), CapFromPoint(p); !got.ApproxEqual(want) {
+ t.Errorf("%v.CapBound() = %v, want %v", r, got, want)
+ }
+ if got, want := r.RectBound(), RectFromLatLng(LatLngFromPoint(p)); !rectsApproxEqual(got, want, epsilon, epsilon) {
+ t.Errorf("%v.RectBound() = %v, want %v", r, got, want)
+ }
+
+ // The leaf cell containing a point is still much larger than the point.
+ cell := CellFromPoint(p)
+ if r.ContainsCell(cell) {
+ t.Errorf("%v.ContainsCell(%v) = true, want false", r, cell)
+ }
+ if !r.IntersectsCell(cell) {
+ t.Errorf("%v.IntersectsCell(%v) = false, want true", r, cell)
+ }
+}
+
+func BenchmarkPointArea(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ PointArea(p000, p090, pz)
+ }
+}
+
+func BenchmarkPointAreaGirardCase(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ PointArea(g1, g2, g3)
+ }
+}