summaryrefslogtreecommitdiff
path: root/vendor/github.com/golang/geo/s2/loop_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/golang/geo/s2/loop_test.go')
-rw-r--r--vendor/github.com/golang/geo/s2/loop_test.go533
1 files changed, 533 insertions, 0 deletions
diff --git a/vendor/github.com/golang/geo/s2/loop_test.go b/vendor/github.com/golang/geo/s2/loop_test.go
new file mode 100644
index 0000000..3a1304c
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/loop_test.go
@@ -0,0 +1,533 @@
+/*
+Copyright 2015 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/r1"
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+var (
+ // The northern hemisphere, defined using two pairs of antipodal points.
+ northHemi = LoopFromPoints(parsePoints("0:-180, 0:-90, 0:0, 0:90"))
+
+ // The northern hemisphere, defined using three points 120 degrees apart.
+ northHemi3 = LoopFromPoints(parsePoints("0:-180, 0:-60, 0:60"))
+
+ // The southern hemisphere, defined using two pairs of antipodal points.
+ southHemi = LoopFromPoints(parsePoints("0:90, 0:0, 0:-90, 0:-180"))
+
+ // The western hemisphere, defined using two pairs of antipodal points.
+ westHemi = LoopFromPoints(parsePoints("0:-180, -90:0, 0:0, 90:0"))
+
+ // The eastern hemisphere, defined using two pairs of antipodal points.
+ eastHemi = LoopFromPoints(parsePoints("90:0, 0:0, -90:0, 0:-180"))
+
+ // The "near" hemisphere, defined using two pairs of antipodal points.
+ nearHemi = LoopFromPoints(parsePoints("0:-90, -90:0, 0:90, 90:0"))
+
+ // The "far" hemisphere, defined using two pairs of antipodal points.
+ farHemi = LoopFromPoints(parsePoints("90:0, 0:90, -90:0, 0:-90"))
+
+ // A spiral stripe that slightly over-wraps the equator.
+ candyCane = LoopFromPoints(parsePoints("-20:150, -20:-70, 0:70, 10:-150, 10:70, -10:-70"))
+
+ // A small clockwise loop in the northern & eastern hemisperes.
+ smallNECW = LoopFromPoints(parsePoints("35:20, 45:20, 40:25"))
+
+ // Loop around the north pole at 80 degrees.
+ arctic80 = LoopFromPoints(parsePoints("80:-150, 80:-30, 80:90"))
+
+ // Loop around the south pole at 80 degrees.
+ antarctic80 = LoopFromPoints(parsePoints("-80:120, -80:0, -80:-120"))
+
+ // A completely degenerate triangle along the equator that RobustCCW()
+ // considers to be CCW.
+ lineTriangle = LoopFromPoints(parsePoints("0:1, 0:2, 0:3"))
+
+ // A nearly-degenerate CCW chevron near the equator with very long sides
+ // (about 80 degrees). Its area is less than 1e-640, which is too small
+ // to represent in double precision.
+ skinnyChevron = LoopFromPoints(parsePoints("0:0, -1e-320:80, 0:1e-320, 1e-320:80"))
+
+ // A diamond-shaped loop around the point 0:180.
+ loopA = LoopFromPoints(parsePoints("0:178, -1:180, 0:-179, 1:-180"))
+
+ // Like loopA, but the vertices are at leaf cell centers.
+ snappedLoopA = LoopFromPoints([]Point{
+ CellIDFromLatLng(parseLatLngs("0:178")[0]).Point(),
+ CellIDFromLatLng(parseLatLngs("-1:180")[0]).Point(),
+ CellIDFromLatLng(parseLatLngs("0:-179")[0]).Point(),
+ CellIDFromLatLng(parseLatLngs("1:-180")[0]).Point(),
+ })
+
+ // A different diamond-shaped loop around the point 0:180.
+ loopB = LoopFromPoints(parsePoints("0:179, -1:180, 0:-178, 1:-180"))
+
+ // The intersection of A and B.
+ aIntersectB = LoopFromPoints(parsePoints("0:179, -1:180, 0:-179, 1:-180"))
+
+ // The union of A and B.
+ aUnionB = LoopFromPoints(parsePoints("0:178, -1:180, 0:-178, 1:-180"))
+
+ // A minus B (concave).
+ aMinusB = LoopFromPoints(parsePoints("0:178, -1:180, 0:179, 1:-180"))
+
+ // B minus A (concave).
+ bMinusA = LoopFromPoints(parsePoints("0:-179, -1:180, 0:-178, 1:-180"))
+
+ // A shape gotten from A by adding a triangle to one edge, and
+ // subtracting a triangle from the opposite edge.
+ loopC = LoopFromPoints(parsePoints("0:178, 0:180, -1:180, 0:-179, 1:-179, 1:-180"))
+
+ // A shape gotten from A by adding a triangle to one edge, and
+ // adding another triangle to the opposite edge.
+ loopD = LoopFromPoints(parsePoints("0:178, -1:178, -1:180, 0:-179, 1:-179, 1:-180"))
+
+ // 3------------2
+ // | | ^
+ // | 7-8 b-c | |
+ // | | | | | | Latitude |
+ // 0--6-9--a-d--1 |
+ // | | | | |
+ // | f-e | +----------->
+ // | | Longitude
+ // 4------------5
+ //
+ // Important: It is not okay to skip over collinear vertices when
+ // defining these loops (e.g. to define loop E as "0,1,2,3") because S2
+ // uses symbolic perturbations to ensure that no three vertices are
+ // *ever* considered collinear (e.g., vertices 0, 6, 9 are not
+ // collinear). In other words, it is unpredictable (modulo knowing the
+ // details of the symbolic perturbations) whether 0123 contains 06123
+ // for example.
+
+ // Loop E: 0,6,9,a,d,1,2,3
+ // Loop F: 0,4,5,1,d,a,9,6
+ // Loop G: 0,6,7,8,9,a,b,c,d,1,2,3
+ // Loop H: 0,6,f,e,9,a,b,c,d,1,2,3
+ // Loop I: 7,6,f,e,9,8
+ loopE = LoopFromPoints(parsePoints("0:30, 0:34, 0:36, 0:39, 0:41, 0:44, 30:44, 30:30"))
+ loopF = LoopFromPoints(parsePoints("0:30, -30:30, -30:44, 0:44, 0:41, 0:39, 0:36, 0:34"))
+ loopG = LoopFromPoints(parsePoints("0:30, 0:34, 10:34, 10:36, 0:36, 0:39, 10:39, 10:41, 0:41, 0:44, 30:44, 30:30"))
+ loopH = LoopFromPoints(parsePoints("0:30, 0:34, -10:34, -10:36, 0:36, 0:39, 10:39, 10:41, 0:41, 0:44, 30:44, 30:30"))
+
+ loopI = LoopFromPoints(parsePoints("10:34, 0:34, -10:34, -10:36, 0:36, 10:36"))
+)
+
+func TestLoopEmptyAndFull(t *testing.T) {
+ emptyLoop := EmptyLoop()
+
+ if !emptyLoop.IsEmpty() {
+ t.Errorf("empty loop should be empty")
+ }
+ if emptyLoop.IsFull() {
+ t.Errorf("empty loop should not be full")
+ }
+ if !emptyLoop.isEmptyOrFull() {
+ t.Errorf("empty loop should pass IsEmptyOrFull")
+ }
+
+ fullLoop := FullLoop()
+
+ if fullLoop.IsEmpty() {
+ t.Errorf("full loop should not be empty")
+ }
+ if !fullLoop.IsFull() {
+ t.Errorf("full loop should be full")
+ }
+ if !fullLoop.isEmptyOrFull() {
+ t.Errorf("full loop should pass IsEmptyOrFull")
+ }
+ if emptyLoop.NumEdges() != 0 {
+ t.Errorf("empty loops should have no edges")
+ }
+ if emptyLoop.numChains() != 0 {
+ t.Errorf("empty loops should have no edge chains")
+ }
+ if fullLoop.NumEdges() != 0 {
+ t.Errorf("full loops should have no edges")
+ }
+ if fullLoop.numChains() != 0 {
+ t.Errorf("full loops should have no edge chains")
+ }
+}
+
+func TestLoopBasic(t *testing.T) {
+ shape := Shape(makeLoop("0:0, 0:1, 1:0"))
+
+ if got := shape.NumEdges(); got != 3 {
+ t.Errorf("shape.NumEdges = %d, want 3", got)
+ }
+ if got := shape.numChains(); got != 1 {
+ t.Errorf("shape.numChains = %d, want 1", got)
+ }
+ if got := shape.chainStart(0); got != 0 {
+ t.Errorf("shape.chainStart(0) = %d, want 3", got)
+ }
+ if got := shape.chainStart(1); got != 3 {
+ t.Errorf("shape.chainStart(1) = %d, want 3", got)
+ }
+
+ v2, v3 := shape.Edge(2)
+ if want := PointFromLatLng(LatLngFromDegrees(1, 0)); !v2.ApproxEqual(want) {
+ t.Errorf("shape.Edge(2) end A = %v, want %v", v2, want)
+ }
+ if want := PointFromLatLng(LatLngFromDegrees(0, 0)); !v3.ApproxEqual(want) {
+
+ t.Errorf("shape.Edge(2) end B = %v, want %v", v3, want)
+ }
+
+ if got := shape.dimension(); got != polygonGeometry {
+ t.Errorf("shape.dimension() = %d, want %v", got, polygonGeometry)
+ }
+ if !shape.HasInterior() {
+ t.Errorf("shape.HasInterior() = false, want true")
+ }
+ if shape.ContainsOrigin() {
+ t.Errorf("shape.ContainsOrigin() = true, want false")
+ }
+}
+
+func TestLoopRectBound(t *testing.T) {
+ if !EmptyLoop().RectBound().IsEmpty() {
+ t.Errorf("empty loop's RectBound should be empty")
+ }
+ if !FullLoop().RectBound().IsFull() {
+ t.Errorf("full loop's RectBound should be full")
+ }
+ if !candyCane.RectBound().Lng.IsFull() {
+ t.Errorf("candy cane loop's RectBound should have a full longitude range")
+ }
+ if got := candyCane.RectBound().Lat.Lo; got >= -0.349066 {
+ t.Errorf("candy cane loop's RectBound should have a lower latitude (%v) under -0.349066 radians", got)
+ }
+ if got := candyCane.RectBound().Lat.Hi; got <= 0.174533 {
+ t.Errorf("candy cane loop's RectBound should have an upper latitude (%v) over 0.174533 radians", got)
+ }
+ if !smallNECW.RectBound().IsFull() {
+ t.Errorf("small northeast clockwise loop's RectBound should be full")
+ }
+ if got, want := arctic80.RectBound(), rectFromDegrees(80, -180, 90, 180); !rectsApproxEqual(got, want, rectErrorLat, rectErrorLng) {
+ t.Errorf("arctic 80 loop's RectBound (%v) should be %v", got, want)
+ }
+ if got, want := antarctic80.RectBound(), rectFromDegrees(-90, -180, -80, 180); !rectsApproxEqual(got, want, rectErrorLat, rectErrorLng) {
+ t.Errorf("antarctic 80 loop's RectBound (%v) should be %v", got, want)
+ }
+ if !southHemi.RectBound().Lng.IsFull() {
+ t.Errorf("south hemi loop's RectBound should have a full longitude range")
+ }
+ got, want := southHemi.RectBound().Lat, r1.Interval{-math.Pi / 2, 0}
+ if !got.ApproxEqual(want) {
+ t.Errorf("south hemi loop's RectBound latitude interval (%v) should be %v", got, want)
+ }
+
+ // Create a loop that contains the complement of the arctic80 loop.
+ arctic80Inv := invert(arctic80)
+ // The highest latitude of each edge is attained at its midpoint.
+ mid := Point{arctic80Inv.vertices[0].Vector.Add(arctic80Inv.vertices[1].Vector).Mul(.5)}
+ if got, want := arctic80Inv.RectBound().Lat.Hi, float64(LatLngFromPoint(mid).Lat); math.Abs(got-want) > 10*dblEpsilon {
+ t.Errorf("arctic 80 inverse loop's RectBound should have a latutude hi of %v, got %v", got, want)
+ }
+}
+
+func TestLoopCapBound(t *testing.T) {
+ if !EmptyLoop().CapBound().IsEmpty() {
+ t.Errorf("empty loop's CapBound should be empty")
+ }
+ if !FullLoop().CapBound().IsFull() {
+ t.Errorf("full loop's CapBound should be full")
+ }
+ if !smallNECW.CapBound().IsFull() {
+ t.Errorf("small northeast clockwise loop's CapBound should be full")
+ }
+ if got, want := arctic80.CapBound(), rectFromDegrees(80, -180, 90, 180).CapBound(); !got.ApproxEqual(want) {
+ t.Errorf("arctic 80 loop's CapBound (%v) should be %v", got, want)
+ }
+ if got, want := antarctic80.CapBound(), rectFromDegrees(-90, -180, -80, 180).CapBound(); !got.ApproxEqual(want) {
+ t.Errorf("antarctic 80 loop's CapBound (%v) should be %v", got, want)
+ }
+}
+
+func invert(l *Loop) *Loop {
+ vertices := make([]Point, 0, len(l.vertices))
+ for i := len(l.vertices) - 1; i >= 0; i-- {
+ vertices = append(vertices, l.vertices[i])
+ }
+ return LoopFromPoints(vertices)
+}
+
+func TestLoopOriginInside(t *testing.T) {
+ if !northHemi.originInside {
+ t.Errorf("north hemisphere polygon should include origin")
+ }
+ if !northHemi3.originInside {
+ t.Errorf("north hemisphere 3 polygon should include origin")
+ }
+ if southHemi.originInside {
+ t.Errorf("south hemisphere polygon should not include origin")
+ }
+ if westHemi.originInside {
+ t.Errorf("west hemisphere polygon should not include origin")
+ }
+ if !eastHemi.originInside {
+ t.Errorf("east hemisphere polygon should include origin")
+ }
+ if nearHemi.originInside {
+ t.Errorf("near hemisphere polygon should not include origin")
+ }
+ if !farHemi.originInside {
+ t.Errorf("far hemisphere polygon should include origin")
+ }
+ if candyCane.originInside {
+ t.Errorf("candy cane polygon should not include origin")
+ }
+ if !smallNECW.originInside {
+ t.Errorf("smallNECW polygon should include origin")
+ }
+ if !arctic80.originInside {
+ t.Errorf("arctic 80 polygon should include origin")
+ }
+ if antarctic80.originInside {
+ t.Errorf("antarctic 80 polygon should not include origin")
+ }
+ if loopA.originInside {
+ t.Errorf("loop A polygon should not include origin")
+ }
+}
+
+func TestLoopContainsPoint(t *testing.T) {
+ north := Point{r3.Vector{0, 0, 1}}
+ south := Point{r3.Vector{0, 0, -1}}
+
+ if EmptyLoop().ContainsPoint(north) {
+ t.Errorf("empty loop should not not have any points")
+ }
+ if !FullLoop().ContainsPoint(south) {
+ t.Errorf("full loop should have full point vertex")
+ }
+
+ for _, tc := range []struct {
+ name string
+ l *Loop
+ in Point
+ out Point
+ }{
+ {
+ "north hemisphere",
+ northHemi,
+ Point{r3.Vector{0, 0, 1}},
+ Point{r3.Vector{0, 0, -1}},
+ },
+ {
+ "south hemisphere",
+ southHemi,
+ Point{r3.Vector{0, 0, -1}},
+ Point{r3.Vector{0, 0, 1}},
+ },
+ {
+ "west hemisphere",
+ westHemi,
+ Point{r3.Vector{0, -1, 0}},
+ Point{r3.Vector{0, 1, 0}},
+ },
+ {
+ "east hemisphere",
+ eastHemi,
+ Point{r3.Vector{0, 1, 0}},
+ Point{r3.Vector{0, -1, 0}},
+ },
+ {
+ "candy cane",
+ candyCane,
+ PointFromLatLng(LatLngFromDegrees(5, 71)),
+ PointFromLatLng(LatLngFromDegrees(-8, 71)),
+ },
+ } {
+ l := tc.l
+ for i := 0; i < 4; i++ {
+ if !l.ContainsPoint(tc.in) {
+ t.Errorf("%s loop should contain %v at rotation %d", tc.name, tc.in, i)
+ }
+ if l.ContainsPoint(tc.out) {
+ t.Errorf("%s loop shouldn't contain %v at rotation %d", tc.name, tc.out, i)
+ }
+ l = rotate(l)
+ }
+ }
+}
+
+func TestLoopVertex(t *testing.T) {
+ tests := []struct {
+ loop *Loop
+ vertex int
+ want Point
+ }{
+ {EmptyLoop(), 0, Point{r3.Vector{0, 0, 1}}},
+ {EmptyLoop(), 1, Point{r3.Vector{0, 0, 1}}},
+ {FullLoop(), 0, Point{r3.Vector{0, 0, -1}}},
+ {FullLoop(), 1, Point{r3.Vector{0, 0, -1}}},
+ {arctic80, 0, parsePoint("80:-150")},
+ {arctic80, 1, parsePoint("80:-30")},
+ {arctic80, 2, parsePoint("80:90")},
+ {arctic80, 3, parsePoint("80:-150")},
+ }
+
+ for _, test := range tests {
+ if got := test.loop.Vertex(test.vertex); !pointsApproxEquals(got, test.want, epsilon) {
+ t.Errorf("%v.Vertex(%d) = %v, want %v", test.loop, test.vertex, got, test.want)
+ }
+ }
+
+ // Check that wrapping is correct.
+ if !pointsApproxEquals(arctic80.Vertex(2), arctic80.Vertex(5), epsilon) {
+ t.Errorf("Vertex should wrap values. %v.Vertex(2) = %v != %v.Vertex(5) = %v",
+ arctic80, arctic80.Vertex(2), arctic80, arctic80.Vertex(5))
+ }
+
+ loopAroundThrice := 2 + 3*len(arctic80.vertices)
+ if !pointsApproxEquals(arctic80.Vertex(2), arctic80.Vertex(loopAroundThrice), epsilon) {
+ t.Errorf("Vertex should wrap values. %v.Vertex(2) = %v != %v.Vertex(%d) = %v",
+ arctic80, arctic80.Vertex(2), arctic80, loopAroundThrice, arctic80.Vertex(loopAroundThrice))
+ }
+}
+
+func TestLoopNumEdges(t *testing.T) {
+ tests := []struct {
+ loop *Loop
+ want int
+ }{
+ {EmptyLoop(), 0},
+ {FullLoop(), 0},
+ {farHemi, 4},
+ {candyCane, 6},
+ {smallNECW, 3},
+ {arctic80, 3},
+ {antarctic80, 3},
+ {lineTriangle, 3},
+ {skinnyChevron, 4},
+ }
+
+ for _, test := range tests {
+ if got := test.loop.NumEdges(); got != test.want {
+ t.Errorf("%v.NumEdges() = %v, want %v", test.loop, got, test.want)
+ }
+ }
+}
+
+func TestLoopEdge(t *testing.T) {
+ tests := []struct {
+ loop *Loop
+ edge int
+ wantA Point
+ wantB Point
+ }{
+ {
+ loop: farHemi,
+ edge: 2,
+ wantA: Point{r3.Vector{0, 0, -1}},
+ wantB: Point{r3.Vector{0, -1, 0}},
+ },
+ {
+ loop: candyCane,
+ edge: 0,
+
+ wantA: parsePoint("-20:150"),
+ wantB: parsePoint("-20:-70"),
+ },
+ {
+ loop: candyCane,
+ edge: 1,
+ wantA: parsePoint("-20:-70"),
+ wantB: parsePoint("0:70"),
+ },
+ {
+ loop: candyCane,
+ edge: 2,
+ wantA: parsePoint("0:70"),
+ wantB: parsePoint("10:-150"),
+ },
+ {
+ loop: candyCane,
+ edge: 3,
+ wantA: parsePoint("10:-150"),
+ wantB: parsePoint("10:70"),
+ },
+ {
+ loop: candyCane,
+ edge: 4,
+ wantA: parsePoint("10:70"),
+ wantB: parsePoint("-10:-70"),
+ },
+ {
+ loop: candyCane,
+ edge: 5,
+ wantA: parsePoint("-10:-70"),
+ wantB: parsePoint("-20:150"),
+ },
+ {
+ loop: skinnyChevron,
+ edge: 2,
+ wantA: parsePoint("0:1e-320"),
+ wantB: parsePoint("1e-320:80"),
+ },
+ {
+ loop: skinnyChevron,
+ edge: 3,
+ wantA: parsePoint("1e-320:80"),
+ wantB: parsePoint("0:0"),
+ },
+ }
+
+ for _, test := range tests {
+ if a, b := test.loop.Edge(test.edge); !(pointsApproxEquals(a, test.wantA, epsilon) && pointsApproxEquals(b, test.wantB, epsilon)) {
+ t.Errorf("%v.Edge(%d) = (%v, %v), want (%v, %v)", test.loop, test.edge, a, b, test.wantA, test.wantB)
+ }
+ }
+}
+
+func rotate(l *Loop) *Loop {
+ vertices := make([]Point, 0, len(l.vertices))
+ for i := 1; i < len(l.vertices); i++ {
+ vertices = append(vertices, l.vertices[i])
+ }
+ vertices = append(vertices, l.vertices[0])
+ return LoopFromPoints(vertices)
+}
+
+func TestLoopFromCell(t *testing.T) {
+ cell := CellFromCellID(CellIDFromLatLng(LatLng{40.565459 * s1.Degree, -74.645276 * s1.Degree}))
+ loopFromCell := LoopFromCell(cell)
+
+ // Demonstrates the reason for this test; the cell bounds are more
+ // conservative than the resulting loop bounds.
+ if loopFromCell.RectBound().Contains(cell.RectBound()) {
+ t.Errorf("loopFromCell's RectBound countains the original cells RectBound, but should not")
+ }
+}
+
+func TestLoopRegularLoop(t *testing.T) {
+ loop := RegularLoop(PointFromLatLng(LatLngFromDegrees(80, 135)), 20*s1.Degree, 4)
+ if len(loop.vertices) != 4 {
+ t.Errorf("RegularLoop with 4 vertices should have 4 vertices, got %d", len(loop.vertices))
+ }
+ // The actual Points values are already tested in the s2point_test method TestRegularPoints.
+}