summaryrefslogtreecommitdiff
path: root/vendor/github.com/golang/geo/s2/cell_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/golang/geo/s2/cell_test.go')
-rw-r--r--vendor/github.com/golang/geo/s2/cell_test.go522
1 files changed, 522 insertions, 0 deletions
diff --git a/vendor/github.com/golang/geo/s2/cell_test.go b/vendor/github.com/golang/geo/s2/cell_test.go
new file mode 100644
index 0000000..491a7f8
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/cell_test.go
@@ -0,0 +1,522 @@
+/*
+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"
+ "unsafe"
+
+ "github.com/golang/geo/r2"
+ "github.com/golang/geo/s1"
+)
+
+// maxCellSize is the upper bounds on the number of bytes we want the Cell object to ever be.
+const maxCellSize = 48
+
+func TestCellObjectSize(t *testing.T) {
+ if sz := unsafe.Sizeof(Cell{}); sz > maxCellSize {
+ t.Errorf("Cell struct too big: %d bytes > %d bytes", sz, maxCellSize)
+ }
+}
+
+func TestCellFaces(t *testing.T) {
+ edgeCounts := make(map[Point]int)
+ vertexCounts := make(map[Point]int)
+
+ for face := 0; face < 6; face++ {
+ id := CellIDFromFace(face)
+ cell := CellFromCellID(id)
+
+ if cell.id != id {
+ t.Errorf("cell.id != id; %v != %v", cell.id, id)
+ }
+
+ if cell.face != int8(face) {
+ t.Errorf("cell.face != face: %v != %v", cell.face, face)
+ }
+
+ if cell.level != 0 {
+ t.Errorf("cell.level != 0: %v != 0", cell.level)
+ }
+
+ // Top-level faces have alternating orientations to get RHS coordinates.
+ if cell.orientation != int8(face&swapMask) {
+ t.Errorf("cell.orientation != orientation: %v != %v", cell.orientation, face&swapMask)
+ }
+
+ if cell.IsLeaf() {
+ t.Errorf("cell should not be a leaf: IsLeaf = %v", cell.IsLeaf())
+ }
+ for k := 0; k < 4; k++ {
+ edgeCounts[cell.Edge(k)]++
+ vertexCounts[cell.Vertex(k)]++
+ if d := cell.Vertex(k).Dot(cell.Edge(k).Vector); !float64Eq(0.0, d) {
+ t.Errorf("dot product of vertex and edge failed, got %v, want 0", d)
+ }
+ if d := cell.Vertex((k + 1) & 3).Dot(cell.Edge(k).Vector); !float64Eq(0.0, d) {
+ t.Errorf("dot product for edge and next vertex failed, got %v, want 0", d)
+ }
+ if d := cell.Vertex(k).Vector.Cross(cell.Vertex((k + 1) & 3).Vector).Normalize().Dot(cell.Edge(k).Vector); !float64Eq(1.0, d) {
+ t.Errorf("dot product of cross product for vertices failed, got %v, want 1.0", d)
+ }
+ }
+ }
+
+ // Check that edges have multiplicity 2 and vertices have multiplicity 3.
+ for k, v := range edgeCounts {
+ if v != 2 {
+ t.Errorf("edge %v counts wrong, got %d, want 2", k, v)
+ }
+ }
+ for k, v := range vertexCounts {
+ if v != 3 {
+ t.Errorf("vertex %v counts wrong, got %d, want 3", k, v)
+ }
+ }
+}
+
+func TestCellChildren(t *testing.T) {
+ testCellChildren(t, CellFromCellID(CellIDFromFace(0)))
+ testCellChildren(t, CellFromCellID(CellIDFromFace(3)))
+ testCellChildren(t, CellFromCellID(CellIDFromFace(5)))
+}
+
+func testCellChildren(t *testing.T, cell Cell) {
+ children, ok := cell.Children()
+ if cell.IsLeaf() && !ok {
+ return
+ }
+ if cell.IsLeaf() && ok {
+ t.Errorf("leaf cells should not be able to return children. cell %v", cell)
+ }
+
+ if !ok {
+ t.Errorf("unable to get Children for %v", cell)
+ return
+ }
+
+ childID := cell.id.ChildBegin()
+ for i, ci := range children {
+ // Check that the child geometry is consistent with its cell ID.
+ if childID != ci.id {
+ t.Errorf("%v.child[%d].id = %v, want %v", cell, i, ci.id, childID)
+ }
+
+ direct := CellFromCellID(childID)
+ if !ci.Center().ApproxEqual(childID.Point()) {
+ t.Errorf("%v.Center() = %v, want %v", ci, ci.Center(), childID.Point())
+ }
+ if ci.face != direct.face {
+ t.Errorf("%v.face = %v, want %v", ci, ci.face, direct.face)
+ }
+ if ci.level != direct.level {
+ t.Errorf("%v.level = %v, want %v", ci, ci.level, direct.level)
+ }
+ if ci.orientation != direct.orientation {
+ t.Errorf("%v.orientation = %v, want %v", ci, ci.orientation, direct.orientation)
+ }
+ if !ci.Center().ApproxEqual(direct.Center()) {
+ t.Errorf("%v.Center() = %v, want %v", ci, ci.Center(), direct.Center())
+ }
+
+ for k := 0; k < 4; k++ {
+ if !direct.Vertex(k).ApproxEqual(ci.Vertex(k)) {
+ t.Errorf("child %d %v.Vertex(%d) = %v, want %v", i, ci, k, ci.Vertex(k), direct.Vertex(k))
+ }
+ if direct.Edge(k) != ci.Edge(k) {
+ t.Errorf("child %d %v.Edge(%d) = %v, want %v", i, ci, k, ci.Edge(k), direct.Edge(k))
+ }
+ }
+
+ // Test ContainsCell() and IntersectsCell().
+ if !cell.ContainsCell(ci) {
+ t.Errorf("%v.ContainsCell(%v) = false, want true", cell, ci)
+ }
+ if !cell.IntersectsCell(ci) {
+ t.Errorf("%v.IntersectsCell(%v) = false, want true", cell, ci)
+ }
+ if ci.ContainsCell(cell) {
+ t.Errorf("%v.ContainsCell(%v) = true, want false", ci, cell)
+ }
+ if !cell.ContainsPoint(ci.Center()) {
+ t.Errorf("%v.ContainsPoint(%v) = false, want true", cell, ci.Center())
+ }
+ for j := 0; j < 4; j++ {
+ if !cell.ContainsPoint(ci.Vertex(j)) {
+ t.Errorf("%v.ContainsPoint(%v.Vertex(%d)) = false, want true", cell, ci, j)
+ }
+ if j != i {
+ if ci.ContainsPoint(children[j].Center()) {
+ t.Errorf("%v.ContainsPoint(%v[%d].Center()) = true, want false", ci, children, j)
+ }
+ if ci.IntersectsCell(children[j]) {
+ t.Errorf("%v.IntersectsCell(%v[%d]) = true, want false", ci, children, j)
+ }
+ }
+ }
+
+ // Test CapBound and RectBound.
+ parentCap := cell.CapBound()
+ parentRect := cell.RectBound()
+ if cell.ContainsPoint(PointFromCoords(0, 0, 1)) || cell.ContainsPoint(PointFromCoords(0, 0, -1)) {
+ if !parentRect.Lng.IsFull() {
+ t.Errorf("%v.Lng.IsFull() = false, want true", parentRect)
+ }
+ }
+ childCap := ci.CapBound()
+ childRect := ci.RectBound()
+ if !childCap.ContainsPoint(ci.Center()) {
+ t.Errorf("childCap %v.ContainsPoint(%v.Center()) = false, want true", childCap, ci)
+ }
+ if !childRect.ContainsPoint(ci.Center()) {
+ t.Errorf("childRect %v.ContainsPoint(%v.Center()) = false, want true", childRect, ci)
+ }
+ if !parentCap.ContainsPoint(ci.Center()) {
+ t.Errorf("parentCap %v.ContainsPoint(%v.Center()) = false, want true", parentCap, ci)
+ }
+ if !parentRect.ContainsPoint(ci.Center()) {
+ t.Errorf("parentRect %v.ContainsPoint(%v.Center()) = false, want true", parentRect, ci)
+ }
+ for j := 0; j < 4; j++ {
+ if !childCap.ContainsPoint(ci.Vertex(j)) {
+ t.Errorf("childCap %v.ContainsPoint(%v.Vertex(%d)) = false, want true", childCap, ci, j)
+ }
+ if !childRect.ContainsPoint(ci.Vertex(j)) {
+ t.Errorf("childRect %v.ContainsPoint(%v.Vertex(%d)) = false, want true", childRect, ci, j)
+ }
+ if !parentCap.ContainsPoint(ci.Vertex(j)) {
+ t.Errorf("parentCap %v.ContainsPoint(%v.Vertex(%d)) = false, want true", parentCap, ci, j)
+ }
+ if !parentRect.ContainsPoint(ci.Vertex(j)) {
+ t.Errorf("parentRect %v.ContainsPoint(%v.Vertex(%d)) = false, want true", parentRect, ci, j)
+ }
+ if j != i {
+ // The bounding caps and rectangles should be tight enough so that
+ // they exclude at least two vertices of each adjacent cell.
+ capCount := 0
+ rectCount := 0
+ for k := 0; k < 4; k++ {
+ if childCap.ContainsPoint(children[j].Vertex(k)) {
+ capCount++
+ }
+ if childRect.ContainsPoint(children[j].Vertex(k)) {
+ rectCount++
+ }
+ }
+ if capCount > 2 {
+ t.Errorf("childs bounding cap should contain no more than 2 points, got %d", capCount)
+ }
+ if childRect.Lat.Lo > -math.Pi/2 && childRect.Lat.Hi < math.Pi/2 {
+ // Bounding rectangles may be too large at the poles
+ // because the pole itself has an arbitrary longitude.
+ if rectCount > 2 {
+ t.Errorf("childs bounding rect should contain no more than 2 points, got %d", rectCount)
+ }
+ }
+ }
+ }
+
+ // Check all children for the first few levels, and then sample randomly.
+ // We also always subdivide the cells containing a few chosen points so
+ // that we have a better chance of sampling the minimum and maximum metric
+ // values. kMaxSizeUV is the absolute value of the u- and v-coordinate
+ // where the cell size at a given level is maximal.
+ maxSizeUV := 0.3964182625366691
+ specialUV := []r2.Point{
+ r2.Point{dblEpsilon, dblEpsilon}, // Face center
+ r2.Point{dblEpsilon, 1}, // Edge midpoint
+ r2.Point{1, 1}, // Face corner
+ r2.Point{maxSizeUV, maxSizeUV}, // Largest cell area
+ r2.Point{dblEpsilon, maxSizeUV}, // Longest edge/diagonal
+ }
+ forceSubdivide := false
+ for _, uv := range specialUV {
+ if ci.BoundUV().ContainsPoint(uv) {
+ forceSubdivide = true
+ }
+ }
+
+ // For a more in depth test, add an "|| oneIn(n)" to this condition
+ // to cause more children to be tested beyond the ones to level 5.
+ if forceSubdivide || cell.level < 5 {
+ testCellChildren(t, ci)
+ }
+
+ childID = childID.Next()
+ }
+}
+
+func TestCellAreas(t *testing.T) {
+ // relative error bounds for each type of area computation
+ var exactError = math.Log(1 + 1e-6)
+ var approxError = math.Log(1.03)
+ var avgError = math.Log(1 + 1e-15)
+
+ // Test 1. Check the area of a top level cell.
+ const level1Cell = CellID(0x1000000000000000)
+ const wantArea = 4 * math.Pi / 6
+ if area := CellFromCellID(level1Cell).ExactArea(); !float64Eq(area, wantArea) {
+ t.Fatalf("Area of a top-level cell %v = %f, want %f", level1Cell, area, wantArea)
+ }
+
+ // Test 2. Iterate inwards from this cell, checking at every level that
+ // the sum of the areas of the children is equal to the area of the parent.
+ childIndex := 1
+ for cell := CellID(0x1000000000000000); cell.Level() < 21; cell = cell.Children()[childIndex] {
+ var exactArea, approxArea, avgArea float64
+ for _, child := range cell.Children() {
+ exactArea += CellFromCellID(child).ExactArea()
+ approxArea += CellFromCellID(child).ApproxArea()
+ avgArea += CellFromCellID(child).AverageArea()
+ }
+
+ if area := CellFromCellID(cell).ExactArea(); !float64Eq(exactArea, area) {
+ t.Fatalf("Areas of children of a level-%d cell %v don't add up to parent's area. "+
+ "This cell: %e, sum of children: %e",
+ cell.Level(), cell, area, exactArea)
+ }
+
+ childIndex = (childIndex + 1) % 4
+
+ // For ExactArea(), the best relative error we can expect is about 1e-6
+ // because the precision of the unit vector coordinates is only about 1e-15
+ // and the edge length of a leaf cell is about 1e-9.
+ if logExact := math.Abs(math.Log(exactArea / CellFromCellID(cell).ExactArea())); logExact > exactError {
+ t.Errorf("The relative error of ExactArea for children of a level-%d "+
+ "cell %v should be less than %e, got %e. This cell: %e, children area: %e",
+ cell.Level(), cell, exactError, logExact,
+ CellFromCellID(cell).ExactArea(), exactArea)
+ }
+ // For ApproxArea(), the areas are accurate to within a few percent.
+ if logApprox := math.Abs(math.Log(approxArea / CellFromCellID(cell).ApproxArea())); logApprox > approxError {
+ t.Errorf("The relative error of ApproxArea for children of a level-%d "+
+ "cell %v should be within %e%%, got %e. This cell: %e, sum of children: %e",
+ cell.Level(), cell, approxError, logApprox,
+ CellFromCellID(cell).ExactArea(), exactArea)
+ }
+ // For AverageArea(), the areas themselves are not very accurate, but
+ // the average area of a parent is exactly 4 times the area of a child.
+ if logAvg := math.Abs(math.Log(avgArea / CellFromCellID(cell).AverageArea())); logAvg > avgError {
+ t.Errorf("The relative error of AverageArea for children of a level-%d "+
+ "cell %v should be less than %e, got %e. This cell: %e, sum of children: %e",
+ cell.Level(), cell, avgError, logAvg,
+ CellFromCellID(cell).AverageArea(), avgArea)
+ }
+ }
+}
+
+func TestCellIntersectsCell(t *testing.T) {
+ tests := []struct {
+ c Cell
+ oc Cell
+ want bool
+ }{
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(5)),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).Next()),
+ false,
+ },
+ }
+ for _, test := range tests {
+ if got := test.c.IntersectsCell(test.oc); got != test.want {
+ t.Errorf("Cell(%v).IntersectsCell(%v) = %t; want %t", test.c, test.oc, got, test.want)
+ }
+ }
+}
+
+func TestCellContainsCell(t *testing.T) {
+ tests := []struct {
+ c Cell
+ oc Cell
+ want bool
+ }{
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(5)),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(5)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ false,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).Next()),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ false,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).Next()),
+ false,
+ },
+ }
+ for _, test := range tests {
+ if got := test.c.ContainsCell(test.oc); got != test.want {
+ t.Errorf("Cell(%v).ContainsCell(%v) = %t; want %t", test.c, test.oc, got, test.want)
+ }
+ }
+}
+
+func TestCellRectBound(t *testing.T) {
+ tests := []struct {
+ lat float64
+ lng float64
+ }{
+ {50, 50},
+ {-50, 50},
+ {50, -50},
+ {-50, -50},
+ {0, 0},
+ {0, 180},
+ {0, -179},
+ }
+ for _, test := range tests {
+ c := CellFromLatLng(LatLngFromDegrees(test.lat, test.lng))
+ rect := c.RectBound()
+ for i := 0; i < 4; i++ {
+ if !rect.ContainsLatLng(LatLngFromPoint(c.Vertex(i))) {
+ t.Errorf("%v should contain %v", rect, c.Vertex(i))
+ }
+ }
+ }
+}
+
+func TestCellRectBoundAroundPoleMinLat(t *testing.T) {
+ tests := []struct {
+ cellID CellID
+ latLng LatLng
+ wantContains bool
+ }{
+ {
+ cellID: CellIDFromFacePosLevel(2, 0, 0),
+ latLng: LatLngFromDegrees(3, 0),
+ wantContains: false,
+ },
+ {
+ cellID: CellIDFromFacePosLevel(2, 0, 0),
+ latLng: LatLngFromDegrees(50, 0),
+ wantContains: true,
+ },
+ {
+ cellID: CellIDFromFacePosLevel(5, 0, 0),
+ latLng: LatLngFromDegrees(-3, 0),
+ wantContains: false,
+ },
+ {
+ cellID: CellIDFromFacePosLevel(5, 0, 0),
+ latLng: LatLngFromDegrees(-50, 0),
+ wantContains: true,
+ },
+ }
+ for _, test := range tests {
+ if got := CellFromCellID(test.cellID).RectBound().ContainsLatLng(test.latLng); got != test.wantContains {
+ t.Errorf("CellID(%v) contains %v: got %t, want %t", test.cellID, test.latLng, got, test.wantContains)
+ }
+ }
+}
+
+func TestCellCapBound(t *testing.T) {
+ c := CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(20))
+ s2Cap := c.CapBound()
+ for i := 0; i < 4; i++ {
+ if !s2Cap.ContainsPoint(c.Vertex(i)) {
+ t.Errorf("%v should contain %v", s2Cap, c.Vertex(i))
+ }
+ }
+}
+
+func TestCellContainsPoint(t *testing.T) {
+ tests := []struct {
+ c Cell
+ p Point
+ want bool
+ }{
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(5)).Vertex(1),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2)).Vertex(1),
+ true,
+ },
+ {
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(5)),
+ CellFromCellID(CellIDFromFace(0).ChildBeginAtLevel(2).Next().ChildBeginAtLevel(5)).Vertex(1),
+ false,
+ },
+ }
+ for _, test := range tests {
+ if got := test.c.ContainsPoint(test.p); got != test.want {
+ t.Errorf("Cell(%v).ContainsPoint(%v) = %t; want %t", test.c, test.p, got, test.want)
+ }
+ }
+}
+
+func TestCellContainsPointConsistentWithS2CellIDFromPoint(t *testing.T) {
+ // Construct many points that are nearly on a Cell edge, and verify that
+ // CellFromCellID(cellIDFromPoint(p)).Contains(p) is always true.
+ for iter := 0; iter < 1000; iter++ {
+ cell := CellFromCellID(randomCellID())
+ i1 := randomUniformInt(4)
+ i2 := (i1 + 1) & 3
+ v1 := cell.Vertex(i1)
+ v2 := samplePointFromCap(CapFromCenterAngle(cell.Vertex(i2), s1.Angle(epsilon)))
+ p := Interpolate(randomFloat64(), v1, v2)
+ if !CellFromCellID(cellIDFromPoint(p)).ContainsPoint(p) {
+ t.Errorf("For p=%v, CellFromCellID(cellIDFromPoint(p)).ContainsPoint(p) was false", p)
+ }
+ }
+}
+
+func TestCellContainsPointContainsAmbiguousPoint(t *testing.T) {
+ // This tests a case where S2CellId returns the "wrong" cell for a point
+ // that is very close to the cell edge. (ConsistentWithS2CellIdFromPoint
+ // generates more examples like this.)
+ //
+ // The Point below should have x = 0, but conversion from LatLng to
+ // (x,y,z) gives x = ~6.1e-17. When xyz is converted to uv, this gives
+ // u = -6.1e-17. However when converting to st, which has a range of [0,1],
+ // the low precision bits of u are lost and we wind up with s = 0.5.
+ // cellIDFromPoint then chooses an arbitrary neighboring cell.
+ //
+ // This tests that Cell.ContainsPoint() expands the cell bounds sufficiently
+ // so that the returned cell is still considered to contain p.
+ p := PointFromLatLng(LatLngFromDegrees(-2, 90))
+ cell := CellFromCellID(cellIDFromPoint(p).Parent(1))
+ if !cell.ContainsPoint(p) {
+ t.Errorf("For p=%v, CellFromCellID(cellIDFromPoint(p)).ContainsPoint(p) was false", p)
+ }
+}