summaryrefslogtreecommitdiff
path: root/vendor/github.com/golang/geo/s2
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2017-03-19 15:19:42 +0000
committerFelix Hanley <felix@userspace.com.au>2017-03-19 15:19:42 +0000
commit8a541d499b6f117cd3a81e475ee779ba60fc0637 (patch)
tree7b3b5326235725ab93056b5ff4637d987fb0a7b6 /vendor/github.com/golang/geo/s2
parentfe847b2d01060044274d20d2c35ae01a684d4ee3 (diff)
downloadcrjw-maps-master.tar.gz
crjw-maps-master.tar.bz2
use golang dep tool for depsHEADmaster
Diffstat (limited to 'vendor/github.com/golang/geo/s2')
-rw-r--r--vendor/github.com/golang/geo/s2/LICENSE202
-rw-r--r--vendor/github.com/golang/geo/s2/cap.go222
-rw-r--r--vendor/github.com/golang/geo/s2/cap_test.go718
-rw-r--r--vendor/github.com/golang/geo/s2/cell_test.go522
-rw-r--r--vendor/github.com/golang/geo/s2/cellid.go176
-rw-r--r--vendor/github.com/golang/geo/s2/cellid_test.go1052
-rw-r--r--vendor/github.com/golang/geo/s2/cellunion_test.go723
-rw-r--r--vendor/github.com/golang/geo/s2/edgeutil.go4
-rw-r--r--vendor/github.com/golang/geo/s2/edgeutil_test.go1201
-rw-r--r--vendor/github.com/golang/geo/s2/latlng.go3
-rw-r--r--vendor/github.com/golang/geo/s2/latlng_test.go155
-rw-r--r--vendor/github.com/golang/geo/s2/loop.go20
-rw-r--r--vendor/github.com/golang/geo/s2/loop_test.go533
-rw-r--r--vendor/github.com/golang/geo/s2/matrix3x3.go16
-rw-r--r--vendor/github.com/golang/geo/s2/matrix3x3_test.go494
-rw-r--r--vendor/github.com/golang/geo/s2/metric_test.go109
-rw-r--r--vendor/github.com/golang/geo/s2/paddedcell_test.go197
-rw-r--r--vendor/github.com/golang/geo/s2/point.go32
-rw-r--r--vendor/github.com/golang/geo/s2/point_test.go384
-rw-r--r--vendor/github.com/golang/geo/s2/polygon.go57
-rw-r--r--vendor/github.com/golang/geo/s2/polygon_test.go199
-rw-r--r--vendor/github.com/golang/geo/s2/polyline.go20
-rw-r--r--vendor/github.com/golang/geo/s2/polyline_test.go144
-rw-r--r--vendor/github.com/golang/geo/s2/predicates_test.go314
-rw-r--r--vendor/github.com/golang/geo/s2/rect.go7
-rw-r--r--vendor/github.com/golang/geo/s2/rect_test.go862
-rw-r--r--vendor/github.com/golang/geo/s2/region.go1
-rw-r--r--vendor/github.com/golang/geo/s2/regioncoverer_test.go151
-rw-r--r--vendor/github.com/golang/geo/s2/s2_test.go413
-rw-r--r--vendor/github.com/golang/geo/s2/s2_test_test.go196
-rw-r--r--vendor/github.com/golang/geo/s2/shapeindex.go80
-rw-r--r--vendor/github.com/golang/geo/s2/shapeindex_test.go84
-rw-r--r--vendor/github.com/golang/geo/s2/stuv_test.go320
33 files changed, 9277 insertions, 334 deletions
diff --git a/vendor/github.com/golang/geo/s2/LICENSE b/vendor/github.com/golang/geo/s2/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/vendor/github.com/golang/geo/s2/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- 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.
diff --git a/vendor/github.com/golang/geo/s2/cap.go b/vendor/github.com/golang/geo/s2/cap.go
index 4f60e86..7655e60 100644
--- a/vendor/github.com/golang/geo/s2/cap.go
+++ b/vendor/github.com/golang/geo/s2/cap.go
@@ -24,17 +24,9 @@ import (
"github.com/golang/geo/s1"
)
-const (
- emptyHeight = -1.0
- zeroHeight = 0.0
- fullHeight = 2.0
-
- roundUp = 1.0 + 1.0/(1<<52)
-)
-
var (
- // centerPoint is the default center for S2Caps
- centerPoint = Point{PointFromCoords(1.0, 0, 0).Normalize()}
+ // centerPoint is the default center for Caps
+ centerPoint = PointFromCoords(1.0, 0, 0)
)
// Cap represents a disc-shaped region defined by a center and radius.
@@ -53,36 +45,49 @@ var (
// The center is a point on the surface of the unit sphere. (Hence the need for
// it to be of unit length.)
//
-// Internally, the cap is represented by its center and "height". The height
-// is the distance from the center point to the cutoff plane. This
-// representation is much more efficient for containment tests than the
-// (center, radius) representation. There is also support for "empty" and
-// "full" caps, which contain no points and all points respectively.
+// A cap can also be defined by its center point and height. The height is the
+// distance from the center point to the cutoff plane. There is also support for
+// "empty" and "full" caps, which contain no points and all points respectively.
+//
+// Here are some useful relationships between the cap height (h), the cap
+// radius (r), the maximum chord length from the cap's center (d), and the
+// radius of cap's base (a).
+//
+// h = 1 - cos(r)
+// = 2 * sin^2(r/2)
+// d^2 = 2 * h
+// = a^2 + h^2
//
// The zero value of Cap is an invalid cap. Use EmptyCap to get a valid empty cap.
type Cap struct {
center Point
- height float64
+ radius s1.ChordAngle
}
// CapFromPoint constructs a cap containing a single point.
func CapFromPoint(p Point) Cap {
- return CapFromCenterHeight(p, zeroHeight)
+ return CapFromCenterChordAngle(p, 0)
}
// CapFromCenterAngle constructs a cap with the given center and angle.
func CapFromCenterAngle(center Point, angle s1.Angle) Cap {
- return CapFromCenterHeight(center, radiusToHeight(angle))
+ return CapFromCenterChordAngle(center, s1.ChordAngleFromAngle(angle))
+}
+
+// CapFromCenterChordAngle constructs a cap where the angle is expressed as an
+// s1.ChordAngle. This constructor is more efficient than using an s1.Angle.
+func CapFromCenterChordAngle(center Point, radius s1.ChordAngle) Cap {
+ return Cap{
+ center: center,
+ radius: radius,
+ }
}
// CapFromCenterHeight constructs a cap with the given center and height. A
// negative height yields an empty cap; a height of 2 or more yields a full cap.
// The center should be unit length.
func CapFromCenterHeight(center Point, height float64) Cap {
- return Cap{
- center: center,
- height: height,
- }
+ return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(2*height))
}
// CapFromCenterArea constructs a cap with the given center and surface area.
@@ -90,33 +95,32 @@ func CapFromCenterHeight(center Point, height float64) Cap {
// cap (because the sphere has unit radius). A negative area yields an empty cap;
// an area of 4*π or more yields a full cap.
func CapFromCenterArea(center Point, area float64) Cap {
- return CapFromCenterHeight(center, area/(math.Pi*2.0))
+ return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(area/math.Pi))
}
// EmptyCap returns a cap that contains no points.
func EmptyCap() Cap {
- return CapFromCenterHeight(centerPoint, emptyHeight)
+ return CapFromCenterChordAngle(centerPoint, s1.NegativeChordAngle)
}
// FullCap returns a cap that contains all points.
func FullCap() Cap {
- return CapFromCenterHeight(centerPoint, fullHeight)
+ return CapFromCenterChordAngle(centerPoint, s1.StraightChordAngle)
}
// IsValid reports whether the Cap is considered valid.
-// Heights are normalized so that they do not exceed 2.
func (c Cap) IsValid() bool {
- return c.center.Vector.IsUnit() && c.height <= fullHeight
+ return c.center.Vector.IsUnit() && c.radius <= s1.StraightChordAngle
}
// IsEmpty reports whether the cap is empty, i.e. it contains no points.
func (c Cap) IsEmpty() bool {
- return c.height < zeroHeight
+ return c.radius < 0
}
// IsFull reports whether the cap is full, i.e. it contains all points.
func (c Cap) IsFull() bool {
- return c.height == fullHeight
+ return c.radius == s1.StraightChordAngle
}
// Center returns the cap's center point.
@@ -124,26 +128,23 @@ func (c Cap) Center() Point {
return c.center
}
-// Height returns the cap's "height".
+// Height returns the height of the cap. This is the distance from the center
+// point to the cutoff plane.
func (c Cap) Height() float64 {
- return c.height
+ return float64(0.5 * c.radius)
}
-// Radius returns the cap's radius.
+// Radius returns the cap radius as an s1.Angle. (Note that the cap angle
+// is stored internally as a ChordAngle, so this method requires a trigonometric
+// operation and may yield a slightly different result than the value passed
+// to CapFromCenterAngle).
func (c Cap) Radius() s1.Angle {
- if c.IsEmpty() {
- return s1.Angle(emptyHeight)
- }
-
- // This could also be computed as acos(1 - height_), but the following
- // formula is much more accurate when the cap height is small. It
- // follows from the relationship h = 1 - cos(r) = 2 sin^2(r/2).
- return s1.Angle(2 * math.Asin(math.Sqrt(0.5*c.height)))
+ return c.radius.Angle()
}
// Area returns the surface area of the Cap on the unit sphere.
func (c Cap) Area() float64 {
- return 2.0 * math.Pi * math.Max(zeroHeight, c.height)
+ return 2.0 * math.Pi * math.Max(0, c.Height())
}
// Contains reports whether this cap contains the other.
@@ -152,7 +153,7 @@ func (c Cap) Contains(other Cap) bool {
if c.IsFull() || other.IsEmpty() {
return true
}
- return c.Radius() >= c.center.Distance(other.center)+other.Radius()
+ return c.radius >= ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
}
// Intersects reports whether this cap intersects the other cap.
@@ -162,27 +163,27 @@ func (c Cap) Intersects(other Cap) bool {
return false
}
- return c.Radius()+other.Radius() >= c.center.Distance(other.center)
+ return c.radius.Add(other.radius) >= ChordAngleBetweenPoints(c.center, other.center)
}
// InteriorIntersects reports whether this caps interior intersects the other cap.
func (c Cap) InteriorIntersects(other Cap) bool {
// Make sure this cap has an interior and the other cap is non-empty.
- if c.height <= zeroHeight || other.IsEmpty() {
+ if c.radius <= 0 || other.IsEmpty() {
return false
}
- return c.Radius()+other.Radius() > c.center.Distance(other.center)
+ return c.radius.Add(other.radius) > ChordAngleBetweenPoints(c.center, other.center)
}
// ContainsPoint reports whether this cap contains the point.
func (c Cap) ContainsPoint(p Point) bool {
- return c.center.Sub(p.Vector).Norm2() <= 2*c.height
+ return ChordAngleBetweenPoints(c.center, p) <= c.radius
}
// InteriorContainsPoint reports whether the point is within the interior of this cap.
func (c Cap) InteriorContainsPoint(p Point) bool {
- return c.IsFull() || c.center.Sub(p.Vector).Norm2() < 2*c.height
+ return c.IsFull() || ChordAngleBetweenPoints(c.center, p) < c.radius
}
// Complement returns the complement of the interior of the cap. A cap and its
@@ -191,11 +192,14 @@ func (c Cap) InteriorContainsPoint(p Point) bool {
// singleton cap (containing a single point) is the same as the complement
// of an empty cap.
func (c Cap) Complement() Cap {
- height := emptyHeight
- if !c.IsFull() {
- height = fullHeight - math.Max(c.height, zeroHeight)
+ if c.IsFull() {
+ return EmptyCap()
}
- return CapFromCenterHeight(Point{c.center.Mul(-1.0)}, height)
+ if c.IsEmpty() {
+ return FullCap()
+ }
+
+ return CapFromCenterChordAngle(Point{c.center.Mul(-1)}, s1.StraightChordAngle.Sub(c.radius))
}
// CapBound returns a bounding spherical cap. This is not guaranteed to be exact.
@@ -241,7 +245,7 @@ func (c Cap) RectBound() Rect {
// minus the latitude). This formula also works for negative latitudes.
//
// The formula for sin(a) follows from the relationship h = 1 - cos(a).
- sinA := math.Sqrt(c.height * (2 - c.height))
+ sinA := c.radius.Sin()
sinC := math.Cos(latitude(c.center).Radians())
if sinA <= sinC {
angleA := math.Asin(sinA / sinC)
@@ -252,31 +256,41 @@ func (c Cap) RectBound() Rect {
return Rect{lat, lng}
}
-// ApproxEqual reports whether this cap's center and height are within
-// a reasonable epsilon from the other cap.
+// Equal reports whether this cap is equal to the other cap.
+func (c Cap) Equal(other Cap) bool {
+ return (c.radius == other.radius && c.center == other.center) ||
+ (c.IsEmpty() && other.IsEmpty()) ||
+ (c.IsFull() && other.IsFull())
+}
+
+// ApproxEqual reports whether this cap is equal to the other cap within the given tolerance.
func (c Cap) ApproxEqual(other Cap) bool {
- // Caps have a wider tolerance than the usual epsilon for approximately equal.
const epsilon = 1e-14
+ r2 := float64(c.radius)
+ otherR2 := float64(other.radius)
return c.center.ApproxEqual(other.center) &&
- math.Abs(c.height-other.height) <= epsilon ||
- c.IsEmpty() && other.height <= epsilon ||
- other.IsEmpty() && c.height <= epsilon ||
- c.IsFull() && other.height >= 2-epsilon ||
- other.IsFull() && c.height >= 2-epsilon
+ math.Abs(r2-otherR2) <= epsilon ||
+ c.IsEmpty() && otherR2 <= epsilon ||
+ other.IsEmpty() && r2 <= epsilon ||
+ c.IsFull() && otherR2 >= 2-epsilon ||
+ other.IsFull() && r2 >= 2-epsilon
}
// AddPoint increases the cap if necessary to include the given point. If this cap is empty,
// then the center is set to the point with a zero height. p must be unit-length.
func (c Cap) AddPoint(p Point) Cap {
if c.IsEmpty() {
- return Cap{center: p}
+ c.center = p
+ c.radius = 0
+ return c
}
- // To make sure that the resulting cap actually includes this point,
- // we need to round up the distance calculation. That is, after
- // calling cap.AddPoint(p), cap.Contains(p) should be true.
- dist2 := c.center.Sub(p.Vector).Norm2()
- c.height = math.Max(c.height, roundUp*0.5*dist2)
+ // After calling cap.AddPoint(p), cap.Contains(p) must be true. However
+ // we don't need to do anything special to achieve this because Contains()
+ // does exactly the same distance calculation that we do here.
+ if newRad := ChordAngleBetweenPoints(c.center, p); newRad > c.radius {
+ c.radius = newRad
+ }
return c
}
@@ -290,8 +304,12 @@ func (c Cap) AddCap(other Cap) Cap {
return c
}
- radius := c.center.Angle(other.center.Vector) + other.Radius()
- c.height = math.Max(c.height, roundUp*radiusToHeight(radius))
+ // We round up the distance to ensure that the cap is actually contained.
+ // TODO(roberts): Do some error analysis in order to guarantee this.
+ dist := ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
+ if newRad := dist.Expanded(dblEpsilon * float64(dist)); newRad > c.radius {
+ c.radius = newRad
+ }
return c
}
@@ -301,7 +319,7 @@ func (c Cap) Expanded(distance s1.Angle) Cap {
if c.IsEmpty() {
return EmptyCap()
}
- return CapFromCenterAngle(c.center, c.Radius()+distance)
+ return CapFromCenterChordAngle(c.center, c.radius.Add(s1.ChordAngleFromAngle(distance)))
}
func (c Cap) String() string {
@@ -311,16 +329,12 @@ func (c Cap) String() string {
// radiusToHeight converts an s1.Angle into the height of the cap.
func radiusToHeight(r s1.Angle) float64 {
if r.Radians() < 0 {
- return emptyHeight
+ return float64(s1.NegativeChordAngle)
}
if r.Radians() >= math.Pi {
- return fullHeight
+ return float64(s1.RightChordAngle)
}
- // The height of the cap can be computed as 1 - cos(r), but this isn't very
- // accurate for angles close to zero (where cos(r) is almost 1). The
- // formula below has much better precision.
- d := math.Sin(0.5 * r.Radians())
- return 2 * d * d
+ return float64(0.5 * s1.ChordAngleFromAngle(r))
}
@@ -357,7 +371,7 @@ func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
// If the cap is a hemisphere or larger, the cell and the complement of the cap
// are both convex. Therefore since no vertex of the cell is contained, no other
// interior point of the cell is contained either.
- if c.height >= 1 {
+ if c.radius >= s1.RightChordAngle {
return false
}
@@ -375,7 +389,7 @@ func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
// At this point we know that the cell does not contain the cap center, and the cap
// does not contain any cell vertex. The only way that they can intersect is if the
// cap intersects the interior of some edge.
- sin2Angle := c.height * (2 - c.height)
+ sin2Angle := c.radius.Sin2()
for k := 0; k < 4; k++ {
edge := cell.Edge(k).Vector
dot := c.center.Vector.Dot(edge)
@@ -402,5 +416,53 @@ func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
return false
}
-// TODO(roberts): Differences from C++
-// Centroid, Union
+// Centroid returns the true centroid of the cap multiplied by its surface area
+// The result lies on the ray from the origin through the cap's center, but it
+// is not unit length. Note that if you just want the "surface centroid", i.e.
+// the normalized result, then it is simpler to call Center.
+//
+// The reason for multiplying the result by the cap area is to make it
+// easier to compute the centroid of more complicated shapes. The centroid
+// of a union of disjoint regions can be computed simply by adding their
+// Centroid() results. Caveat: for caps that contain a single point
+// (i.e., zero radius), this method always returns the origin (0, 0, 0).
+// This is because shapes with no area don't affect the centroid of a
+// union whose total area is positive.
+func (c Cap) Centroid() Point {
+ // From symmetry, the centroid of the cap must be somewhere on the line
+ // from the origin to the center of the cap on the surface of the sphere.
+ // When a sphere is divided into slices of constant thickness by a set of
+ // parallel planes, all slices have the same surface area. This implies
+ // that the radial component of the centroid is simply the midpoint of the
+ // range of radial distances spanned by the cap. That is easily computed
+ // from the cap height.
+ if c.IsEmpty() {
+ return Point{}
+ }
+ r := 1 - 0.5*c.Height()
+ return Point{c.center.Mul(r * c.Area())}
+}
+
+// Union returns the smallest cap which encloses this cap and other.
+func (c Cap) Union(other Cap) Cap {
+ // If the other cap is larger, swap c and other for the rest of the computations.
+ if c.radius < other.radius {
+ c, other = other, c
+ }
+
+ if c.IsFull() || other.IsEmpty() {
+ return c
+ }
+
+ // TODO: This calculation would be more efficient using s1.ChordAngles.
+ cRadius := c.Radius()
+ otherRadius := other.Radius()
+ distance := c.center.Distance(other.center)
+ if cRadius >= distance+otherRadius {
+ return c
+ }
+
+ resRadius := 0.5 * (distance + cRadius + otherRadius)
+ resCenter := InterpolateAtDistance(0.5*(distance-cRadius+otherRadius), c.center, other.center)
+ return CapFromCenterAngle(resCenter, resRadius)
+}
diff --git a/vendor/github.com/golang/geo/s2/cap_test.go b/vendor/github.com/golang/geo/s2/cap_test.go
new file mode 100644
index 0000000..a8ab810
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/cap_test.go
@@ -0,0 +1,718 @@
+/*
+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"
+)
+
+const (
+ tinyRad = 1e-10
+)
+
+var (
+ emptyCap = EmptyCap()
+ fullCap = FullCap()
+ defaultCap = EmptyCap()
+
+ zeroHeight = 0.0
+ fullHeight = 2.0
+ emptyHeight = -1.0
+
+ xAxisPt = Point{r3.Vector{1, 0, 0}}
+ yAxisPt = Point{r3.Vector{0, 1, 0}}
+
+ xAxis = CapFromPoint(xAxisPt)
+ yAxis = CapFromPoint(yAxisPt)
+ xComp = xAxis.Complement()
+
+ hemi = CapFromCenterHeight(PointFromCoords(1, 0, 1), 1)
+ concave = CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(80, 10)), s1.Angle(150.0)*s1.Degree)
+ tiny = CapFromCenterAngle(PointFromCoords(1, 2, 3), s1.Angle(tinyRad))
+)
+
+func TestCapBasicEmptyFullValid(t *testing.T) {
+ tests := []struct {
+ got Cap
+ empty, full, valid bool
+ }{
+ {Cap{}, false, false, false},
+
+ {emptyCap, true, false, true},
+ {emptyCap.Complement(), false, true, true},
+ {fullCap, false, true, true},
+ {fullCap.Complement(), true, false, true},
+ {defaultCap, true, false, true},
+
+ {xComp, false, true, true},
+ {xComp.Complement(), true, false, true},
+
+ {tiny, false, false, true},
+ {concave, false, false, true},
+ {hemi, false, false, true},
+ {tiny, false, false, true},
+ }
+ for _, test := range tests {
+ if e := test.got.IsEmpty(); e != test.empty {
+ t.Errorf("%v.IsEmpty() = %t; want %t", test.got, e, test.empty)
+ }
+ if f := test.got.IsFull(); f != test.full {
+ t.Errorf("%v.IsFull() = %t; want %t", test.got, f, test.full)
+ }
+ if v := test.got.IsValid(); v != test.valid {
+ t.Errorf("%v.IsValid() = %t; want %t", test.got, v, test.valid)
+ }
+ }
+}
+
+func TestCapCenterHeightRadius(t *testing.T) {
+ if xAxis == xAxis.Complement().Complement() {
+ t.Errorf("the complement of the complement is not the original. %v == %v",
+ xAxis, xAxis.Complement().Complement())
+ }
+
+ if fullCap.Height() != fullHeight {
+ t.Error("full Caps should be full height")
+ }
+ if fullCap.Radius().Degrees() != 180.0 {
+ t.Error("radius of x-axis cap should be 180 degrees")
+ }
+
+ if emptyCap.center != defaultCap.center {
+ t.Error("empty Caps should be have the same center as the default")
+ }
+ if emptyCap.Height() != defaultCap.Height() {
+ t.Error("empty Caps should be have the same height as the default")
+ }
+
+ if yAxis.Height() != zeroHeight {
+ t.Error("y-axis cap should not be empty height")
+ }
+
+ if xAxis.Height() != zeroHeight {
+ t.Error("x-axis cap should not be empty height")
+ }
+ if xAxis.Radius().Radians() != zeroHeight {
+ t.Errorf("radius of x-axis cap got %f want %f", xAxis.Radius().Radians(), emptyHeight)
+ }
+
+ hc := Point{hemi.center.Mul(-1.0)}
+ if hc != hemi.Complement().center {
+ t.Error("hemi center and its complement should have the same center")
+ }
+ if hemi.Height() != 1.0 {
+ t.Error("hemi cap should be 1.0 in height")
+ }
+}
+
+func TestCapContains(t *testing.T) {
+ tests := []struct {
+ c1, c2 Cap
+ want bool
+ }{
+ {emptyCap, emptyCap, true},
+ {fullCap, emptyCap, true},
+ {fullCap, fullCap, true},
+ {emptyCap, xAxis, false},
+ {fullCap, xAxis, true},
+ {xAxis, fullCap, false},
+ {xAxis, xAxis, true},
+ {xAxis, emptyCap, true},
+ {hemi, tiny, true},
+ {hemi, CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/4-epsilon)), true},
+ {hemi, CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/4+epsilon)), false},
+ {concave, hemi, true},
+ {concave, CapFromCenterHeight(Point{concave.center.Mul(-1.0)}, 0.1), false},
+ }
+ for _, test := range tests {
+ if got := test.c1.Contains(test.c2); got != test.want {
+ t.Errorf("%v.Contains(%v) = %t; want %t", test.c1, test.c2, got, test.want)
+ }
+ }
+}
+
+func TestCapContainsPoint(t *testing.T) {
+ // We don't use the standard epsilon in this test due different compiler
+ // math optimizations that are permissible (FMA vs no FMA) that yield
+ // slightly different floating point results between gccgo and gc.
+ const epsilon = 1e-14
+ tangent := tiny.center.Cross(r3.Vector{3, 2, 1}).Normalize()
+ tests := []struct {
+ c Cap
+ p Point
+ want bool
+ }{
+ {xAxis, xAxisPt, true},
+ {xAxis, Point{r3.Vector{1, 1e-20, 0}}, false},
+ {yAxis, xAxis.center, false},
+ {xComp, xAxis.center, true},
+ {xComp.Complement(), xAxis.center, false},
+ {tiny, Point{tiny.center.Add(tangent.Mul(tinyRad * 0.99))}, true},
+ {tiny, Point{tiny.center.Add(tangent.Mul(tinyRad * 1.01))}, false},
+ {hemi, PointFromCoords(1, 0, -(1 - epsilon)), true},
+ {hemi, xAxisPt, true},
+ {hemi.Complement(), xAxisPt, false},
+ {concave, PointFromLatLng(LatLngFromDegrees(-70*(1-epsilon), 10)), true},
+ {concave, PointFromLatLng(LatLngFromDegrees(-70*(1+epsilon), 10)), false},
+ // This test case is the one where the floating point values end up
+ // different in the 15th place and beyond.
+ {concave, PointFromLatLng(LatLngFromDegrees(-50*(1-epsilon), -170)), true},
+ {concave, PointFromLatLng(LatLngFromDegrees(-50*(1+epsilon), -170)), false},
+ }
+ for _, test := range tests {
+ if got := test.c.ContainsPoint(test.p); got != test.want {
+ t.Errorf("%v.ContainsPoint(%v) = %t, want %t", test.c, test.p, got, test.want)
+ }
+ }
+}
+
+func TestCapInteriorIntersects(t *testing.T) {
+ tests := []struct {
+ c1, c2 Cap
+ want bool
+ }{
+ {emptyCap, emptyCap, false},
+ {emptyCap, xAxis, false},
+ {fullCap, emptyCap, false},
+ {fullCap, fullCap, true},
+ {fullCap, xAxis, true},
+ {xAxis, fullCap, false},
+ {xAxis, xAxis, false},
+ {xAxis, emptyCap, false},
+ {concave, hemi.Complement(), true},
+ }
+ for _, test := range tests {
+ if got := test.c1.InteriorIntersects(test.c2); got != test.want {
+ t.Errorf("%v.InteriorIntersects(%v); got %t want %t", test.c1, test.c2, got, test.want)
+ }
+ }
+}
+
+func TestCapInteriorContains(t *testing.T) {
+ if hemi.InteriorContainsPoint(Point{r3.Vector{1, 0, -(1 + epsilon)}}) {
+ t.Errorf("hemi (%v) should not contain point just past half way(%v)", hemi,
+ Point{r3.Vector{1, 0, -(1 + epsilon)}})
+ }
+}
+
+func TestCapExpanded(t *testing.T) {
+ cap50 := CapFromCenterAngle(xAxisPt, 50.0*s1.Degree)
+ cap51 := CapFromCenterAngle(xAxisPt, 51.0*s1.Degree)
+
+ if !emptyCap.Expanded(s1.Angle(fullHeight)).IsEmpty() {
+ t.Error("Expanding empty cap should return an empty cap")
+ }
+ if !fullCap.Expanded(s1.Angle(fullHeight)).IsFull() {
+ t.Error("Expanding a full cap should return an full cap")
+ }
+
+ if !cap50.Expanded(0).ApproxEqual(cap50) {
+ t.Error("Expanding a cap by 0° should be equal to the original")
+ }
+ if !cap50.Expanded(1 * s1.Degree).ApproxEqual(cap51) {
+ t.Error("Expanding 50° by 1° should equal the 51° cap")
+ }
+
+ if cap50.Expanded(129.99 * s1.Degree).IsFull() {
+ t.Error("Expanding 50° by 129.99° should not give a full cap")
+ }
+ if !cap50.Expanded(130.01 * s1.Degree).IsFull() {
+ t.Error("Expanding 50° by 130.01° should give a full cap")
+ }
+}
+
+func TestCapRadiusToHeight(t *testing.T) {
+ tests := []struct {
+ got s1.Angle
+ want float64
+ }{
+ // Above/below boundary checks.
+ {s1.Angle(-0.5), emptyHeight},
+ {s1.Angle(0), 0},
+ {s1.Angle(math.Pi), fullHeight},
+ {s1.Angle(2 * math.Pi), fullHeight},
+ // Degree tests.
+ {-7.0 * s1.Degree, emptyHeight},
+ {-0.0 * s1.Degree, 0},
+ {0.0 * s1.Degree, 0},
+ {12.0 * s1.Degree, 0.0218523992661943},
+ {30.0 * s1.Degree, 0.1339745962155613},
+ {45.0 * s1.Degree, 0.2928932188134525},
+ {90.0 * s1.Degree, 1.0},
+ {179.99 * s1.Degree, 1.9999999847691292},
+ {180.0 * s1.Degree, fullHeight},
+ {270.0 * s1.Degree, fullHeight},
+ // Radians tests.
+ {-1.0 * s1.Radian, emptyHeight},
+ {-0.0 * s1.Radian, 0},
+ {0.0 * s1.Radian, 0},
+ {1.0 * s1.Radian, 0.45969769413186},
+ {math.Pi / 2.0 * s1.Radian, 1.0},
+ {2.0 * s1.Radian, 1.4161468365471424},
+ {3.0 * s1.Radian, 1.9899924966004454},
+ {math.Pi * s1.Radian, fullHeight},
+ {4.0 * s1.Radian, fullHeight},
+ }
+ for _, test := range tests {
+ // float64Eq comes from s2latlng_test.go
+ if got := radiusToHeight(test.got); !float64Eq(got, test.want) {
+ t.Errorf("radiusToHeight(%v) = %v; want %v", test.got, got, test.want)
+ }
+ }
+}
+
+func TestCapRectBounds(t *testing.T) {
+ const epsilon = 1e-13
+ var tests = []struct {
+ desc string
+ have Cap
+ latLoDeg float64
+ latHiDeg float64
+ lngLoDeg float64
+ lngHiDeg float64
+ isFull bool
+ }{
+ {
+ "Cap that includes South Pole.",
+ CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(-45, 57)), s1.Degree*50),
+ -90, 5, -180, 180, true,
+ },
+ {
+ "Cap that is tangent to the North Pole.",
+ CapFromCenterAngle(PointFromCoords(1, 0, 1), s1.Radian*(math.Pi/4.0+1e-16)),
+ 0, 90, -180, 180, true,
+ },
+ {
+ "Cap that at 45 degree center that goes from equator to the pole.",
+ CapFromCenterAngle(PointFromCoords(1, 0, 1), s1.Degree*(45+5e-15)),
+ 0, 90, -180, 180, true,
+ },
+ {
+ "The eastern hemisphere.",
+ CapFromCenterAngle(Point{r3.Vector{0, 1, 0}}, s1.Radian*(math.Pi/2+2e-16)),
+ -90, 90, -180, 180, true,
+ },
+ {
+ "A cap centered on the equator.",
+ CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(0, 50)), s1.Degree*20),
+ -20, 20, 30, 70, false,
+ },
+ {
+ "A cap centered on the North Pole.",
+ CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(90, 123)), s1.Degree*10),
+ 80, 90, -180, 180, true,
+ },
+ }
+
+ for _, test := range tests {
+ r := test.have.RectBound()
+ if !float64Near(s1.Angle(r.Lat.Lo).Degrees(), test.latLoDeg, epsilon) {
+ t.Errorf("%s: %v.RectBound(), Lat.Lo not close enough, got %0.20f, want %0.20f",
+ test.desc, test.have, s1.Angle(r.Lat.Lo).Degrees(), test.latLoDeg)
+ }
+ if !float64Near(s1.Angle(r.Lat.Hi).Degrees(), test.latHiDeg, epsilon) {
+ t.Errorf("%s: %v.RectBound(), Lat.Hi not close enough, got %0.20f, want %0.20f",
+ test.desc, test.have, s1.Angle(r.Lat.Hi).Degrees(), test.latHiDeg)
+ }
+ if !float64Near(s1.Angle(r.Lng.Lo).Degrees(), test.lngLoDeg, epsilon) {
+ t.Errorf("%s: %v.RectBound(), Lng.Lo not close enough, got %0.20f, want %0.20f",
+ test.desc, test.have, s1.Angle(r.Lng.Lo).Degrees(), test.lngLoDeg)
+ }
+ if !float64Near(s1.Angle(r.Lng.Hi).Degrees(), test.lngHiDeg, epsilon) {
+ t.Errorf("%s: %v.RectBound(), Lng.Hi not close enough, got %0.20f, want %0.20f",
+ test.desc, test.have, s1.Angle(r.Lng.Hi).Degrees(), test.lngHiDeg)
+ }
+ if got := r.Lng.IsFull(); got != test.isFull {
+ t.Errorf("%s: RectBound(%v).isFull() = %t, want %t", test.desc, test.have, got, test.isFull)
+ }
+ }
+
+ // Empty and full caps.
+ if !EmptyCap().RectBound().IsEmpty() {
+ t.Errorf("RectBound() on EmptyCap should be empty.")
+ }
+
+ if !FullCap().RectBound().IsFull() {
+ t.Errorf("RectBound() on FullCap should be full.")
+ }
+}
+
+func TestCapAddPoint(t *testing.T) {
+ const epsilon = 1e-14
+ tests := []struct {
+ have Cap
+ p Point
+ want Cap
+ }{
+ // Cap plus its center equals itself.
+ {xAxis, xAxisPt, xAxis},
+ {yAxis, yAxisPt, yAxis},
+
+ // Cap plus opposite point equals full.
+ {xAxis, Point{r3.Vector{-1, 0, 0}}, fullCap},
+ {yAxis, Point{r3.Vector{0, -1, 0}}, fullCap},
+
+ // Cap plus orthogonal axis equals half cap.
+ {xAxis, Point{r3.Vector{0, 0, 1}}, CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/2.0))},
+ {xAxis, Point{r3.Vector{0, 0, -1}}, CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/2.0))},
+
+ // The 45 degree angled hemisphere plus some points.
+ {
+ hemi,
+ PointFromCoords(0, 1, -1),
+ CapFromCenterAngle(Point{r3.Vector{1, 0, 1}},
+ s1.Angle(120.0)*s1.Degree),
+ },
+ {
+ hemi,
+ PointFromCoords(0, -1, -1),
+ CapFromCenterAngle(Point{r3.Vector{1, 0, 1}},
+ s1.Angle(120.0)*s1.Degree),
+ },
+ {
+ hemi,
+ PointFromCoords(-1, -1, -1),
+ CapFromCenterAngle(Point{r3.Vector{1, 0, 1}},
+ s1.Angle(math.Acos(-math.Sqrt(2.0/3.0)))),
+ },
+ {hemi, Point{r3.Vector{0, 1, 1}}, hemi},
+ {hemi, Point{r3.Vector{1, 0, 0}}, hemi},
+ }
+
+ for _, test := range tests {
+ got := test.have.AddPoint(test.p)
+ if !got.ApproxEqual(test.want) {
+ t.Errorf("%v.AddPoint(%v) = %v, want %v", test.have, test.p, got, test.want)
+ }
+
+ if !got.ContainsPoint(test.p) {
+ t.Errorf("%v.AddPoint(%v) did not contain added point", test.have, test.p)
+ }
+ }
+}
+
+func TestCapAddCap(t *testing.T) {
+ tests := []struct {
+ have Cap
+ other Cap
+ want Cap
+ }{
+ // Identity cases.
+ {emptyCap, emptyCap, emptyCap},
+ {fullCap, fullCap, fullCap},
+
+ // Anything plus empty equals itself.
+ {fullCap, emptyCap, fullCap},
+ {emptyCap, fullCap, fullCap},
+ {xAxis, emptyCap, xAxis},
+ {emptyCap, xAxis, xAxis},
+ {yAxis, emptyCap, yAxis},
+ {emptyCap, yAxis, yAxis},
+
+ // Two halves make a whole.
+ {xAxis, xComp, fullCap},
+
+ // Two zero-height orthogonal axis caps make a half-cap.
+ {xAxis, yAxis, CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/2.0))},
+ }
+
+ for _, test := range tests {
+ got := test.have.AddCap(test.other)
+ if !got.ApproxEqual(test.want) {
+ t.Errorf("%v.AddCap(%v) = %v, want %v", test.have, test.other, got, test.want)
+ }
+ }
+}
+
+func TestCapContainsCell(t *testing.T) {
+ faceRadius := math.Atan(math.Sqrt2)
+ for face := 0; face < 6; face++ {
+ // The cell consisting of the entire face.
+ rootCell := CellFromCellID(CellIDFromFace(face))
+
+ // A leaf cell at the midpoint of the v=1 edge.
+ edgeCell := CellFromPoint(Point{faceUVToXYZ(face, 0, 1-epsilon)})
+
+ // A leaf cell at the u=1, v=1 corner
+ cornerCell := CellFromPoint(Point{faceUVToXYZ(face, 1-epsilon, 1-epsilon)})
+
+ // Quick check for full and empty caps.
+ if !fullCap.ContainsCell(rootCell) {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", fullCap, rootCell, false, true)
+ }
+
+ // Check intersections with the bounding caps of the leaf cells that are adjacent to
+ // cornerCell along the Hilbert curve. Because this corner is at (u=1,v=1), the curve
+ // stays locally within the same cube face.
+ first := cornerCell.id.Advance(-3)
+ last := cornerCell.id.Advance(4)
+ for id := first; id < last; id = id.Next() {
+ c := CellFromCellID(id).CapBound()
+ if got, want := c.ContainsCell(cornerCell), id == cornerCell.id; got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", c, cornerCell, got, want)
+ }
+ }
+
+ for capFace := 0; capFace < 6; capFace++ {
+ // A cap that barely contains all of capFace.
+ center := unitNorm(capFace)
+ covering := CapFromCenterAngle(center, s1.Angle(faceRadius+epsilon))
+ if got, want := covering.ContainsCell(rootCell), capFace == face; got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", covering, rootCell, got, want)
+ }
+ if got, want := covering.ContainsCell(edgeCell), center.Vector.Dot(edgeCell.id.Point().Vector) > 0.1; got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", covering, edgeCell, got, want)
+ }
+ if got, want := covering.ContainsCell(edgeCell), covering.IntersectsCell(edgeCell); got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", covering, edgeCell, got, want)
+ }
+ if got, want := covering.ContainsCell(cornerCell), capFace == face; got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", covering, cornerCell, got, want)
+ }
+
+ // A cap that barely intersects the edges of capFace.
+ bulging := CapFromCenterAngle(center, s1.Angle(math.Pi/4+epsilon))
+ if bulging.ContainsCell(rootCell) {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", bulging, rootCell, true, false)
+ }
+ if got, want := bulging.ContainsCell(edgeCell), capFace == face; got != want {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", bulging, edgeCell, got, want)
+ }
+ if bulging.ContainsCell(cornerCell) {
+ t.Errorf("Cap(%v).ContainsCell(%v) = %t; want = %t", bulging, cornerCell, true, false)
+ }
+ }
+ }
+}
+
+func TestCapIntersectsCell(t *testing.T) {
+ faceRadius := math.Atan(math.Sqrt2)
+ for face := 0; face < 6; face++ {
+ // The cell consisting of the entire face.
+ rootCell := CellFromCellID(CellIDFromFace(face))
+
+ // A leaf cell at the midpoint of the v=1 edge.
+ edgeCell := CellFromPoint(Point{faceUVToXYZ(face, 0, 1-epsilon)})
+
+ // A leaf cell at the u=1, v=1 corner
+ cornerCell := CellFromPoint(Point{faceUVToXYZ(face, 1-epsilon, 1-epsilon)})
+
+ // Quick check for full and empty caps.
+ if emptyCap.IntersectsCell(rootCell) {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", emptyCap, rootCell, true, false)
+ }
+
+ // Check intersections with the bounding caps of the leaf cells that are adjacent to
+ // cornerCell along the Hilbert curve. Because this corner is at (u=1,v=1), the curve
+ // stays locally within the same cube face.
+ first := cornerCell.id.Advance(-3)
+ last := cornerCell.id.Advance(4)
+ for id := first; id < last; id = id.Next() {
+ c := CellFromCellID(id).CapBound()
+ if got, want := c.IntersectsCell(cornerCell), id.immediateParent().Contains(cornerCell.id); got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", c, cornerCell, got, want)
+ }
+ }
+
+ antiFace := (face + 3) % 6
+ for capFace := 0; capFace < 6; capFace++ {
+ // A cap that barely contains all of capFace.
+ center := unitNorm(capFace)
+ covering := CapFromCenterAngle(center, s1.Angle(faceRadius+epsilon))
+ if got, want := covering.IntersectsCell(rootCell), capFace != antiFace; got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", covering, rootCell, got, want)
+ }
+ if got, want := covering.IntersectsCell(edgeCell), covering.ContainsCell(edgeCell); got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", covering, edgeCell, got, want)
+ }
+ if got, want := covering.IntersectsCell(cornerCell), center.Vector.Dot(cornerCell.id.Point().Vector) > 0; got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", covering, cornerCell, got, want)
+ }
+
+ // A cap that barely intersects the edges of capFace.
+ bulging := CapFromCenterAngle(center, s1.Angle(math.Pi/4+epsilon))
+ if got, want := bulging.IntersectsCell(rootCell), capFace != antiFace; got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", bulging, rootCell, got, want)
+ }
+ if got, want := bulging.IntersectsCell(edgeCell), center.Vector.Dot(edgeCell.id.Point().Vector) > 0.1; got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", bulging, edgeCell, got, want)
+ }
+ if bulging.IntersectsCell(cornerCell) {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", bulging, cornerCell, true, false)
+ }
+
+ // A singleton cap.
+ singleton := CapFromCenterAngle(center, 0)
+ if got, want := singleton.IntersectsCell(rootCell), capFace == face; got != want {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", singleton, rootCell, got, want)
+ }
+ if singleton.IntersectsCell(edgeCell) {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", singleton, edgeCell, true, false)
+ }
+ if singleton.IntersectsCell(cornerCell) {
+ t.Errorf("Cap(%v).IntersectsCell(%v) = %t; want = %t", singleton, cornerCell, true, false)
+ }
+ }
+ }
+}
+
+func TestCapCentroid(t *testing.T) {
+ // Empty and full caps.
+ if got, want := EmptyCap().Centroid(), (Point{}); !got.ApproxEqual(want) {
+ t.Errorf("Centroid of EmptyCap should be zero point, got %v", want)
+ }
+ if got, want := FullCap().Centroid().Norm(), 1e-15; got > want {
+ t.Errorf("Centroid of FullCap should have a Norm of 0, got %v", want)
+ }
+
+ // Random caps.
+ for i := 0; i < 100; i++ {
+ center := randomPoint()
+ height := randomUniformFloat64(0.0, 2.0)
+ c := CapFromCenterHeight(center, height)
+ got := c.Centroid()
+ want := center.Mul((1.0 - height/2.0) * c.Area())
+ if delta := got.Sub(want).Norm(); delta > 1e-15 {
+ t.Errorf("%v.Sub(%v).Norm() = %v, want %v", got, want, delta, 1e-15)
+ }
+ }
+}
+
+func TestCapUnion(t *testing.T) {
+ // Two caps which have the same center but one has a larger radius.
+ a := CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(50.0, 10.0)), s1.Degree*0.2)
+ b := CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(50.0, 10.0)), s1.Degree*0.3)
+ if !b.Contains(a) {
+ t.Errorf("%v.Contains(%v) = false, want true", b, a)
+ }
+ if got := b.ApproxEqual(a.Union(b)); !got {
+ t.Errorf("%v.ApproxEqual(%v) = %v, want true", b, a.Union(b), got)
+ }
+
+ // Two caps where one is the full cap.
+ if got := a.Union(FullCap()); !got.IsFull() {
+ t.Errorf("%v.Union(%v).IsFull() = %v, want true", a, got, got.IsFull())
+ }
+
+ // Two caps where one is the empty cap.
+ if got := a.Union(EmptyCap()); !a.ApproxEqual(got) {
+ t.Errorf("%v.Union(EmptyCap) = %v, want %v", a, got, a)
+ }
+
+ // Two caps which have different centers, one entirely encompasses the other.
+ c := CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(51.0, 11.0)), s1.Degree*1.5)
+ if !c.Contains(a) {
+ t.Errorf("%v.Contains(%v) = false, want true", c, a)
+ }
+ if got := a.Union(c).center; !got.ApproxEqual(c.center) {
+ t.Errorf("%v.Union(%v).center = %v, want %v", a, c, got, c.center)
+ }
+ if got := a.Union(c); !float64Eq(float64(got.Radius()), float64(c.Radius())) {
+ t.Errorf("%v.Union(%v).Radius = %v, want %v", a, c, got.Radius(), c.Radius())
+ }
+
+ // Two entirely disjoint caps.
+ d := CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(51.0, 11.0)), s1.Degree*0.1)
+ if d.Contains(a) {
+ t.Errorf("%v.Contains(%v) = true, want false", d, a)
+ }
+ if d.Intersects(a) {
+ t.Errorf("%v.Intersects(%v) = true, want false", d, a)
+ }
+
+ // Check union and reverse direction are the same.
+ aUnionD := a.Union(d)
+ if !aUnionD.ApproxEqual(d.Union(a)) {
+ t.Errorf("%v.Union(%v).ApproxEqual(%v.Union(%v)) = false, want true", a, d, d, a)
+ }
+ if got, want := LatLngFromPoint(aUnionD.center).Lat.Degrees(), 50.4588; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Center.Lat = %v, want %v", aUnionD, got, want)
+ }
+ if got, want := LatLngFromPoint(aUnionD.center).Lng.Degrees(), 10.4525; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Center.Lng = %v, want %v", aUnionD, got, want)
+ }
+ if got, want := aUnionD.Radius().Degrees(), 0.7425; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Radius = %v, want %v", aUnionD, got, want)
+ }
+
+ // Two partially overlapping caps.
+ e := CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(50.3, 10.3)), s1.Degree*0.2)
+ aUnionE := a.Union(e)
+ if e.Contains(a) {
+ t.Errorf("%v.Contains(%v) = false, want true", e, a)
+ }
+ if !e.Intersects(a) {
+ t.Errorf("%v.Intersects(%v) = false, want true", e, a)
+ }
+ if !aUnionE.ApproxEqual(e.Union(a)) {
+ t.Errorf("%v.Union(%v).ApproxEqual(%v.Union(%v)) = false, want true", a, e, e, a)
+ }
+ if got, want := LatLngFromPoint(aUnionE.center).Lat.Degrees(), 50.1500; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Center.Lat = %v, want %v", aUnionE, got, want)
+ }
+ if got, want := LatLngFromPoint(aUnionE.center).Lng.Degrees(), 10.1495; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Center.Lng = %v, want %v", aUnionE, got, want)
+ }
+ if got, want := aUnionE.Radius().Degrees(), 0.3781; !float64Near(got, want, 0.001) {
+ t.Errorf("%v.Radius = %v, want %v", aUnionE, got, want)
+ }
+
+ p1 := Point{r3.Vector{0, 0, 1}}
+ p2 := Point{r3.Vector{0, 1, 0}}
+ // Two very large caps, whose radius sums to in excess of 180 degrees, and
+ // whose centers are not antipodal.
+ f := CapFromCenterAngle(p1, s1.Degree*150)
+ g := CapFromCenterAngle(p2, s1.Degree*150)
+ if !f.Union(g).IsFull() {
+ t.Errorf("%v.Union(%v).IsFull() = false, want true", f, g)
+ }
+
+ // Two non-overlapping hemisphere caps with antipodal centers.
+ hemi := CapFromCenterHeight(p1, 1)
+ if !hemi.Union(hemi.Complement()).IsFull() {
+ t.Errorf("%v.Union(%v).Complement().IsFull() = false, want true", hemi, hemi.Complement())
+ }
+}
+
+func TestCapEqual(t *testing.T) {
+ tests := []struct {
+ a, b Cap
+ want bool
+ }{
+ {EmptyCap(), EmptyCap(), true},
+ {EmptyCap(), FullCap(), false},
+ {FullCap(), FullCap(), true},
+ {
+ CapFromCenterAngle(PointFromCoords(0, 0, 1), s1.Degree*150),
+ CapFromCenterAngle(PointFromCoords(0, 0, 1), s1.Degree*151),
+ false,
+ },
+ {xAxis, xAxis, true},
+ {xAxis, yAxis, false},
+ {xComp, xAxis.Complement(), true},
+ }
+
+ for _, test := range tests {
+ if got := test.a.Equal(test.b); got != test.want {
+ t.Errorf("%v.Equal(%v) = %t, want %t", test.a, test.b, got, test.want)
+ }
+ }
+}
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)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/cellid.go b/vendor/github.com/golang/geo/s2/cellid.go
index fdb2954..df3e306 100644
--- a/vendor/github.com/golang/geo/s2/cellid.go
+++ b/vendor/github.com/golang/geo/s2/cellid.go
@@ -26,6 +26,7 @@ import (
"github.com/golang/geo/r1"
"github.com/golang/geo/r2"
"github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
)
// CellID uniquely identifies a cell in the S2 cell decomposition.
@@ -34,13 +35,34 @@ import (
// along the Hilbert curve on that face. The zero value and the value
// (1<<64)-1 are invalid cell IDs. The first compares less than any
// valid cell ID, the second as greater than any valid cell ID.
+//
+// Sequentially increasing cell IDs follow a continuous space-filling curve
+// over the entire sphere. They have the following properties:
+//
+// - The ID of a cell at level k consists of a 3-bit face number followed
+// by k bit pairs that recursively select one of the four children of
+// each cell. The next bit is always 1, and all other bits are 0.
+// Therefore, the level of a cell is determined by the position of its
+// lowest-numbered bit that is turned on (for a cell at level k, this
+// position is 2 * (maxLevel - k)).
+//
+// - The ID of a parent cell is at the midpoint of the range of IDs spanned
+// by its children (or by its descendants at any level).
+//
+// Leaf cells are often used to represent points on the unit sphere, and
+// this type provides methods for converting directly between these two
+// representations. For cells that represent 2D regions rather than
+// discrete point, it is better to use Cells.
type CellID uint64
// TODO(dsymonds): Some of these constants should probably be exported.
const (
- faceBits = 3
- numFaces = 6
- maxLevel = 30
+ faceBits = 3
+ numFaces = 6
+ maxLevel = 30
+ // The extra position bit (61 rather than 60) lets us encode each cell as its
+ // Hilbert curve position at the cell center (which is halfway along the
+ // portion of the Hilbert curve that fills that cell).
posBits = 2*maxLevel + 1
maxSize = 1 << maxLevel
wrapOffset = uint64(numFaces) << posBits
@@ -211,6 +233,59 @@ func (ci CellID) VertexNeighbors(level int) []CellID {
return results
}
+// AllNeighbors returns all neighbors of this cell at the given level. Two
+// cells X and Y are neighbors if their boundaries intersect but their
+// interiors do not. In particular, two cells that intersect at a single
+// point are neighbors. Note that for cells adjacent to a face vertex, the
+// same neighbor may be returned more than once. There could be up to eight
+// neighbors including the diagonal ones that share the vertex.
+//
+// This requires level >= ci.Level().
+func (ci CellID) AllNeighbors(level int) []CellID {
+ var neighbors []CellID
+
+ face, i, j, _ := ci.faceIJOrientation()
+
+ // Find the coordinates of the lower left-hand leaf cell. We need to
+ // normalize (i,j) to a known position within the cell because level
+ // may be larger than this cell's level.
+ size := sizeIJ(ci.Level())
+ i &= -size
+ j &= -size
+
+ nbrSize := sizeIJ(level)
+
+ // We compute the top-bottom, left-right, and diagonal neighbors in one
+ // pass. The loop test is at the end of the loop to avoid 32-bit overflow.
+ for k := -nbrSize; ; k += nbrSize {
+ var sameFace bool
+ if k < 0 {
+ sameFace = (j+k >= 0)
+ } else if k >= size {
+ sameFace = (j+k < maxSize)
+ } else {
+ sameFace = true
+ // Top and bottom neighbors.
+ neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j-nbrSize,
+ j-size >= 0).Parent(level))
+ neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j+size,
+ j+size < maxSize).Parent(level))
+ }
+
+ // Left, right, and diagonal neighbors.
+ neighbors = append(neighbors, cellIDFromFaceIJSame(face, i-nbrSize, j+k,
+ sameFace && i-size >= 0).Parent(level))
+ neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+size, j+k,
+ sameFace && i+size < maxSize).Parent(level))
+
+ if k >= size {
+ break
+ }
+ }
+
+ return neighbors
+}
+
// RangeMin returns the minimum CellID that is contained within this cell.
func (ci CellID) RangeMin() CellID { return CellID(uint64(ci) - (ci.lsb() - 1)) }
@@ -352,12 +427,18 @@ func (ci CellID) AdvanceWrap(steps int64) CellID {
// TODO: the methods below are not exported yet. Settle on the entire API design
// before doing this. Do we want to mirror the C++ one as closely as possible?
+// distanceFromBegin returns the number of steps that this cell is from the first
+// node in the S2 heirarchy at our level. (i.e., FromFace(0).ChildBeginAtLevel(ci.Level())).
+// The return value is always non-negative.
+func (ci CellID) distanceFromBegin() int64 {
+ return int64(ci >> uint64(2*(maxLevel-ci.Level())+1))
+}
+
// rawPoint returns an unnormalized r3 vector from the origin through the center
// of the s2 cell on the sphere.
func (ci CellID) rawPoint() r3.Vector {
face, si, ti := ci.faceSiTi()
return faceUVToXYZ(face, stToUV((0.5/maxSize)*float64(si)), stToUV((0.5/maxSize)*float64(ti)))
-
}
// faceSiTi returns the Face/Si/Ti coordinates of the center of the cell.
@@ -677,7 +758,57 @@ func (ci CellID) centerUV() r2.Point {
func (ci CellID) boundUV() r2.Rect {
_, i, j, _ := ci.faceIJOrientation()
return ijLevelToBoundUV(i, j, ci.Level())
+}
+// expandEndpoint returns a new u-coordinate u' such that the distance from the
+// line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance
+// (which is specified as the sine of the angle corresponding to the distance).
+func expandEndpoint(u, maxV, sinDist float64) float64 {
+ // This is based on solving a spherical right triangle, similar to the
+ // calculation in Cap.RectBound.
+ // Given an edge of the form (u,v0)-(u,v1), let maxV = max(abs(v0), abs(v1)).
+ sinUShift := sinDist * math.Sqrt((1+u*u+maxV*maxV)/(1+u*u))
+ cosUShift := math.Sqrt(1 - sinUShift*sinUShift)
+ // The following is an expansion of tan(atan(u) + asin(sinUShift)).
+ return (cosUShift*u + sinUShift) / (cosUShift - sinUShift*u)
+}
+
+// expandedByDistanceUV returns a rectangle expanded in (u,v)-space so that it
+// contains all points within the given distance of the boundary, and return the
+// smallest such rectangle. If the distance is negative, then instead shrink this
+// rectangle so that it excludes all points within the given absolute distance
+// of the boundary.
+//
+// Distances are measured *on the sphere*, not in (u,v)-space. For example,
+// you can use this method to expand the (u,v)-bound of an CellID so that
+// it contains all points within 5km of the original cell. You can then
+// test whether a point lies within the expanded bounds like this:
+//
+// if u, v, ok := faceXYZtoUV(face, point); ok && bound.ContainsPoint(r2.Point{u,v}) { ... }
+//
+// Limitations:
+//
+// - Because the rectangle is drawn on one of the six cube-face planes
+// (i.e., {x,y,z} = +/-1), it can cover at most one hemisphere. This
+// limits the maximum amount that a rectangle can be expanded. For
+// example, CellID bounds can be expanded safely by at most 45 degrees
+// (about 5000 km on the Earth's surface).
+//
+// - The implementation is not exact for negative distances. The resulting
+// rectangle will exclude all points within the given distance of the
+// boundary but may be slightly smaller than necessary.
+func expandedByDistanceUV(uv r2.Rect, distance s1.Angle) r2.Rect {
+ // Expand each of the four sides of the rectangle just enough to include all
+ // points within the given distance of that side. (The rectangle may be
+ // expanded by a different amount in (u,v)-space on each side.)
+ maxU := math.Max(math.Abs(uv.X.Lo), math.Abs(uv.X.Hi))
+ maxV := math.Max(math.Abs(uv.Y.Lo), math.Abs(uv.Y.Hi))
+ sinDist := math.Sin(float64(distance))
+ return r2.Rect{
+ X: r1.Interval{expandEndpoint(uv.X.Lo, maxV, -sinDist),
+ expandEndpoint(uv.X.Hi, maxV, sinDist)},
+ Y: r1.Interval{expandEndpoint(uv.Y.Lo, maxU, -sinDist),
+ expandEndpoint(uv.Y.Hi, maxU, sinDist)}}
}
// MaxTile returns the largest cell with the same RangeMin such that
@@ -723,7 +854,36 @@ func (ci CellID) MaxTile(limit CellID) CellID {
return ci
}
-// TODO: Differences from C++:
-// ExpandedByDistanceUV/ExpandEndpoint
-// CenterSiTi
-// AppendVertexNeighbors/AppendAllNeighbors
+// centerFaceSiTi returns the (face, si, ti) coordinates of the center of the cell.
+// Note that although (si,ti) coordinates span the range [0,2**31] in general,
+// the cell center coordinates are always in the range [1,2**31-1] and
+// therefore can be represented using a signed 32-bit integer.
+func (ci CellID) centerFaceSiTi() (face, si, ti int) {
+ // First we compute the discrete (i,j) coordinates of a leaf cell contained
+ // within the given cell. Given that cells are represented by the Hilbert
+ // curve position corresponding at their center, it turns out that the cell
+ // returned by faceIJOrientation is always one of two leaf cells closest
+ // to the center of the cell (unless the given cell is a leaf cell itself,
+ // in which case there is only one possibility).
+ //
+ // Given a cell of size s >= 2 (i.e. not a leaf cell), and letting (imin,
+ // jmin) be the coordinates of its lower left-hand corner, the leaf cell
+ // returned by faceIJOrientation is either (imin + s/2, jmin + s/2)
+ // (imin + s/2 - 1, jmin + s/2 - 1). The first case is the one we want.
+ // We can distinguish these two cases by looking at the low bit of i or
+ // j. In the second case the low bit is one, unless s == 2 (i.e. the
+ // level just above leaf cells) in which case the low bit is zero.
+ //
+ // In the code below, the expression ((i ^ (int(id) >> 2)) & 1) is true
+ // if we are in the second case described above.
+ face, i, j, _ := ci.faceIJOrientation()
+ delta := 0
+ if ci.IsLeaf() {
+ delta = 1
+ } else if (int64(i)^(int64(ci)>>2))&1 == 1 {
+ delta = 2
+ }
+
+ // Note that (2 * {i,j} + delta) will never overflow a 32-bit integer.
+ return face, 2*i + delta, 2*j + delta
+}
diff --git a/vendor/github.com/golang/geo/s2/cellid_test.go b/vendor/github.com/golang/geo/s2/cellid_test.go
new file mode 100644
index 0000000..c460b6d
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/cellid_test.go
@@ -0,0 +1,1052 @@
+/*
+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"
+ "reflect"
+ "sort"
+ "testing"
+
+ "github.com/golang/geo/r2"
+ "github.com/golang/geo/s1"
+)
+
+func TestCellIDFromFace(t *testing.T) {
+ for face := 0; face < 6; face++ {
+ fpl := CellIDFromFacePosLevel(face, 0, 0)
+ f := CellIDFromFace(face)
+ if fpl != f {
+ t.Errorf("CellIDFromFacePosLevel(%d, 0, 0) != CellIDFromFace(%d), got %v wanted %v", face, face, f, fpl)
+ }
+ }
+}
+
+func TestCellIDParentChildRelationships(t *testing.T) {
+ ci := CellIDFromFacePosLevel(3, 0x12345678, maxLevel-4)
+
+ if !ci.IsValid() {
+ t.Errorf("CellID %v should be valid", ci)
+ }
+ if f := ci.Face(); f != 3 {
+ t.Errorf("ci.Face() is %v, want 3", f)
+ }
+ if p := ci.Pos(); p != 0x12345700 {
+ t.Errorf("ci.Pos() is 0x%X, want 0x12345700", p)
+ }
+ if l := ci.Level(); l != 26 { // 26 is maxLevel - 4
+ t.Errorf("ci.Level() is %v, want 26", l)
+ }
+ if ci.IsLeaf() {
+ t.Errorf("CellID %v should not be a leaf", ci)
+ }
+
+ if kid2 := ci.ChildBeginAtLevel(ci.Level() + 2).Pos(); kid2 != 0x12345610 {
+ t.Errorf("child two levels down is 0x%X, want 0x12345610", kid2)
+ }
+ if kid0 := ci.ChildBegin().Pos(); kid0 != 0x12345640 {
+ t.Errorf("first child is 0x%X, want 0x12345640", kid0)
+ }
+ if kid0 := ci.Children()[0].Pos(); kid0 != 0x12345640 {
+ t.Errorf("first child is 0x%X, want 0x12345640", kid0)
+ }
+ if parent := ci.immediateParent().Pos(); parent != 0x12345400 {
+ t.Errorf("ci.immediateParent().Pos() = 0x%X, want 0x12345400", parent)
+ }
+ if parent := ci.Parent(ci.Level() - 2).Pos(); parent != 0x12345000 {
+ t.Errorf("ci.Parent(l-2).Pos() = 0x%X, want 0x12345000", parent)
+ }
+
+ if uint64(ci.ChildBegin()) >= uint64(ci) {
+ t.Errorf("ci.ChildBegin() is 0x%X, want < 0x%X", ci.ChildBegin(), ci)
+ }
+ if uint64(ci.ChildEnd()) <= uint64(ci) {
+ t.Errorf("ci.ChildEnd() is 0x%X, want > 0x%X", ci.ChildEnd(), ci)
+ }
+ if ci.ChildEnd() != ci.ChildBegin().Next().Next().Next().Next() {
+ t.Errorf("ci.ChildEnd() is 0x%X, want 0x%X", ci.ChildEnd(), ci.ChildBegin().Next().Next().Next().Next())
+ }
+ if ci.RangeMin() != ci.ChildBeginAtLevel(maxLevel) {
+ t.Errorf("ci.RangeMin() is 0x%X, want 0x%X", ci.RangeMin(), ci.ChildBeginAtLevel(maxLevel))
+ }
+ if ci.RangeMax().Next() != ci.ChildEndAtLevel(maxLevel) {
+ t.Errorf("ci.RangeMax().Next() is 0x%X, want 0x%X", ci.RangeMax().Next(), ci.ChildEndAtLevel(maxLevel))
+ }
+}
+
+func TestCellIDContainment(t *testing.T) {
+ a := CellID(0x80855c0000000000) // Pittsburg
+ b := CellID(0x80855d0000000000) // child of a
+ c := CellID(0x80855dc000000000) // child of b
+ d := CellID(0x8085630000000000) // part of Pittsburg disjoint from a
+ tests := []struct {
+ x, y CellID
+ xContainsY, yContainsX, xIntersectsY bool
+ }{
+ {a, a, true, true, true},
+ {a, b, true, false, true},
+ {a, c, true, false, true},
+ {a, d, false, false, false},
+ {b, b, true, true, true},
+ {b, c, true, false, true},
+ {b, d, false, false, false},
+ {c, c, true, true, true},
+ {c, d, false, false, false},
+ {d, d, true, true, true},
+ }
+ should := func(b bool) string {
+ if b {
+ return "should"
+ }
+ return "should not"
+ }
+ for _, test := range tests {
+ if test.x.Contains(test.y) != test.xContainsY {
+ t.Errorf("%v %s contain %v", test.x, should(test.xContainsY), test.y)
+ }
+ if test.x.Intersects(test.y) != test.xIntersectsY {
+ t.Errorf("%v %s intersect %v", test.x, should(test.xIntersectsY), test.y)
+ }
+ if test.y.Contains(test.x) != test.yContainsX {
+ t.Errorf("%v %s contain %v", test.y, should(test.yContainsX), test.x)
+ }
+ }
+
+ // TODO(dsymonds): Test Contains, Intersects better, such as with adjacent cells.
+}
+
+func TestCellIDString(t *testing.T) {
+ ci := CellID(0xbb04000000000000)
+ if s, exp := ci.String(), "5/31200"; s != exp {
+ t.Errorf("ci.String() = %q, want %q", s, exp)
+ }
+}
+
+func TestCellIDLatLng(t *testing.T) {
+ // You can generate these with the s2cellid2latlngtestcase C++ program in this directory.
+ tests := []struct {
+ id CellID
+ lat, lng float64
+ }{
+ {0x47a1cbd595522b39, 49.703498679, 11.770681595},
+ {0x46525318b63be0f9, 55.685376759, 12.588490937},
+ {0x52b30b71698e729d, 45.486546517, -93.449700022},
+ {0x46ed8886cfadda85, 58.299984854, 23.049300056},
+ {0x3663f18a24cbe857, 34.364439040, 108.330699969},
+ {0x10a06c0a948cf5d, -30.694551352, -30.048758753},
+ {0x2b2bfd076787c5df, -25.285264027, 133.823116966},
+ {0xb09dff882a7809e1, -75.000000031, 0.000000133},
+ {0x94daa3d000000001, -24.694439215, -47.537363213},
+ {0x87a1000000000001, 38.899730392, -99.901813021},
+ {0x4fc76d5000000001, 81.647200334, -55.631712940},
+ {0x3b00955555555555, 10.050986518, 78.293170610},
+ {0x1dcc469991555555, -34.055420593, 18.551140038},
+ {0xb112966aaaaaaaab, -69.219262171, 49.670072392},
+ }
+ for _, test := range tests {
+ l1 := LatLngFromDegrees(test.lat, test.lng)
+ l2 := test.id.LatLng()
+ if l1.Distance(l2) > 1e-9*s1.Degree { // ~0.1mm on earth.
+ t.Errorf("LatLng() for CellID %x (%s) : got %s, want %s", uint64(test.id), test.id, l2, l1)
+ }
+ c1 := test.id
+ c2 := CellIDFromLatLng(l1)
+ if c1 != c2 {
+ t.Errorf("CellIDFromLatLng(%s) = %x (%s), want %s", l1, uint64(c2), c2, c1)
+ }
+ }
+}
+
+func TestCellIDEdgeNeighbors(t *testing.T) {
+ // Check the edge neighbors of face 1.
+ faces := []int{5, 3, 2, 0}
+ for i, nbr := range cellIDFromFaceIJ(1, 0, 0).Parent(0).EdgeNeighbors() {
+ if !nbr.isFace() {
+ t.Errorf("CellID(%d) is not a face", nbr)
+ }
+ if got, want := nbr.Face(), faces[i]; got != want {
+ t.Errorf("CellID(%d).Face() = %d, want %d", nbr, got, want)
+ }
+ }
+ // Check the edge neighbors of the corner cells at all levels. This case is
+ // trickier because it requires projecting onto adjacent faces.
+ const maxIJ = maxSize - 1
+ for level := 1; level <= maxLevel; level++ {
+ id := cellIDFromFaceIJ(1, 0, 0).Parent(level)
+ // These neighbors were determined manually using the face and axis
+ // relationships.
+ levelSizeIJ := sizeIJ(level)
+ want := []CellID{
+ cellIDFromFaceIJ(5, maxIJ, maxIJ).Parent(level),
+ cellIDFromFaceIJ(1, levelSizeIJ, 0).Parent(level),
+ cellIDFromFaceIJ(1, 0, levelSizeIJ).Parent(level),
+ cellIDFromFaceIJ(0, maxIJ, 0).Parent(level),
+ }
+ for i, nbr := range id.EdgeNeighbors() {
+ if nbr != want[i] {
+ t.Errorf("CellID(%d).EdgeNeighbors()[%d] = %v, want %v", id, i, nbr, want[i])
+ }
+ }
+ }
+}
+
+type byCellID []CellID
+
+func (v byCellID) Len() int { return len(v) }
+func (v byCellID) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
+func (v byCellID) Less(i, j int) bool { return uint64(v[i]) < uint64(v[j]) }
+
+func TestCellIDVertexNeighbors(t *testing.T) {
+ // Check the vertex neighbors of the center of face 2 at level 5.
+ id := cellIDFromPoint(PointFromCoords(0, 0, 1))
+ neighbors := id.VertexNeighbors(5)
+ sort.Sort(byCellID(neighbors))
+
+ for n, nbr := range neighbors {
+ i, j := 1<<29, 1<<29
+ if n < 2 {
+ i--
+ }
+ if n == 0 || n == 3 {
+ j--
+ }
+ want := cellIDFromFaceIJ(2, i, j).Parent(5)
+
+ if nbr != want {
+ t.Errorf("CellID(%s).VertexNeighbors()[%d] = %v, want %v", id, n, nbr, want)
+ }
+ }
+
+ // Check the vertex neighbors of the corner of faces 0, 4, and 5.
+ id = CellIDFromFacePosLevel(0, 0, maxLevel)
+ neighbors = id.VertexNeighbors(0)
+ sort.Sort(byCellID(neighbors))
+ if len(neighbors) != 3 {
+ t.Errorf("len(CellID(%d).VertexNeighbors()) = %d, wanted %d", id, len(neighbors), 3)
+ }
+ if neighbors[0] != CellIDFromFace(0) {
+ t.Errorf("CellID(%d).VertexNeighbors()[0] = %d, wanted %d", id, neighbors[0], CellIDFromFace(0))
+ }
+ if neighbors[1] != CellIDFromFace(4) {
+ t.Errorf("CellID(%d).VertexNeighbors()[1] = %d, wanted %d", id, neighbors[1], CellIDFromFace(4))
+ }
+}
+
+// dedupCellIDs returns the unique slice of CellIDs from the sorted input list.
+func dedupCellIDs(ids []CellID) []CellID {
+ var out []CellID
+ var prev CellID
+ for _, id := range ids {
+ if id != prev {
+ out = append(out, id)
+ }
+ prev = id
+ }
+
+ return out
+}
+
+func TestCellIDAllNeighbors(t *testing.T) {
+ // Check that AllNeighbors produces results that are consistent
+ // with VertexNeighbors for a bunch of random cells.
+ for i := 0; i < 1000; i++ {
+ id := randomCellID()
+ if id.IsLeaf() {
+ id = id.immediateParent()
+ }
+
+ // testAllNeighbors computes approximately 2**(2*(diff+1)) cell ids,
+ // so it's not reasonable to use large values of diff.
+ maxDiff := min(6, maxLevel-id.Level()-1)
+ level := id.Level() + randomUniformInt(maxDiff)
+
+ // We compute AllNeighbors, and then add in all the children of id
+ // at the given level. We then compare this against the result of finding
+ // all the vertex neighbors of all the vertices of children of id at the
+ // given level. These should give the same result.
+ var want []CellID
+ all := id.AllNeighbors(level)
+ end := id.ChildEndAtLevel(level + 1)
+ for c := id.ChildBeginAtLevel(level + 1); c != end; c = c.Next() {
+ all = append(all, c.immediateParent())
+ want = append(want, c.VertexNeighbors(level)...)
+ }
+
+ // Sort the results and eliminate duplicates.
+ sort.Sort(byCellID(all))
+ sort.Sort(byCellID(want))
+ all = dedupCellIDs(all)
+ want = dedupCellIDs(want)
+
+ if !reflect.DeepEqual(all, want) {
+ t.Errorf("%v.AllNeighbors(%d) = %v, want %v", id, level, all, want)
+ }
+ }
+}
+
+func TestCellIDTokensNominal(t *testing.T) {
+ tests := []struct {
+ token string
+ id CellID
+ }{
+ {"1", 0x1000000000000000},
+ {"3", 0x3000000000000000},
+ {"14", 0x1400000000000000},
+ {"41", 0x4100000000000000},
+ {"094", 0x0940000000000000},
+ {"537", 0x5370000000000000},
+ {"3fec", 0x3fec000000000000},
+ {"72f3", 0x72f3000000000000},
+ {"52b8c", 0x52b8c00000000000},
+ {"990ed", 0x990ed00000000000},
+ {"4476dc", 0x4476dc0000000000},
+ {"2a724f", 0x2a724f0000000000},
+ {"7d4afc4", 0x7d4afc4000000000},
+ {"b675785", 0xb675785000000000},
+ {"40cd6124", 0x40cd612400000000},
+ {"3ba32f81", 0x3ba32f8100000000},
+ {"08f569b5c", 0x08f569b5c0000000},
+ {"385327157", 0x3853271570000000},
+ {"166c4d1954", 0x166c4d1954000000},
+ {"96f48d8c39", 0x96f48d8c39000000},
+ {"0bca3c7f74c", 0x0bca3c7f74c00000},
+ {"1ae3619d12f", 0x1ae3619d12f00000},
+ {"07a77802a3fc", 0x07a77802a3fc0000},
+ {"4e7887ec1801", 0x4e7887ec18010000},
+ {"4adad7ae74124", 0x4adad7ae74124000},
+ {"90aba04afe0c5", 0x90aba04afe0c5000},
+ {"8ffc3f02af305c", 0x8ffc3f02af305c00},
+ {"6fa47550938183", 0x6fa4755093818300},
+ {"aa80a565df5e7fc", 0xaa80a565df5e7fc0},
+ {"01614b5e968e121", 0x01614b5e968e1210},
+ {"aa05238e7bd3ee7c", 0xaa05238e7bd3ee7c},
+ {"48a23db9c2963e5b", 0x48a23db9c2963e5b},
+ }
+ for _, test := range tests {
+ ci := CellIDFromToken(test.token)
+ if ci != test.id {
+ t.Errorf("CellIDFromToken(%q) = %x, want %x", test.token, uint64(ci), uint64(test.id))
+ }
+
+ token := ci.ToToken()
+ if token != test.token {
+ t.Errorf("ci.ToToken = %q, want %q", token, test.token)
+ }
+ }
+}
+
+func TestCellIDFromTokensErrorCases(t *testing.T) {
+ noneToken := CellID(0).ToToken()
+ if noneToken != "X" {
+ t.Errorf("CellID(0).Token() = %q, want X", noneToken)
+ }
+ noneID := CellIDFromToken(noneToken)
+ if noneID != CellID(0) {
+ t.Errorf("CellIDFromToken(%q) = %x, want 0", noneToken, uint64(noneID))
+ }
+ tests := []string{
+ "876b e99",
+ "876bee99\n",
+ "876[ee99",
+ " 876bee99",
+ }
+ for _, test := range tests {
+ ci := CellIDFromToken(test)
+ if uint64(ci) != 0 {
+ t.Errorf("CellIDFromToken(%q) = %x, want 0", test, uint64(ci))
+ }
+ }
+}
+
+func TestIJLevelToBoundUV(t *testing.T) {
+ maxIJ := 1<<maxLevel - 1
+
+ tests := []struct {
+ i int
+ j int
+ level int
+ want r2.Rect
+ }{
+ // The i/j space is [0, 2^30 - 1) which maps to [-1, 1] for the
+ // x/y axes of the face surface. Results are scaled by the size of a cell
+ // at the given level. At level 0, everything is one cell of the full size
+ // of the space. At maxLevel, the bounding rect is almost floating point
+ // noise.
+
+ // What should be out of bounds values, but passes the C++ code as well.
+ {
+ -1, -1, 0,
+ r2.RectFromPoints(r2.Point{-5, -5}, r2.Point{-1, -1}),
+ },
+ {
+ -1 * maxIJ, -1 * maxIJ, 0,
+ r2.RectFromPoints(r2.Point{-5, -5}, r2.Point{-1, -1}),
+ },
+ {
+ -1, -1, maxLevel,
+ r2.RectFromPoints(r2.Point{-1.0000000024835267, -1.0000000024835267},
+ r2.Point{-1, -1}),
+ },
+ {
+ 0, 0, maxLevel + 1,
+ r2.RectFromPoints(r2.Point{-1, -1}, r2.Point{-1, -1}),
+ },
+
+ // Minimum i,j at different levels
+ {
+ 0, 0, 0,
+ r2.RectFromPoints(r2.Point{-1, -1}, r2.Point{1, 1}),
+ },
+ {
+ 0, 0, maxLevel / 2,
+ r2.RectFromPoints(r2.Point{-1, -1},
+ r2.Point{-0.999918621033430099, -0.999918621033430099}),
+ },
+ {
+ 0, 0, maxLevel,
+ r2.RectFromPoints(r2.Point{-1, -1},
+ r2.Point{-0.999999997516473060, -0.999999997516473060}),
+ },
+
+ // Just a hair off the outer bounds at different levels.
+ {
+ 1, 1, 0,
+ r2.RectFromPoints(r2.Point{-1, -1}, r2.Point{1, 1}),
+ },
+ {
+ 1, 1, maxLevel / 2,
+ r2.RectFromPoints(r2.Point{-1, -1},
+ r2.Point{-0.999918621033430099, -0.999918621033430099}),
+ },
+ {
+ 1, 1, maxLevel,
+ r2.RectFromPoints(r2.Point{-0.9999999975164731, -0.9999999975164731},
+ r2.Point{-0.9999999950329462, -0.9999999950329462}),
+ },
+
+ // Center point of the i,j space at different levels.
+ {
+ maxIJ / 2, maxIJ / 2, 0,
+ r2.RectFromPoints(r2.Point{-1, -1}, r2.Point{1, 1})},
+ {
+ maxIJ / 2, maxIJ / 2, maxLevel / 2,
+ r2.RectFromPoints(r2.Point{-0.000040691345930099, -0.000040691345930099},
+ r2.Point{0, 0})},
+ {
+ maxIJ / 2, maxIJ / 2, maxLevel,
+ r2.RectFromPoints(r2.Point{-0.000000001241763433, -0.000000001241763433},
+ r2.Point{0, 0})},
+
+ // Maximum i, j at different levels.
+ {
+ maxIJ, maxIJ, 0,
+ r2.RectFromPoints(r2.Point{-1, -1}, r2.Point{1, 1}),
+ },
+ {
+ maxIJ, maxIJ, maxLevel / 2,
+ r2.RectFromPoints(r2.Point{0.999918621033430099, 0.999918621033430099},
+ r2.Point{1, 1}),
+ },
+ {
+ maxIJ, maxIJ, maxLevel,
+ r2.RectFromPoints(r2.Point{0.999999997516473060, 0.999999997516473060},
+ r2.Point{1, 1}),
+ },
+ }
+
+ for _, test := range tests {
+ uv := ijLevelToBoundUV(test.i, test.j, test.level)
+ if !float64Eq(uv.X.Lo, test.want.X.Lo) ||
+ !float64Eq(uv.X.Hi, test.want.X.Hi) ||
+ !float64Eq(uv.Y.Lo, test.want.Y.Lo) ||
+ !float64Eq(uv.Y.Hi, test.want.Y.Hi) {
+ t.Errorf("ijLevelToBoundUV(%d, %d, %d), got %v, want %v",
+ test.i, test.j, test.level, uv, test.want)
+ }
+ }
+}
+
+func TestCellIDCommonAncestorLevel(t *testing.T) {
+ tests := []struct {
+ ci CellID
+ other CellID
+ want int
+ wantOk bool
+ }{
+ // Identical cell IDs.
+ {
+ CellIDFromFace(0),
+ CellIDFromFace(0),
+ 0,
+ true,
+ },
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(30),
+ CellIDFromFace(0).ChildBeginAtLevel(30),
+ 30,
+ true,
+ },
+ // One cell is a descendant of the other.
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(30),
+ CellIDFromFace(0),
+ 0,
+ true,
+ },
+ {
+ CellIDFromFace(5),
+ CellIDFromFace(5).ChildEndAtLevel(30).Prev(),
+ 0,
+ true,
+ },
+ // No common ancestors.
+ {
+ CellIDFromFace(0),
+ CellIDFromFace(5),
+ 0,
+ false,
+ },
+ {
+ CellIDFromFace(2).ChildBeginAtLevel(30),
+ CellIDFromFace(3).ChildBeginAtLevel(20),
+ 0,
+ false,
+ },
+ // Common ancestor distinct from both.
+ {
+ CellIDFromFace(5).ChildBeginAtLevel(9).Next().ChildBeginAtLevel(15),
+ CellIDFromFace(5).ChildBeginAtLevel(9).ChildBeginAtLevel(20),
+ 8,
+ true,
+ },
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(2).ChildBeginAtLevel(30),
+ CellIDFromFace(0).ChildBeginAtLevel(2).Next().ChildBeginAtLevel(5),
+ 1,
+ true,
+ },
+ }
+ for _, test := range tests {
+ if got, ok := test.ci.CommonAncestorLevel(test.other); ok != test.wantOk || got != test.want {
+ t.Errorf("CellID(%v).VertexNeighbors(%v) = %d, %t; want %d, %t", test.ci, test.other, got, ok, test.want, test.wantOk)
+ }
+ }
+}
+
+func TestCellIDDistanceToBegin(t *testing.T) {
+ tests := []struct {
+ id CellID
+ want int64
+ }{
+ {
+ // at level 0 (i.e. full faces), there are only 6 cells from
+ // the last face to the beginning of the Hilbert curve.
+ id: CellIDFromFace(5).ChildEndAtLevel(0),
+ want: 6,
+ },
+ {
+ // from the last cell on the last face at the smallest cell size,
+ // there are the maximum number of possible cells.
+ id: CellIDFromFace(5).ChildEndAtLevel(maxLevel),
+ want: 6 * (1 << uint(2*maxLevel)),
+ },
+ {
+ // from the first cell on the first face.
+ id: CellIDFromFace(0).ChildBeginAtLevel(0),
+ want: 0,
+ },
+ {
+ // from the first cell at the smallest level on the first face.
+ id: CellIDFromFace(0).ChildBeginAtLevel(maxLevel),
+ want: 0,
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.id.distanceFromBegin(); got != test.want {
+ t.Errorf("%v.distanceToBegin() = %v, want %v", test.id, got, test.want)
+ }
+ }
+
+ // Test that advancing from the beginning by the distance from a cell gets
+ // us back to that cell.
+ id := CellIDFromFacePosLevel(3, 0x12345678, maxLevel-4)
+ if got := CellIDFromFace(0).ChildBeginAtLevel(id.Level()).Advance(id.distanceFromBegin()); got != id {
+ t.Errorf("advancing from the beginning by the distance of a cell should return us to that cell. got %v, want %v", got, id)
+ }
+}
+
+func TestFindMSBSetNonZero64(t *testing.T) {
+ testOne := uint64(0x8000000000000000)
+ testAll := uint64(0xFFFFFFFFFFFFFFFF)
+ testSome := uint64(0xFEDCBA9876543210)
+ for i := 63; i >= 0; i-- {
+ if got := findMSBSetNonZero64(testOne); got != i {
+ t.Errorf("findMSBSetNonZero64(%x) = %d, want = %d", testOne, got, i)
+ }
+ if got := findMSBSetNonZero64(testAll); got != i {
+ t.Errorf("findMSBSetNonZero64(%x) = %d, want = %d", testAll, got, i)
+ }
+ if got := findMSBSetNonZero64(testSome); got != i {
+ t.Errorf("findMSBSetNonZero64(%x) = %d, want = %d", testSome, got, i)
+ }
+ testOne >>= 1
+ testAll >>= 1
+ testSome >>= 1
+ }
+
+ if got := findMSBSetNonZero64(1); got != 0 {
+ t.Errorf("findMSBSetNonZero64(1) = %v, want 0", got)
+ }
+
+ if got := findMSBSetNonZero64(0); got != 0 {
+ t.Errorf("findMSBSetNonZero64(0) = %v, want 0", got)
+ }
+}
+
+func TestFindLSBSetNonZero64(t *testing.T) {
+ testOne := uint64(0x0000000000000001)
+ testAll := uint64(0xFFFFFFFFFFFFFFFF)
+ testSome := uint64(0x0123456789ABCDEF)
+ for i := 0; i < 64; i++ {
+ if got := findLSBSetNonZero64(testOne); got != i {
+ t.Errorf("findLSBSetNonZero64(%x) = %d, want = %d", testOne, got, i)
+ }
+ if got := findLSBSetNonZero64(testAll); got != i {
+ t.Errorf("findLSBSetNonZero64(%x) = %d, want = %d", testAll, got, i)
+ }
+ if got := findLSBSetNonZero64(testSome); got != i {
+ t.Errorf("findLSBSetNonZero64(%x) = %d, want = %d", testSome, got, i)
+ }
+ testOne <<= 1
+ testAll <<= 1
+ testSome <<= 1
+ }
+
+ if got := findLSBSetNonZero64(0); got != 0 {
+ t.Errorf("findLSBSetNonZero64(0) = %v, want 0", got)
+ }
+}
+
+func TestCellIDWrapping(t *testing.T) {
+ id := CellIDFromFacePosLevel(3, 0x12345678, maxLevel-4)
+
+ tests := []struct {
+ msg string
+ got CellID
+ want CellID
+ }{
+ {
+ "test wrap from beginning to end of Hilbert curve",
+ CellIDFromFace(5).ChildEndAtLevel(0).Prev(),
+ CellIDFromFace(0).ChildBeginAtLevel(0).PrevWrap(),
+ },
+ {
+ "smallest end leaf wraps to smallest first leaf using PrevWrap",
+ CellIDFromFacePosLevel(5, ^uint64(0)>>faceBits, maxLevel),
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel).PrevWrap(),
+ },
+ {
+ "smallest end leaf wraps to smallest first leaf using AdvanceWrap",
+ CellIDFromFacePosLevel(5, ^uint64(0)>>faceBits, maxLevel),
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel).AdvanceWrap(-1),
+ },
+ {
+ "PrevWrap is the same as AdvanceWrap(-1)",
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel).AdvanceWrap(-1),
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel).PrevWrap(),
+ },
+ {
+ "Prev + NextWrap stays the same at given level",
+ CellIDFromFace(0).ChildBeginAtLevel(4),
+ CellIDFromFace(5).ChildEndAtLevel(4).Prev().NextWrap(),
+ },
+ {
+ "AdvanceWrap forward and back stays the same at given level",
+ CellIDFromFace(0).ChildBeginAtLevel(4),
+ CellIDFromFace(5).ChildEndAtLevel(4).Advance(-1).AdvanceWrap(1),
+ },
+ {
+ "Prev().NextWrap() stays same for first cell at level",
+ CellIDFromFacePosLevel(0, 0, maxLevel),
+ CellIDFromFace(5).ChildEndAtLevel(maxLevel).Prev().NextWrap(),
+ },
+ {
+ "AdvanceWrap forward and back stays same for first cell at level",
+ CellIDFromFacePosLevel(0, 0, maxLevel),
+ CellIDFromFace(5).ChildEndAtLevel(maxLevel).Advance(-1).AdvanceWrap(1),
+ },
+ // Check basic properties of AdvanceWrap().
+ {
+ "advancing 7 steps around cube should end up one past start.",
+ CellIDFromFace(1),
+ CellIDFromFace(0).ChildBeginAtLevel(0).AdvanceWrap(7),
+ },
+ {
+ "twice around should end up where we started",
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ CellIDFromFace(0).ChildBeginAtLevel(0).AdvanceWrap(12),
+ },
+ {
+ "backwards once around plus one step should be one before we started",
+ CellIDFromFace(4),
+ CellIDFromFace(5).AdvanceWrap(-7),
+ },
+ {
+ "wrapping even multiple of times around should end where we started",
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ CellIDFromFace(0).ChildBeginAtLevel(0).AdvanceWrap(-12000000),
+ },
+ {
+ "wrapping combination of even times around should end where it started",
+ CellIDFromFace(0).ChildBeginAtLevel(5).AdvanceWrap(6644),
+ CellIDFromFace(0).ChildBeginAtLevel(5).AdvanceWrap(-11788),
+ },
+ {
+ "moving 256 should advance us one cell at max level",
+ id.Next().ChildBeginAtLevel(maxLevel),
+ id.ChildBeginAtLevel(maxLevel).AdvanceWrap(256),
+ },
+ {
+ "wrapping by 4 times cells per face should advance 4 faces",
+ CellIDFromFacePosLevel(1, 0, maxLevel),
+ CellIDFromFacePosLevel(5, 0, maxLevel).AdvanceWrap(2 << (2 * maxLevel)),
+ },
+ }
+
+ for _, test := range tests {
+ if test.got != test.want {
+ t.Errorf("%s: got %v want %v", test.msg, test.got, test.want)
+ }
+ }
+}
+
+func TestCellIDAdvance(t *testing.T) {
+ tests := []struct {
+ ci CellID
+ steps int64
+ want CellID
+ }{
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ 7,
+ CellIDFromFace(5).ChildEndAtLevel(0),
+ },
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ 12,
+ CellIDFromFace(5).ChildEndAtLevel(0),
+ },
+ {
+ CellIDFromFace(5).ChildEndAtLevel(0),
+ -7,
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ },
+ {
+ CellIDFromFace(5).ChildEndAtLevel(0),
+ -12000000,
+ CellIDFromFace(0).ChildBeginAtLevel(0),
+ },
+ {
+ CellIDFromFace(0).ChildBeginAtLevel(5),
+ 500,
+ CellIDFromFace(5).ChildEndAtLevel(5).Advance(500 - (6 << (2 * 5))),
+ },
+ {
+ CellIDFromFacePosLevel(3, 0x12345678, maxLevel-4).ChildBeginAtLevel(maxLevel),
+ 256,
+ CellIDFromFacePosLevel(3, 0x12345678, maxLevel-4).Next().ChildBeginAtLevel(maxLevel),
+ },
+ {
+ CellIDFromFacePosLevel(1, 0, maxLevel),
+ 4 << (2 * maxLevel),
+ CellIDFromFacePosLevel(5, 0, maxLevel),
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.ci.Advance(test.steps); got != test.want {
+ t.Errorf("CellID(%v).Advance(%d) = %v; want = %v", test.ci, test.steps, got, test.want)
+ }
+ }
+}
+
+func TestCellIDFaceSiTi(t *testing.T) {
+ id := CellIDFromFacePosLevel(3, 0x12345678, maxLevel)
+ // Check that the (si, ti) coordinates of the center end in a
+ // 1 followed by (30 - level) 0's.
+ for level := uint64(0); level <= maxLevel; level++ {
+ l := maxLevel - int(level)
+ want := 1 << level
+ mask := 1<<(level+1) - 1
+
+ _, si, ti := id.Parent(l).faceSiTi()
+ if want != si&mask {
+ t.Errorf("CellID.Parent(%d).faceSiTi(), si = %b, want %b", l, si&mask, want)
+ }
+ if want != ti&mask {
+ t.Errorf("CellID.Parent(%d).faceSiTi(), ti = %b, want %b", l, ti&mask, want)
+ }
+ }
+}
+
+func TestCellIDContinuity(t *testing.T) {
+ const maxWalkLevel = 8
+ const cellSize = 1.0 / (1 << maxWalkLevel)
+
+ // Make sure that sequentially increasing cell ids form a continuous
+ // path over the surface of the sphere, i.e. there are no
+ // discontinuous jumps from one region to another.
+
+ maxDist := MaxWidthMetric.Value(maxWalkLevel)
+ end := CellIDFromFace(5).ChildEndAtLevel(maxWalkLevel)
+ id := CellIDFromFace(0).ChildBeginAtLevel(maxWalkLevel)
+
+ for ; id != end; id = id.Next() {
+
+ if got := id.rawPoint().Angle(id.NextWrap().rawPoint()); float64(got) > maxDist {
+ t.Errorf("%v.rawPoint().Angle(%v.NextWrap().rawPoint()) = %v > %v", id, id, got, maxDist)
+ }
+ if id.NextWrap() != id.AdvanceWrap(1) {
+ t.Errorf("%v.NextWrap() != %v.AdvanceWrap(1) %v != %v)", id, id, id.NextWrap(), id.AdvanceWrap(1))
+ }
+ if id != id.NextWrap().AdvanceWrap(-1) {
+ t.Errorf("%v.NextWrap().AdvanceWrap(-1) = %v want %v)", id, id.NextWrap().AdvanceWrap(-1), id)
+ }
+
+ // Check that the rawPoint() returns the center of each cell
+ // in (s,t) coordinates.
+ _, u, v := xyzToFaceUV(id.rawPoint())
+ if !float64Eq(math.Remainder(uvToST(u), 0.5*cellSize), 0.0) {
+ t.Errorf("uvToST(%v) = %v, want %v", u, uvToST(u), 0.5*cellSize)
+ }
+ if !float64Eq(math.Remainder(uvToST(v), 0.5*cellSize), 0.0) {
+ t.Errorf("uvToST(%v) = %v, want %v", v, uvToST(v), 0.5*cellSize)
+ }
+ }
+}
+
+// sampleBoundary returns a random point on the boundary of the given rectangle.
+func sampleBoundary(rect r2.Rect) (u, v float64) {
+ if oneIn(2) {
+ v = randomUniformFloat64(rect.Y.Lo, rect.Y.Hi)
+ if oneIn(2) {
+ u = rect.X.Lo
+ } else {
+ u = rect.X.Hi
+ }
+ } else {
+ u = randomUniformFloat64(rect.X.Lo, rect.X.Hi)
+ if oneIn(2) {
+ v = rect.Y.Lo
+ } else {
+ v = rect.Y.Hi
+ }
+ }
+ return u, v
+}
+
+// projectToBoundary returns the closest point to uv on the boundary of rect.
+func projectToBoundary(u, v float64, rect r2.Rect) r2.Point {
+ du0 := math.Abs(u - rect.X.Lo)
+ du1 := math.Abs(u - rect.X.Hi)
+ dv0 := math.Abs(v - rect.Y.Lo)
+ dv1 := math.Abs(v - rect.Y.Hi)
+
+ dmin := math.Min(math.Min(du0, du1), math.Min(dv0, dv1))
+ if du0 == dmin {
+ return r2.Point{rect.X.Lo, rect.Y.ClampPoint(v)}
+ }
+ if du1 == dmin {
+ return r2.Point{rect.X.Hi, rect.Y.ClampPoint(v)}
+ }
+ if dv0 == dmin {
+ return r2.Point{rect.X.ClampPoint(u), rect.Y.Lo}
+ }
+
+ return r2.Point{rect.X.ClampPoint(u), rect.Y.Hi}
+}
+
+func TestCellIDExpandedByDistanceUV(t *testing.T) {
+ const maxDistDegrees = 10
+ for i := 0; i < 1000; i++ {
+ id := randomCellID()
+ distance := s1.Degree * s1.Angle(randomUniformFloat64(-maxDistDegrees, maxDistDegrees))
+
+ bound := id.boundUV()
+ expanded := expandedByDistanceUV(bound, distance)
+ for iter := 0; iter < 10; iter++ {
+ // Choose a point on the boundary of the rectangle.
+ face := randomUniformInt(6)
+ centerU, centerV := sampleBoundary(bound)
+ center := Point{faceUVToXYZ(face, centerU, centerV).Normalize()}
+
+ // Now sample a point from a disc of radius (2 * distance).
+ p := samplePointFromCap(CapFromCenterHeight(center, 2*math.Abs(float64(distance))))
+
+ // Find the closest point on the boundary to the sampled point.
+ u, v, ok := faceXYZToUV(face, p)
+ if !ok {
+ continue
+ }
+
+ uv := r2.Point{u, v}
+ closestUV := projectToBoundary(u, v, bound)
+ closest := faceUVToXYZ(face, closestUV.X, closestUV.Y).Normalize()
+ actualDist := p.Distance(Point{closest})
+
+ if distance >= 0 {
+ // expanded should contain all points in the original bound,
+ // and also all points within distance of the boundary.
+ if bound.ContainsPoint(uv) || actualDist < distance {
+ if !expanded.ContainsPoint(uv) {
+ t.Errorf("expandedByDistanceUV(%v, %v).ContainsPoint(%v) = false, want true", bound, distance, uv)
+ }
+ }
+ } else {
+ // expanded should not contain any points within distance
+ // of the original boundary.
+ if actualDist < -distance {
+ if expanded.ContainsPoint(uv) {
+ t.Errorf("negatively expandedByDistanceUV(%v, %v).ContainsPoint(%v) = true, want false", bound, distance, uv)
+ }
+ }
+ }
+ }
+ }
+}
+
+func TestCellIDMaxTile(t *testing.T) {
+ // This method is also tested more thoroughly in s2cellunion_test.
+ for iter := 0; iter < 1000; iter++ {
+ id := randomCellIDForLevel(10)
+
+ // Check that limit is returned for tiles at or beyond limit.
+ if got, want := id, id.MaxTile(id); got != want {
+ t.Errorf("%v.MaxTile(%v) = %v, want %v", id, id, got, want)
+ }
+ if got, want := id, id.Children()[0].MaxTile(id); got != want {
+ t.Errorf("%v.Children()[0].MaxTile(%v) = %v, want %v", id, id, got, want)
+ }
+ if got, want := id, id.Children()[1].MaxTile(id); got != want {
+ t.Errorf("%v.Children()[1].MaxTile(%v) = %v, want %v", id, id, got, want)
+ }
+ if got, want := id, id.Next().MaxTile(id); got != want {
+ t.Errorf("%v.Next().MaxTile(%v) = %v, want %v", id, id, got, want)
+ }
+ if got, want := id.Children()[0], id.MaxTile(id.Children()[0]); got != want {
+ t.Errorf("%v.MaxTile(%v.Children()[0] = %v, want %v", id, id, got, want)
+ }
+
+ // Check that the tile size is increased when possible.
+ if got, want := id, id.Children()[0].MaxTile(id.Next()); got != want {
+ t.Errorf("%v.Children()[0].MaxTile(%v.Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.Children()[0].MaxTile(id.Next().Children()[0]); got != want {
+ t.Errorf("%v.Children()[0].MaxTile(%v.Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.Children()[0].MaxTile(id.Next().Children()[1].Children()[0]); got != want {
+ t.Errorf("%v.Children()[0].MaxTile(%v.Next().Children()[1].Children()[0] = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.Children()[0].Children()[0].MaxTile(id.Next()); got != want {
+ t.Errorf("%v.Children()[0].Children()[0].MaxTile(%v.Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.Children()[0].Children()[0].Children()[0].MaxTile(id.Next()); got != want {
+ t.Errorf("%v.Children()[0].Children()[0].Children()[0].MaxTile(%v.Next()) = %v, want %v", id, id, got, want)
+ }
+
+ // Check that the tile size is decreased when necessary.
+ if got, want := id.Children()[0], id.MaxTile(id.Children()[0].Next()); got != want {
+ t.Errorf("%v.Children()[0], id.MaxTile(%v.Children()[0].Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id.Children()[0], id.MaxTile(id.Children()[0].Next().Children()[0]); got != want {
+ t.Errorf("%v.Children()[0], id.MaxTile(%v.Children()[0].Next().Children()[0]) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id.Children()[0], id.MaxTile(id.Children()[0].Next().Children()[1]); got != want {
+ t.Errorf("%v.Children()[0], id.MaxTile(%v.Children()[0].Next().Children()[1]) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id.Children()[0].Children()[0], id.MaxTile(id.Children()[0].Children()[0].Next()); got != want {
+ t.Errorf("%v.Children()[0].Children()[0], id.MaxTile(%v.Children()[0].Children()[0].Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id.Children()[0].Children()[0].Children()[0],
+ id.MaxTile(id.Children()[0].Children()[0].Children()[0].Next()); got != want {
+ t.Errorf("%v.MaxTile(%v.Children()[0].Children()[0].Children()[0].Next()) = %v, want %v", id, id, got, want)
+ }
+
+ // Check that the tile size is otherwise unchanged.
+ if got, want := id, id.MaxTile(id.Next()); got != want {
+ t.Errorf("%v.MaxTile(%v.Next()) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.MaxTile(id.Next().Children()[0]); got != want {
+ t.Errorf("%v.MaxTile(%v.Next().Children()[0]) = %v, want %v", id, id, got, want)
+ }
+
+ if got, want := id, id.MaxTile(id.Next().Children()[1].Children()[0]); got != want {
+ t.Errorf("%v.MaxTile(%v.Next().Children()[1].Children()[0]) = %v, want %v", id, id, got, want)
+ }
+ }
+}
+
+func TestCellIDCenterFaceSiTi(t *testing.T) {
+ // Check that the (si, ti) coordinates of the center end in a
+ // 1 followed by (30 - level) 0s.
+
+ id := CellIDFromFacePosLevel(3, 0x12345678, maxLevel)
+
+ tests := []struct {
+ id CellID
+ levelOffset uint
+ }{
+ // Leaf level, 30.
+ {id, 0},
+ // Level 29.
+ {id.Parent(maxLevel - 1), 1},
+ // Level 28.
+ {id.Parent(maxLevel - 2), 2},
+ // Level 20.
+ {id.Parent(maxLevel - 10), 10},
+ // Level 10.
+ {id.Parent(maxLevel - 20), 20},
+ // Level 0.
+ {id.Parent(0), maxLevel},
+ }
+
+ for _, test := range tests {
+ _, si, ti := test.id.centerFaceSiTi()
+ want := 1 << test.levelOffset
+ mask := (1 << (test.levelOffset + 1)) - 1
+ if want != si&mask {
+ t.Errorf("Level Offset %d. %b != %b", test.levelOffset, want, si&mask)
+ }
+ if want != ti&mask {
+ t.Errorf("Level Offset: %d. %b != %b", test.levelOffset, want, ti&mask)
+ }
+ }
+}
+
+// TODO(roberts): Remaining tests to convert.
+// Coverage
+// TraversalOrder
diff --git a/vendor/github.com/golang/geo/s2/cellunion_test.go b/vendor/github.com/golang/geo/s2/cellunion_test.go
new file mode 100644
index 0000000..a48ca1b
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/cellunion_test.go
@@ -0,0 +1,723 @@
+/*
+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"
+ "reflect"
+ "testing"
+
+ "github.com/golang/geo/r1"
+ "github.com/golang/geo/s1"
+)
+
+func TestCellUnionNormalization(t *testing.T) {
+ cu := CellUnion{
+ 0x80855c0000000000, // A: a cell over Pittsburg CA
+ 0x80855d0000000000, // B, a child of A
+ 0x8085634000000000, // first child of X, disjoint from A
+ 0x808563c000000000, // second child of X
+ 0x80855dc000000000, // a child of B
+ 0x808562c000000000, // third child of X
+ 0x8085624000000000, // fourth child of X
+ 0x80855d0000000000, // B again
+ }
+ exp := CellUnion{
+ 0x80855c0000000000, // A
+ 0x8085630000000000, // X
+ }
+ cu.Normalize()
+ if !reflect.DeepEqual(cu, exp) {
+ t.Errorf("got %v, want %v", cu, exp)
+ }
+
+ // add a redundant cell
+ /* TODO(dsymonds)
+ cu.Add(0x808562c000000000)
+ if !reflect.DeepEqual(cu, exp) {
+ t.Errorf("after redundant add, got %v, want %v", cu, exp)
+ }
+ */
+}
+
+func TestCellUnionBasic(t *testing.T) {
+ empty := CellUnion{}
+ empty.Normalize()
+ if len(empty) != 0 {
+ t.Errorf("empty CellUnion had %d cells, want 0", len(empty))
+ }
+
+ face1ID := CellIDFromFace(1)
+ face1Cell := CellFromCellID(face1ID)
+ face1Union := CellUnion{face1ID}
+ face1Union.Normalize()
+ if len(face1Union) != 1 {
+ t.Errorf("%v had %d cells, want 1", face1Union, len(face1Union))
+ }
+ if face1ID != face1Union[0] {
+ t.Errorf("%v[0] = %v, want %v", face1Union, face1Union[0], face1ID)
+ }
+ if got := face1Union.ContainsCell(face1Cell); !got {
+ t.Errorf("%v.ContainsCell(%v) = %t, want %t", face1Union, face1Cell, got, true)
+ }
+
+ face2ID := CellIDFromFace(2)
+ face2Cell := CellFromCellID(face2ID)
+ face2Union := CellUnion{face2ID}
+ face2Union.Normalize()
+ if len(face2Union) != 1 {
+ t.Errorf("%v had %d cells, want 1", face2Union, len(face2Union))
+ }
+ if face2ID != face2Union[0] {
+ t.Errorf("%v[0] = %v, want %v", face2Union, face2Union[0], face2ID)
+ }
+
+ if got := face1Union.ContainsCell(face2Cell); got {
+ t.Errorf("%v.ContainsCell(%v) = %t, want %t", face1Union, face2Cell, got, false)
+ }
+
+}
+
+func TestCellUnion(t *testing.T) {
+ tests := []struct {
+ cells []CellID // A test CellUnion.
+ contained []CellID // List of cellIDs contained in the CellUnion.
+ overlaps []CellID // List of CellIDs that intersects the CellUnion but not contained in it.
+ disjoint []CellID // List of CellIDs that are disjoint from the CellUnion.
+ }{
+ {
+ // Single cell around NYC, and some simple nearby probes
+ cells: []CellID{0x89c25c0000000000},
+ contained: []CellID{
+ CellID(0x89c25c0000000000).ChildBegin(),
+ CellID(0x89c25c0000000000).ChildBeginAtLevel(28),
+ },
+ overlaps: []CellID{
+ CellID(0x89c25c0000000000).immediateParent(),
+ CellIDFromFace(CellID(0x89c25c0000000000).Face()), // the whole face
+ },
+ disjoint: []CellID{
+ CellID(0x89c25c0000000000).Next(), // Cell next to this one at same level
+ CellID(0x89c25c0000000000).Next().ChildBeginAtLevel(28), // Cell next to this one at deep level
+ 0x89c2700000000000, // Big(er) neighbor cell
+ 0x89e9000000000000, // Very big next door cell.
+ 0x89c1000000000000, // Very big cell, smaller value than probe
+ },
+ },
+
+ {
+ // NYC and SFO:
+ cells: []CellID{
+ 0x89c25b0000000000, // NYC
+ 0x89c2590000000000, // NYC
+ 0x89c2f70000000000, // NYC
+ 0x89c2f50000000000, // NYC
+ 0x8085870000000000, // SFO
+ 0x8085810000000000, // SFO
+ 0x808f7d0000000000, // SFO
+ 0x808f7f0000000000, // SFO
+ },
+ contained: []CellID{
+ 0x808f7ef300000000, // SFO
+ 0x808f7e5cf0000000, // SFO
+ 0x808587f000000000, // SFO
+ 0x89c25ac000000000, // NYC
+ 0x89c259a400000000, // NYC
+ 0x89c258fa10000000, // NYC
+ 0x89c258f174007000, // NYC
+ },
+ overlaps: []CellID{
+ 0x808c000000000000, // Big SFO
+ 0x89c4000000000000, // Big NYC
+ },
+ disjoint: []CellID{
+ 0x89c15a4fcb1bb000, // outside NYC
+ 0x89c15a4e4aa95000, // outside NYC
+ 0x8094000000000000, // outside SFO (big)
+ 0x8096f10000000000, // outside SFO (smaller)
+
+ 0x87c0000000000000, // Midwest very big
+ },
+ },
+ {
+ // CellUnion with cells at many levels:
+ cells: []CellID{
+ 0x8100000000000000, // starting around california
+ 0x8740000000000000, // adjacent cells at increasing
+ 0x8790000000000000, // levels, moving eastward.
+ 0x87f4000000000000,
+ 0x87f9000000000000, // going down across the midwest
+ 0x87ff400000000000,
+ 0x87ff900000000000,
+ 0x87fff40000000000,
+ 0x87fff90000000000,
+ 0x87ffff4000000000,
+ 0x87ffff9000000000,
+ 0x87fffff400000000,
+ 0x87fffff900000000,
+ 0x87ffffff40000000,
+ 0x87ffffff90000000,
+ 0x87fffffff4000000,
+ 0x87fffffff9000000,
+ 0x87ffffffff400000, // to a very small cell in Wisconsin
+ },
+ contained: []CellID{
+ 0x808f400000000000,
+ 0x80eb118b00000000,
+ 0x8136a7a11d000000,
+ 0x8136a7a11dac0000,
+ 0x876c7c0000000000,
+ 0x87f96d0000000000,
+ 0x87ffffffff400000,
+ },
+ overlaps: []CellID{
+ CellID(0x8100000000000000).immediateParent(),
+ CellID(0x8740000000000000).immediateParent(),
+ },
+ disjoint: []CellID{
+ 0x52aaaaaaab300000,
+ 0x52aaaaaaacd00000,
+ 0x87fffffffa100000,
+ 0x87ffffffed500000,
+ 0x87ffffffa0100000,
+ 0x87fffffed5540000,
+ 0x87fffffed6240000,
+ 0x52aaaacccb340000,
+ 0x87a0000400000000,
+ 0x87a000001f000000,
+ 0x87a0000029d00000,
+ 0x9500000000000000,
+ },
+ },
+ }
+ for _, test := range tests {
+ union := CellUnion(test.cells)
+ union.Normalize()
+
+ // Ensure self-containment tests are correct.
+ for _, id := range test.cells {
+ if !union.IntersectsCellID(id) {
+ t.Errorf("CellUnion %v should self-intersect %v but does not", union, id)
+ }
+ if !union.ContainsCellID(id) {
+ t.Errorf("CellUnion %v should self-contain %v but does not", union, id)
+ }
+ }
+ // Test for containment specified in test case.
+ for _, id := range test.contained {
+ if !union.IntersectsCellID(id) {
+ t.Errorf("CellUnion %v should intersect %v but does not", union, id)
+ }
+ if !union.ContainsCellID(id) {
+ t.Errorf("CellUnion %v should contain %v but does not", union, id)
+ }
+ }
+ // Make sure the CellUnion intersect these cells but do not contain.
+ for _, id := range test.overlaps {
+ if !union.IntersectsCellID(id) {
+ t.Errorf("CellUnion %v should intersect %v but does not", union, id)
+ }
+ if union.ContainsCellID(id) {
+ t.Errorf("CellUnion %v should not contain %v but does", union, id)
+ }
+ }
+ // Negative cases make sure the CellUnion neither contain nor intersect these cells
+ for _, id := range test.disjoint {
+ if union.IntersectsCellID(id) {
+ t.Errorf("CellUnion %v should not intersect %v but does", union, id)
+ }
+ if union.ContainsCellID(id) {
+ t.Errorf("CellUnion %v should not contain %v but does", union, id)
+ }
+ }
+ }
+}
+
+func addCells(id CellID, selected bool, input *[]CellID, expected *[]CellID, t *testing.T) {
+ // Decides whether to add "id" and/or some of its descendants to the test case. If "selected"
+ // is true, then the region covered by "id" *must* be added to the test case (either by adding
+ // "id" itself, or some combination of its descendants, or both). If cell ids are to the test
+ // case "input", then the corresponding expected result after simplification is added to
+ // "expected".
+
+ if id == 0 {
+ // Initial call: decide whether to add cell(s) from each face.
+ for face := 0; face < 6; face++ {
+ addCells(CellIDFromFace(face), false, input, expected, t)
+ }
+ return
+ }
+
+ if id.IsLeaf() {
+ // The oneIn() call below ensures that the parent of a leaf cell will always be selected (if
+ // we make it that far down the hierarchy).
+ if selected != true {
+ t.Errorf("id IsLeaf() and not selected")
+ }
+ *input = append(*input, id)
+ return
+ }
+
+ // The following code ensures that the probability of selecting a cell at each level is
+ // approximately the same, i.e. we test normalization of cells at all levels.
+ if !selected && oneIn(maxLevel-id.Level()) {
+ // Once a cell has been selected, the expected output is predetermined. We then make sure
+ // that cells are selected that will normalize to the desired output.
+ *expected = append(*expected, id)
+ selected = true
+
+ }
+
+ // With the rnd.OneIn() constants below, this function adds an average
+ // of 5/6 * (kMaxLevel - level) cells to "input" where "level" is the
+ // level at which the cell was first selected (level 15 on average).
+ // Therefore the average number of input cells in a test case is about
+ // (5/6 * 15 * 6) = 75. The average number of output cells is about 6.
+
+ // If a cell is selected, we add it to "input" with probability 5/6.
+ added := false
+ if selected && !oneIn(6) {
+ *input = append(*input, id)
+ added = true
+ }
+ numChildren := 0
+ for child := id.ChildBegin(); child != id.ChildEnd(); child = child.Next() {
+ // If the cell is selected, on average we recurse on 4/12 = 1/3 child.
+ // This intentionally may result in a cell and some of its children
+ // being included in the test case.
+ //
+ // If the cell is not selected, on average we recurse on one child.
+ // We also make sure that we do not recurse on all 4 children, since
+ // then we might include all 4 children in the input case by accident
+ // (in which case the expected output would not be correct).
+ recurse := false
+ if selected {
+ recurse = oneIn(12)
+ } else {
+ recurse = oneIn(4)
+ }
+ if recurse && numChildren < 3 {
+ addCells(child, selected, input, expected, t)
+ numChildren++
+ }
+ // If this cell was selected but the cell itself was not added, we
+ // must ensure that all 4 children (or some combination of their
+ // descendants) are added.
+
+ if selected && !added {
+ addCells(child, selected, input, expected, t)
+ }
+ }
+}
+
+func TestCellUnionNormalizePseudoRandom(t *testing.T) {
+ // Try a bunch of random test cases, and keep track of average statistics
+ // for normalization (to see if they agree with the analysis above).
+
+ inSum := 0
+ outSum := 0
+ iters := 2000
+
+ for i := 0; i < iters; i++ {
+ input := []CellID{}
+ expected := []CellID{}
+ addCells(CellID(0), false, &input, &expected, t)
+ inSum += len(input)
+ outSum += len(expected)
+ cellunion := CellUnion(input)
+ cellunion.Normalize()
+
+ if len(expected) != len(cellunion) {
+ t.Errorf("Expected size of union to be %d, but got %d.",
+ len(expected), len(cellunion))
+ }
+
+ // Test GetCapBound().
+ cb := cellunion.CapBound()
+ for _, ci := range cellunion {
+ if !cb.ContainsCell(CellFromCellID(ci)) {
+ t.Errorf("CapBound %v of union %v should contain cellID %v", cb, cellunion, ci)
+ }
+ }
+
+ for _, j := range input {
+ if !cellunion.ContainsCellID(j) {
+ t.Errorf("Expected containment of CellID %v", j)
+ }
+
+ if cellunion.IntersectsCellID(j) == false {
+ t.Errorf("Expected intersection with %v.", j)
+ }
+
+ if !j.isFace() {
+ if cellunion.IntersectsCellID(j.immediateParent()) == false {
+ t.Errorf("Expected intersection with parent cell %v.", j.immediateParent())
+ if j.Level() > 1 {
+ if cellunion.IntersectsCellID(j.immediateParent().immediateParent()) == false {
+ t.Errorf("Expected intersection with parent's parent %v.",
+ j.immediateParent().immediateParent())
+ }
+ if cellunion.IntersectsCellID(j.Parent(0)) == false {
+ t.Errorf("Expected intersection with parent %v at level 0.", j.Parent(0))
+ }
+ }
+ }
+ }
+
+ if !j.IsLeaf() {
+ if cellunion.ContainsCellID(j.ChildBegin()) == false {
+ t.Errorf("Expected containment of %v.", j.ChildBegin())
+ }
+ if cellunion.IntersectsCellID(j.ChildBegin()) == false {
+ t.Errorf("Expected intersection with %v.", j.ChildBegin())
+ }
+ if cellunion.ContainsCellID(j.ChildEnd().Prev()) == false {
+ t.Errorf("Expected containment of %v.", j.ChildEnd().Prev())
+ }
+ if cellunion.IntersectsCellID(j.ChildEnd().Prev()) == false {
+ t.Errorf("Expected intersection with %v.", j.ChildEnd().Prev())
+ }
+ if cellunion.ContainsCellID(j.ChildBeginAtLevel(maxLevel)) == false {
+ t.Errorf("Expected containment of %v.", j.ChildBeginAtLevel(maxLevel))
+ }
+ if cellunion.IntersectsCellID(j.ChildBeginAtLevel(maxLevel)) == false {
+ t.Errorf("Expected intersection with %v.", j.ChildBeginAtLevel(maxLevel))
+ }
+ }
+ }
+
+ for _, exp := range expected {
+ if !exp.isFace() {
+ if cellunion.ContainsCellID(exp.Parent(exp.Level() - 1)) {
+ t.Errorf("cellunion should not contain its parent %v", exp.Parent(exp.Level()-1))
+ }
+ if cellunion.ContainsCellID(exp.Parent(0)) {
+ t.Errorf("cellunion should not contain the top level parent %v", exp.Parent(0))
+ }
+ }
+ }
+
+ var test []CellID
+ var dummy []CellID
+ addCells(CellID(0), false, &test, &dummy, t)
+ for _, j := range test {
+ intersects := false
+ contains := false
+ for _, k := range expected {
+ if k.Contains(j) {
+ contains = true
+ }
+ if k.Intersects(j) {
+ intersects = true
+ }
+ }
+ if cellunion.ContainsCellID(j) != contains {
+ t.Errorf("Expected contains with %v.", (uint64)(j))
+ }
+ if cellunion.IntersectsCellID(j) != intersects {
+ t.Errorf("Expected intersection with %v.", (uint64)(j))
+ }
+ }
+ }
+ t.Logf("avg in %.2f, avg out %.2f\n", (float64)(inSum)/(float64)(iters), (float64)(outSum)/(float64)(iters))
+}
+
+func TestCellUnionDenormalize(t *testing.T) {
+ tests := []struct {
+ name string
+ minL int
+ lMod int
+ cu *CellUnion
+ exp *CellUnion
+ }{
+ {
+ "not expanded, level mod == 1",
+ 10,
+ 1,
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(11),
+ CellIDFromFace(2).ChildBeginAtLevel(11),
+ CellIDFromFace(3).ChildBeginAtLevel(14),
+ CellIDFromFace(0).ChildBeginAtLevel(10),
+ },
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(11),
+ CellIDFromFace(2).ChildBeginAtLevel(11),
+ CellIDFromFace(3).ChildBeginAtLevel(14),
+ CellIDFromFace(0).ChildBeginAtLevel(10),
+ },
+ },
+ {
+ "not expanded, level mod > 1",
+ 10,
+ 2,
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(12),
+ CellIDFromFace(2).ChildBeginAtLevel(12),
+ CellIDFromFace(3).ChildBeginAtLevel(14),
+ CellIDFromFace(0).ChildBeginAtLevel(10),
+ },
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(12),
+ CellIDFromFace(2).ChildBeginAtLevel(12),
+ CellIDFromFace(3).ChildBeginAtLevel(14),
+ CellIDFromFace(0).ChildBeginAtLevel(10),
+ },
+ },
+ {
+ "expended, (level - min_level) is not multiple of level mod",
+ 10,
+ 3,
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(12),
+ CellIDFromFace(5).ChildBeginAtLevel(11),
+ },
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(12).Children()[0],
+ CellIDFromFace(2).ChildBeginAtLevel(12).Children()[1],
+ CellIDFromFace(2).ChildBeginAtLevel(12).Children()[2],
+ CellIDFromFace(2).ChildBeginAtLevel(12).Children()[3],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[0],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[1],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[2],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[0].Children()[3],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[0],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[1],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[2],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[1].Children()[3],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[0],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[1],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[2],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[2].Children()[3],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[0],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[1],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[2],
+ CellIDFromFace(5).ChildBeginAtLevel(11).Children()[3].Children()[3],
+ },
+ },
+ {
+ "expended, level < min_level",
+ 10,
+ 3,
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(9),
+ },
+ &CellUnion{
+ CellIDFromFace(2).ChildBeginAtLevel(9).Children()[0],
+ CellIDFromFace(2).ChildBeginAtLevel(9).Children()[1],
+ CellIDFromFace(2).ChildBeginAtLevel(9).Children()[2],
+ CellIDFromFace(2).ChildBeginAtLevel(9).Children()[3],
+ },
+ },
+ }
+ for _, test := range tests {
+ if test.cu.Denormalize(test.minL, test.lMod); !reflect.DeepEqual(test.cu, test.exp) {
+ t.Errorf("test: %s; got %v, want %v", test.name, test.cu, test.exp)
+ }
+ }
+}
+
+func TestCellUnionRectBound(t *testing.T) {
+ tests := []struct {
+ cu *CellUnion
+ want Rect
+ }{
+ {&CellUnion{}, EmptyRect()},
+ {
+ &CellUnion{CellIDFromFace(1)},
+ Rect{
+ r1.Interval{-math.Pi / 4, math.Pi / 4},
+ s1.Interval{math.Pi / 4, 3 * math.Pi / 4},
+ },
+ },
+ {
+ &CellUnion{
+ 0x808c000000000000, // Big SFO
+ },
+ Rect{
+ r1.Interval{
+ float64(s1.Degree * 34.644220547108482),
+ float64(s1.Degree * 38.011928357226651),
+ },
+ s1.Interval{
+ float64(s1.Degree * -124.508522987668428),
+ float64(s1.Degree * -121.628309835221216),
+ },
+ },
+ },
+ {
+ &CellUnion{
+ 0x89c4000000000000, // Big NYC
+ },
+ Rect{
+ r1.Interval{
+ float64(s1.Degree * 38.794595155857657),
+ float64(s1.Degree * 41.747046884651063),
+ },
+ s1.Interval{
+ float64(s1.Degree * -76.456308667788633),
+ float64(s1.Degree * -73.465162142654819),
+ },
+ },
+ },
+ {
+ &CellUnion{
+ 0x89c4000000000000, // Big NYC
+ 0x808c000000000000, // Big SFO
+ },
+ Rect{
+ r1.Interval{
+ float64(s1.Degree * 34.644220547108482),
+ float64(s1.Degree * 41.747046884651063),
+ },
+ s1.Interval{
+ float64(s1.Degree * -124.508522987668428),
+ float64(s1.Degree * -73.465162142654819),
+ },
+ },
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.cu.RectBound(); !rectsApproxEqual(got, test.want, epsilon, epsilon) {
+ t.Errorf("%v.RectBound() = %v, want %v", test.cu, got, test.want)
+ }
+ }
+}
+
+func TestCellUnionLeafCellsCovered(t *testing.T) {
+ tests := []struct {
+ have []CellID
+ want int64
+ }{
+ {},
+ {
+ have: []CellID{},
+ want: 0,
+ },
+ {
+ // One leaf cell on face 0.
+ have: []CellID{
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel),
+ },
+ want: 1,
+ },
+ {
+ // Face 0 itself (which includes the previous leaf cell).
+ have: []CellID{
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel),
+ CellIDFromFace(0),
+ },
+ want: 1 << 60,
+ },
+ /*
+ TODO(roberts): Once Expand is implemented, add the two tests for these
+ // Five faces.
+ cell_union.Expand(0),
+ want: 5 << 60,
+ // Whole world.
+ cell_union.Expand(0),
+ want: 6 << 60,
+ */
+ {
+ // Add some disjoint cells.
+ have: []CellID{
+ CellIDFromFace(0).ChildBeginAtLevel(maxLevel),
+ CellIDFromFace(0),
+ CellIDFromFace(1).ChildBeginAtLevel(1),
+ CellIDFromFace(2).ChildBeginAtLevel(2),
+ CellIDFromFace(2).ChildEndAtLevel(2).Prev(),
+ CellIDFromFace(3).ChildBeginAtLevel(14),
+ CellIDFromFace(4).ChildBeginAtLevel(27),
+ CellIDFromFace(4).ChildEndAtLevel(15).Prev(),
+ CellIDFromFace(5).ChildBeginAtLevel(30),
+ },
+ want: 1 + (1 << 6) + (1 << 30) + (1 << 32) +
+ (2 << 56) + (1 << 58) + (1 << 60),
+ },
+ }
+
+ for _, test := range tests {
+ cu := CellUnion(test.have)
+ cu.Normalize()
+ if got := cu.LeafCellsCovered(); got != test.want {
+ t.Errorf("CellUnion(%v).LeafCellsCovered() = %v, want %v", cu, got, test.want)
+ }
+ }
+}
+
+func TestCellUnionFromRange(t *testing.T) {
+ for iter := 0; iter < 100; iter++ {
+ min := randomCellIDForLevel(maxLevel)
+ max := randomCellIDForLevel(maxLevel)
+ if min > max {
+ min, max = max, min
+ }
+
+ cu := CellUnionFromRange(min, max.Next())
+ if len(cu) <= 0 {
+ t.Errorf("len(CellUnionFromRange(%v, %v)) = %d, want > 0", min, max.Next(), len(cu))
+ }
+ if min != cu[0].RangeMin() {
+ t.Errorf("%v.RangeMin of CellUnion should not be below the minimum value it was created from %v", cu[0], min)
+ }
+ if max != cu[len(cu)-1].RangeMax() {
+ t.Errorf("%v.RangeMax of CellUnion should not be above the maximum value it was created from %v", cu[len(cu)-1], max)
+ }
+ for i := 1; i < len(cu); i++ {
+ if got, want := cu[i].RangeMin(), cu[i-1].RangeMax().Next(); got != want {
+ t.Errorf("%v.RangeMin() = %v, want %v", cu[i], got, want)
+ }
+ }
+ }
+
+ // Focus on test cases that generate an empty or full range.
+
+ // Test an empty range before the minimum CellID.
+ idBegin := CellIDFromFace(0).ChildBeginAtLevel(maxLevel)
+ cu := CellUnionFromRange(idBegin, idBegin)
+ if len(cu) != 0 {
+ t.Errorf("CellUnionFromRange with begin and end as the first CellID should be empty, got %d", len(cu))
+ }
+
+ // Test an empty range after the maximum CellID.
+ idEnd := CellIDFromFace(5).ChildEndAtLevel(maxLevel)
+ cu = CellUnionFromRange(idEnd, idEnd)
+ if len(cu) != 0 {
+ t.Errorf("CellUnionFromRange with begin and end as the last CellID should be empty, got %d", len(cu))
+ }
+
+ // Test the full sphere.
+ cu = CellUnionFromRange(idBegin, idEnd)
+ if len(cu) != 6 {
+ t.Errorf("CellUnionFromRange from first CellID to last CellID should have 6 cells, got %d", len(cu))
+ }
+
+ for i := 0; i < len(cu); i++ {
+ if !cu[i].isFace() {
+ t.Errorf("CellUnionFromRange for full sphere cu[%d].isFace() = %t, want %t", i, cu[i].isFace(), true)
+ }
+ }
+}
+
+func BenchmarkCellUnionFromRange(b *testing.B) {
+ x := CellIDFromFace(0).ChildBeginAtLevel(maxLevel)
+ y := CellIDFromFace(5).ChildEndAtLevel(maxLevel)
+ for i := 0; i < b.N; i++ {
+ CellUnionFromRange(x, y)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/edgeutil.go b/vendor/github.com/golang/geo/s2/edgeutil.go
index c1e5c90..332e5f2 100644
--- a/vendor/github.com/golang/geo/s2/edgeutil.go
+++ b/vendor/github.com/golang/geo/s2/edgeutil.go
@@ -292,7 +292,7 @@ func (r *RectBounder) AddPoint(b Point) {
// and B attains its minimum and maximum latitudes). To test whether AB
// crosses this plane, we compute a vector M perpendicular to this
// plane and then project A and B onto it.
- m := n.Cross(PointFromCoords(0, 0, 1).Vector)
+ m := n.Cross(r3.Vector{0, 0, 1})
mA := m.Dot(r.a.Vector)
mB := m.Dot(b.Vector)
@@ -845,7 +845,7 @@ func clipDestination(a, b, scaledN, aTan, bTan pointUVW, scaleUV float64) (r2.Po
// Otherwise find the point B' where the line AB exits the face.
uv = scaledN.exitPoint(scaledN.exitAxis()).Mul(scaleUV)
- p := pointUVW(PointFromCoords(uv.X, uv.Y, 1.0))
+ p := pointUVW(Point{r3.Vector{uv.X, uv.Y, 1.0}})
// Determine if the exit point B' is contained within the segment. We do this
// by computing the dot products with two inward-facing tangent vectors at A
diff --git a/vendor/github.com/golang/geo/s2/edgeutil_test.go b/vendor/github.com/golang/geo/s2/edgeutil_test.go
new file mode 100644
index 0000000..031645a
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/edgeutil_test.go
@@ -0,0 +1,1201 @@
+/*
+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 (
+ "fmt"
+ "math"
+ "testing"
+
+ "github.com/golang/geo/r1"
+ "github.com/golang/geo/r2"
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+func TestEdgeutilCrossings(t *testing.T) {
+ na1 := math.Nextafter(1, 0)
+ na2 := math.Nextafter(1, 2)
+
+ tests := []struct {
+ msg string
+ a, b, c, d Point
+ simpleTest bool
+ robust Crossing
+ vertex bool
+ edgeOrVertex bool
+ }{
+ {
+ "two regular edges that cross",
+ Point{r3.Vector{1, 2, 1}},
+ Point{r3.Vector{1, -3, 0.5}},
+ Point{r3.Vector{1, -0.5, -3}},
+ Point{r3.Vector{0.1, 0.5, 3}},
+ true,
+ Cross,
+ true,
+ true,
+ },
+ {
+ "two regular edges that cross antipodal points",
+ Point{r3.Vector{1, 2, 1}},
+ Point{r3.Vector{1, -3, 0.5}},
+ Point{r3.Vector{-1, 0.5, 3}},
+ Point{r3.Vector{-0.1, -0.5, -3}},
+ true,
+ DoNotCross,
+ true,
+ false,
+ },
+ {
+ "two edges on the same great circle",
+ Point{r3.Vector{0, 0, -1}},
+ Point{r3.Vector{0, 1, 0}},
+ Point{r3.Vector{0, 1, 1}},
+ Point{r3.Vector{0, 0, 1}},
+ true,
+ DoNotCross,
+ false,
+ false,
+ },
+ {
+ "two edges that cross where one vertex is the OriginPoint",
+ Point{r3.Vector{1, 0, 0}},
+ OriginPoint(),
+ Point{r3.Vector{1, -0.1, 1}},
+ Point{r3.Vector{1, 1, -0.1}},
+ true,
+ Cross,
+ true,
+ true,
+ },
+ {
+ "two edges that cross antipodal points",
+ Point{r3.Vector{1, 0, 0}},
+ Point{r3.Vector{0, 1, 0}},
+ Point{r3.Vector{0, 0, -1}},
+ Point{r3.Vector{-1, -1, 1}},
+ true,
+ DoNotCross,
+ true,
+ false,
+ },
+ {
+ "two edges that share an endpoint",
+ // The Ortho() direction is (-4,0,2) and edge CD
+ // is further CCW around (2,3,4) than AB.
+ Point{r3.Vector{2, 3, 4}},
+ Point{r3.Vector{-1, 2, 5}},
+ Point{r3.Vector{7, -2, 3}},
+ Point{r3.Vector{2, 3, 4}},
+ true,
+ MaybeCross,
+ true,
+ false,
+ },
+ {
+ "two edges that barely cross near the middle of one edge",
+ // The edge AB is approximately in the x=y plane, while CD is approximately
+ // perpendicular to it and ends exactly at the x=y plane.
+ Point{r3.Vector{1, 1, 1}},
+ Point{r3.Vector{1, na1, -1}},
+ Point{r3.Vector{11, -12, -1}},
+ Point{r3.Vector{10, 10, 1}},
+ false,
+ DoNotCross, // TODO(sbeckman): Should be 1, fix once exactSign is implemented.
+ true,
+ false, // TODO(sbeckman): Should be true, fix once exactSign is implemented.
+ },
+ {
+ "two edges that barely cross near the middle separated by a distance of about 1e-15",
+ Point{r3.Vector{1, 1, 1}},
+ Point{r3.Vector{1, na2, -1}},
+ Point{r3.Vector{1, -1, 0}},
+ Point{r3.Vector{1, 1, 0}},
+ false,
+ DoNotCross,
+ false,
+ false,
+ },
+ {
+ "two edges that barely cross each other near the end of both edges",
+ // This example cannot be handled using regular double-precision
+ // arithmetic due to floating-point underflow.
+ Point{r3.Vector{0, 0, 1}},
+ Point{r3.Vector{2, -1e-323, 1}},
+ Point{r3.Vector{1, -1, 1}},
+ Point{r3.Vector{1e-323, 0, 1}},
+ false,
+ DoNotCross, // TODO(sbeckman): Should be 1, fix once exactSign is implemented.
+ false,
+ false, // TODO(sbeckman): Should be true, fix once exactSign is implemented.
+ },
+ {
+ "two edges that barely cross each other near the end separated by a distance of about 1e-640",
+ Point{r3.Vector{0, 0, 1}},
+ Point{r3.Vector{2, 1e-323, 1}},
+ Point{r3.Vector{1, -1, 1}},
+ Point{r3.Vector{1e-323, 0, 1}},
+ false,
+ DoNotCross,
+ false,
+ false,
+ },
+ {
+ "two edges that barely cross each other near the middle of one edge",
+ // Computing the exact determinant of some of the triangles in this test
+ // requires more than 2000 bits of precision.
+ Point{r3.Vector{1, -1e-323, -1e-323}},
+ Point{r3.Vector{1e-323, 1, 1e-323}},
+ Point{r3.Vector{1, -1, 1e-323}},
+ Point{r3.Vector{1, 1, 0}},
+ false,
+ Cross,
+ true,
+ true,
+ },
+ {
+ "two edges that barely cross each other near the middle separated by a distance of about 1e-640",
+ Point{r3.Vector{1, 1e-323, -1e-323}},
+ Point{r3.Vector{-1e-323, 1, 1e-323}},
+ Point{r3.Vector{1, -1, 1e-323}},
+ Point{r3.Vector{1, 1, 0}},
+ false,
+ Cross, // TODO(sbeckman): Should be -1, fix once exactSign is implemented.
+ true,
+ true, // TODO(sbeckman): Should be false, fix once exactSign is implemented.
+ },
+ }
+
+ for _, test := range tests {
+ if err := testCrossing(test.a, test.b, test.c, test.d, test.robust, test.vertex, test.edgeOrVertex, test.simpleTest); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+ if err := testCrossing(test.b, test.a, test.c, test.d, test.robust, test.vertex, test.edgeOrVertex, test.simpleTest); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+ if err := testCrossing(test.a, test.b, test.d, test.c, test.robust, test.vertex, test.edgeOrVertex, test.simpleTest); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+ if err := testCrossing(test.b, test.a, test.c, test.d, test.robust, test.vertex, test.edgeOrVertex, test.simpleTest); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+ if err := testCrossing(test.a, test.b, test.a, test.b, MaybeCross, true, true, false); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+ if err := testCrossing(test.c, test.d, test.a, test.b, test.robust, test.vertex, test.edgeOrVertex != (test.robust == MaybeCross), test.simpleTest); err != nil {
+ t.Errorf("%s: %v", test.msg, err)
+ }
+
+ if got := VertexCrossing(test.a, test.b, test.c, test.b); got != test.vertex {
+ t.Errorf("%s: VertexCrossing(%v,%v,%v,%v) = %t, want %t", test.msg, test.a, test.b, test.c, test.d, got, test.vertex)
+ }
+ }
+}
+
+func testCrossing(a, b, c, d Point, robust Crossing, vertex, edgeOrVertex, simple bool) error {
+ input := fmt.Sprintf("a: %v, b: %v, c: %v, d: %v", a, b, c, d)
+ if got, want := SimpleCrossing(a, b, c, d), robust == Cross; simple && got != want {
+ return fmt.Errorf("%v, SimpleCrossing(a, b, c, d) = %t, want %t", input, got, want)
+ }
+
+ crosser := NewChainEdgeCrosser(a, b, c)
+ if got, want := crosser.ChainCrossingSign(d), robust; got != want {
+ return fmt.Errorf("%v, ChainCrossingSign(d) = %d, want %d", input, got, want)
+ }
+ if got, want := crosser.ChainCrossingSign(c), robust; got != want {
+ return fmt.Errorf("%v, ChainCrossingSign(c) = %d, want %d", input, got, want)
+ }
+ if got, want := crosser.CrossingSign(d, c), robust; got != want {
+ return fmt.Errorf("%v, CrossingSign(d, c) = %d, want %d", input, got, want)
+ }
+ if got, want := crosser.CrossingSign(c, d), robust; got != want {
+ return fmt.Errorf("%v, CrossingSign(c, d) = %d, want %d", input, got, want)
+ }
+
+ crosser.RestartAt(c)
+ if got, want := crosser.EdgeOrVertexChainCrossing(d), edgeOrVertex; got != want {
+ return fmt.Errorf("%v, EdgeOrVertexChainCrossing(d) = %t, want %t", input, got, want)
+ }
+ if got, want := crosser.EdgeOrVertexChainCrossing(c), edgeOrVertex; got != want {
+ return fmt.Errorf("%v, EdgeOrVertexChainCrossing(c) = %t, want %t", input, got, want)
+ }
+ if got, want := crosser.EdgeOrVertexCrossing(d, c), edgeOrVertex; got != want {
+ return fmt.Errorf("%v, EdgeOrVertexCrossing(d, c) = %t, want %t", input, got, want)
+ }
+ if got, want := crosser.EdgeOrVertexCrossing(c, d), edgeOrVertex; got != want {
+ return fmt.Errorf("%v, EdgeOrVertexCrossing(c, d) = %t, want %t", input, got, want)
+ }
+ return nil
+}
+
+func TestEdgeutilInterpolate(t *testing.T) {
+ // Choose test points designed to expose floating-point errors.
+ p1 := PointFromCoords(0.1, 1e-30, 0.3)
+ p2 := PointFromCoords(-0.7, -0.55, -1e30)
+
+ tests := []struct {
+ a, b Point
+ dist float64
+ want Point
+ }{
+ // A zero-length edge.
+ {p1, p1, 0, p1},
+ {p1, p1, 1, p1},
+ // Start, end, and middle of a medium-length edge.
+ {p1, p2, 0, p1},
+ {p1, p2, 1, p2},
+ {p1, p2, 0.5, Point{(p1.Add(p2.Vector)).Mul(0.5)}},
+
+ // Test that interpolation is done using distances on the sphere
+ // rather than linear distances.
+ {
+ Point{r3.Vector{1, 0, 0}},
+ Point{r3.Vector{0, 1, 0}},
+ 1.0 / 3.0,
+ Point{r3.Vector{math.Sqrt(3), 1, 0}},
+ },
+ {
+ Point{r3.Vector{1, 0, 0}},
+ Point{r3.Vector{0, 1, 0}},
+ 2.0 / 3.0,
+ Point{r3.Vector{1, math.Sqrt(3), 0}},
+ },
+ }
+
+ for _, test := range tests {
+ // We allow a bit more than the usual 1e-15 error tolerance because
+ // Interpolate() uses trig functions.
+ if got := Interpolate(test.dist, test.a, test.b); !pointsApproxEquals(got, test.want, 3e-15) {
+ t.Errorf("Interpolate(%v, %v, %v) = %v, want %v", test.dist, test.a, test.b, got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilInterpolateOverLongEdge(t *testing.T) {
+ lng := math.Pi - 1e-2
+ a := Point{PointFromLatLng(LatLng{0, 0}).Normalize()}
+ b := Point{PointFromLatLng(LatLng{0, s1.Angle(lng)}).Normalize()}
+
+ for f := 0.4; f > 1e-15; f *= 0.1 {
+ // Test that interpolation is accurate on a long edge (but not so long that
+ // the definition of the edge itself becomes too unstable).
+ want := Point{PointFromLatLng(LatLng{0, s1.Angle(f * lng)}).Normalize()}
+ if got := Interpolate(f, a, b); !pointsApproxEquals(got, want, 3e-15) {
+ t.Errorf("long edge Interpolate(%v, %v, %v) = %v, want %v", f, a, b, got, want)
+ }
+
+ // Test the remainder of the dist also matches.
+ wantRem := Point{PointFromLatLng(LatLng{0, s1.Angle((1 - f) * lng)}).Normalize()}
+ if got := Interpolate(1-f, a, b); !pointsApproxEquals(got, wantRem, 3e-15) {
+ t.Errorf("long edge Interpolate(%v, %v, %v) = %v, want %v", 1-f, a, b, got, wantRem)
+ }
+ }
+}
+
+func TestEdgeutilInterpolateAntipodal(t *testing.T) {
+ p1 := PointFromCoords(0.1, 1e-30, 0.3)
+
+ // Test that interpolation on a 180 degree edge (antipodal endpoints) yields
+ // a result with the correct distance from each endpoint.
+ for dist := 0.0; dist <= 1.0; dist += 0.125 {
+ actual := Interpolate(dist, p1, Point{p1.Mul(-1)})
+ if !float64Near(actual.Distance(p1).Radians(), dist*math.Pi, 3e-15) {
+ t.Errorf("antipodal points Interpolate(%v, %v, %v) = %v, want %v", dist, p1, Point{p1.Mul(-1)}, actual, dist*math.Pi)
+ }
+ }
+}
+
+func TestEdgeutilRepeatedInterpolation(t *testing.T) {
+ // Check that points do not drift away from unit length when repeated
+ // interpolations are done.
+ for i := 0; i < 100; i++ {
+ a := randomPoint()
+ b := randomPoint()
+ for j := 0; j < 1000; j++ {
+ a = Interpolate(0.01, a, b)
+ }
+ if !a.Vector.IsUnit() {
+ t.Errorf("repeated Interpolate(%v, %v, %v) calls did not stay unit length for", 0.01, a, b)
+ }
+ }
+}
+
+func rectBoundForPoints(a, b Point) Rect {
+ bounder := NewRectBounder()
+ bounder.AddPoint(a)
+ bounder.AddPoint(b)
+ return bounder.RectBound()
+}
+
+func TestEdgeutilRectBounderMaxLatitudeSimple(t *testing.T) {
+ cubeLat := math.Asin(1 / math.Sqrt(3)) // 35.26 degrees
+ cubeLatRect := Rect{r1.IntervalFromPoint(-cubeLat).AddPoint(cubeLat),
+ s1.IntervalFromEndpoints(-math.Pi/4, math.Pi/4)}
+
+ tests := []struct {
+ a, b Point
+ want Rect
+ }{
+ // Check cases where the min/max latitude is attained at a vertex.
+ {
+ a: Point{r3.Vector{1, 1, 1}},
+ b: Point{r3.Vector{1, -1, -1}},
+ want: cubeLatRect,
+ },
+ {
+ a: Point{r3.Vector{1, -1, 1}},
+ b: Point{r3.Vector{1, 1, -1}},
+ want: cubeLatRect,
+ },
+ }
+
+ for _, test := range tests {
+ if got := rectBoundForPoints(test.a, test.b); !rectsApproxEqual(got, test.want, rectErrorLat, rectErrorLng) {
+ t.Errorf("RectBounder for points (%v, %v) near max lat failed: got %v, want %v", test.a, test.b, got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilRectBounderMaxLatitudeEdgeInterior(t *testing.T) {
+ // Check cases where the min/max latitude occurs in the edge interior.
+ // These tests expect the result to be pretty close to the middle of the
+ // allowable error range (i.e., by adding 0.5 * kRectError).
+
+ tests := []struct {
+ got float64
+ want float64
+ }{
+ // Max latitude, CW edge
+ {
+ math.Pi/4 + 0.5*rectErrorLat,
+ rectBoundForPoints(Point{r3.Vector{1, 1, 1}}, Point{r3.Vector{1, -1, 1}}).Lat.Hi,
+ },
+ // Min latitude, CW edge
+ {
+ -math.Pi/4 - 0.5*rectErrorLat,
+ rectBoundForPoints(Point{r3.Vector{1, -1, -1}}, Point{r3.Vector{-1, -1, -1}}).Lat.Lo,
+ },
+ // Max latitude, CCW edge
+ {
+ math.Pi/4 + 0.5*rectErrorLat,
+ rectBoundForPoints(Point{r3.Vector{1, -1, 1}}, Point{r3.Vector{1, 1, 1}}).Lat.Hi,
+ },
+ // Min latitude, CCW edge
+ {
+ -math.Pi/4 - 0.5*rectErrorLat,
+ rectBoundForPoints(Point{r3.Vector{-1, 1, -1}}, Point{r3.Vector{-1, -1, -1}}).Lat.Lo,
+ },
+
+ // Check cases where the edge passes through one of the poles.
+ {
+ math.Pi / 2,
+ rectBoundForPoints(Point{r3.Vector{.3, .4, 1}}, Point{r3.Vector{-.3, -.4, 1}}).Lat.Hi,
+ },
+ {
+ -math.Pi / 2,
+ rectBoundForPoints(Point{r3.Vector{.3, .4, -1}}, Point{r3.Vector{-.3, -.4, -1}}).Lat.Lo,
+ },
+ }
+
+ for _, test := range tests {
+ if !float64Eq(test.got, test.want) {
+ t.Errorf("RectBound for max lat on interior of edge failed; got %v want %v", test.got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilRectBounderMaxLatitudeRandom(t *testing.T) {
+ // Check that the maximum latitude of edges is computed accurately to within
+ // 3 * dblEpsilon (the expected maximum error). We concentrate on maximum
+ // latitudes near the equator and north pole since these are the extremes.
+
+ for i := 0; i < 100; i++ {
+ // Construct a right-handed coordinate frame (U,V,W) such that U points
+ // slightly above the equator, V points at the equator, and W is slightly
+ // offset from the north pole.
+ u := randomPoint()
+ u.Z = dblEpsilon * 1e-6 * math.Pow(1e12, randomFloat64())
+
+ u = Point{u.Normalize()}
+ v := Point{PointFromCoords(0, 0, 1).PointCross(u).Normalize()}
+ w := Point{u.PointCross(v).Normalize()}
+
+ // Construct a line segment AB that passes through U, and check that the
+ // maximum latitude of this segment matches the latitude of U.
+ a := Point{u.Sub(v.Mul(randomFloat64())).Normalize()}
+ b := Point{u.Add(v.Mul(randomFloat64())).Normalize()}
+ abBound := rectBoundForPoints(a, b)
+ if !float64Near(latitude(u).Radians(), abBound.Lat.Hi, rectErrorLat) {
+ t.Errorf("bound for line AB not near enough to the latitude of point %v. got %v, want %v",
+ u, latitude(u).Radians(), abBound.Lat.Hi)
+ }
+
+ // Construct a line segment CD that passes through W, and check that the
+ // maximum latitude of this segment matches the latitude of W.
+ c := Point{w.Sub(v.Mul(randomFloat64())).Normalize()}
+ d := Point{w.Add(v.Mul(randomFloat64())).Normalize()}
+ cdBound := rectBoundForPoints(c, d)
+ if !float64Near(latitude(w).Radians(), cdBound.Lat.Hi, rectErrorLat) {
+ t.Errorf("bound for line CD not near enough to the lat of point %v. got %v, want %v",
+ v, latitude(w).Radians(), cdBound.Lat.Hi)
+ }
+ }
+}
+
+func TestEdgeutilExpandForSubregions(t *testing.T) {
+ // Test the full and empty bounds.
+ if !ExpandForSubregions(FullRect()).IsFull() {
+ t.Errorf("Subregion Bound of full rect should be full")
+ }
+ if !ExpandForSubregions(EmptyRect()).IsEmpty() {
+ t.Errorf("Subregion Bound of empty rect should be empty")
+ }
+
+ tests := []struct {
+ xLat, xLng, yLat, yLng float64
+ wantFull bool
+ }{
+ // Cases where the bound does not straddle the equator (but almost does),
+ // and spans nearly 180 degrees in longitude.
+ {3e-16, 0, 1e-14, math.Pi, true},
+ {9e-16, 0, 1e-14, math.Pi, false},
+ {1e-16, 7e-16, 1e-14, math.Pi, true},
+ {3e-16, 14e-16, 1e-14, math.Pi, false},
+ {1e-100, 14e-16, 1e-14, math.Pi, true},
+ {1e-100, 22e-16, 1e-14, math.Pi, false},
+ // Cases where the bound spans at most 90 degrees in longitude, and almost
+ // 180 degrees in latitude. Note that DBL_EPSILON is about 2.22e-16, which
+ // implies that the double-precision value just below Pi/2 can be written as
+ // (math.Pi/2 - 2e-16).
+ {-math.Pi / 2, -1e-15, math.Pi/2 - 7e-16, 0, true},
+ {-math.Pi / 2, -1e-15, math.Pi/2 - 30e-16, 0, false},
+ {-math.Pi/2 + 4e-16, 0, math.Pi/2 - 2e-16, 1e-7, true},
+ {-math.Pi/2 + 30e-16, 0, math.Pi / 2, 1e-7, false},
+ {-math.Pi/2 + 4e-16, 0, math.Pi/2 - 4e-16, math.Pi / 2, true},
+ {-math.Pi / 2, 0, math.Pi/2 - 30e-16, math.Pi / 2, false},
+ // Cases where the bound straddles the equator and spans more than 90
+ // degrees in longitude. These are the cases where the critical distance is
+ // between a corner of the bound and the opposite longitudinal edge. Unlike
+ // the cases above, here the bound may contain nearly-antipodal points (to
+ // within 3.055 * DBL_EPSILON) even though the latitude and longitude ranges
+ // are both significantly less than (math.Pi - 3.055 * DBL_EPSILON).
+ {-math.Pi / 2, 0, math.Pi/2 - 1e-8, math.Pi - 1e-7, true},
+ {-math.Pi / 2, 0, math.Pi/2 - 1e-7, math.Pi - 1e-7, false},
+ {-math.Pi/2 + 1e-12, -math.Pi + 1e-4, math.Pi / 2, 0, true},
+ {-math.Pi/2 + 1e-11, -math.Pi + 1e-4, math.Pi / 2, 0, true},
+ }
+
+ for _, tc := range tests {
+ in := RectFromLatLng(LatLng{s1.Angle(tc.xLat), s1.Angle(tc.xLng)})
+ in = in.AddPoint(LatLng{s1.Angle(tc.yLat), s1.Angle(tc.yLng)})
+ got := ExpandForSubregions(in)
+
+ // Test that the bound is actually expanded.
+ if !got.Contains(in) {
+ t.Errorf("Subregion bound of (%f, %f, %f, %f) should contain original rect", tc.xLat, tc.xLng, tc.yLat, tc.yLng)
+ }
+ if in.Lat == validRectLatRange && in.Lat.ContainsInterval(got.Lat) {
+ t.Errorf("Subregion bound of (%f, %f, %f, %f) shouldn't be contained by original rect", tc.xLat, tc.xLng, tc.yLat, tc.yLng)
+ }
+
+ // We check the various situations where the bound contains nearly-antipodal points. The tests are organized into pairs
+ // where the two bounds are similar except that the first bound meets the nearly-antipodal criteria while the second does not.
+ if got.IsFull() != tc.wantFull {
+ t.Errorf("Subregion Bound of (%f, %f, %f, %f).IsFull should be %t", tc.xLat, tc.xLng, tc.yLat, tc.yLng, tc.wantFull)
+ }
+ }
+
+ rectTests := []struct {
+ xLat, xLng, yLat, yLng float64
+ wantRect Rect
+ }{
+ {1.5, -math.Pi / 2, 1.5, math.Pi/2 - 2e-16, Rect{r1.Interval{1.5, 1.5}, s1.FullInterval()}},
+ {1.5, -math.Pi / 2, 1.5, math.Pi/2 - 7e-16, Rect{r1.Interval{1.5, 1.5}, s1.Interval{-math.Pi / 2, math.Pi/2 - 7e-16}}},
+ // Check for cases where the bound is expanded to include one of the poles
+ {-math.Pi/2 + 1e-15, 0, -math.Pi/2 + 1e-15, 0, Rect{r1.Interval{-math.Pi / 2, -math.Pi/2 + 1e-15}, s1.FullInterval()}},
+ {math.Pi/2 - 1e-15, 0, math.Pi/2 - 1e-15, 0, Rect{r1.Interval{math.Pi/2 - 1e-15, math.Pi / 2}, s1.FullInterval()}},
+ }
+
+ for _, tc := range rectTests {
+ // Now we test cases where the bound does not contain nearly-antipodal
+ // points, but it does contain points that are approximately 180 degrees
+ // apart in latitude.
+ in := RectFromLatLng(LatLng{s1.Angle(tc.xLat), s1.Angle(tc.xLng)})
+ in = in.AddPoint(LatLng{s1.Angle(tc.yLat), s1.Angle(tc.yLng)})
+ got := ExpandForSubregions(in)
+ if !rectsApproxEqual(got, tc.wantRect, rectErrorLat, rectErrorLng) {
+ t.Errorf("Subregion Bound of (%f, %f, %f, %f) = (%v) should be %v", tc.xLat, tc.xLng, tc.yLat, tc.yLng, got, tc.wantRect)
+ }
+ }
+}
+
+func TestEdgeutilIntersectsFace(t *testing.T) {
+ tests := []struct {
+ a pointUVW
+ want bool
+ }{
+ {pointUVW{r3.Vector{2.05335e-06, 3.91604e-22, 2.90553e-06}}, false},
+ {pointUVW{r3.Vector{-3.91604e-22, -2.05335e-06, -2.90553e-06}}, false},
+ {pointUVW{r3.Vector{0.169258, -0.169258, 0.664013}}, false},
+ {pointUVW{r3.Vector{0.169258, -0.169258, -0.664013}}, false},
+ {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 3.88578e-16}}, true},
+ {pointUVW{r3.Vector{-3.88578e-16, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, true},
+ }
+
+ for _, test := range tests {
+ if got := test.a.intersectsFace(); got != test.want {
+ t.Errorf("%v.intersectsFace() = %v, want %v", test.a, got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilIntersectsOppositeEdges(t *testing.T) {
+ tests := []struct {
+ a pointUVW
+ want bool
+ }{
+ {pointUVW{r3.Vector{0.169258, -0.169258, 0.664013}}, false},
+ {pointUVW{r3.Vector{0.169258, -0.169258, -0.664013}}, false},
+
+ {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), 0, -math.Sqrt(4.0 / 3.0)}}, true},
+ {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), 0, math.Sqrt(4.0 / 3.0)}}, true},
+
+ {pointUVW{r3.Vector{-math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 1.66533453694e-16}}, false},
+ {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0), -1.66533453694e-16}}, false},
+ }
+ for _, test := range tests {
+ if got := test.a.intersectsOppositeEdges(); got != test.want {
+ t.Errorf("%v.intersectsOppositeEdges() = %v, want %v", test.a, got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilExitAxis(t *testing.T) {
+ tests := []struct {
+ a pointUVW
+ want axis
+ }{
+ {pointUVW{r3.Vector{0, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, axisU},
+ {pointUVW{r3.Vector{0, math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0)}}, axisU},
+ {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV},
+ {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), math.Sqrt(4.0 / 3.0), 0}}, axisV},
+ {pointUVW{r3.Vector{math.Sqrt(2.0 / 3.0), -math.Sqrt(2.0 / 3.0), 0}}, axisV},
+ {pointUVW{r3.Vector{1.67968702783622, 0, 0.870988820096491}}, axisV},
+ {pointUVW{r3.Vector{0, math.Sqrt2, math.Sqrt2}}, axisU},
+ }
+
+ for _, test := range tests {
+ if got := test.a.exitAxis(); got != test.want {
+ t.Errorf("%v.exitAxis() = %v, want %v", test.a, got, test.want)
+ }
+ }
+}
+
+func TestEdgeutilExitPoint(t *testing.T) {
+ tests := []struct {
+ a pointUVW
+ exitAxis axis
+ want r2.Point
+ }{
+ {pointUVW{r3.Vector{-3.88578058618805e-16, -math.Sqrt(2.0 / 3.0), math.Sqrt(2.0 / 3.0)}}, axisU, r2.Point{-1, 1}},
+ {pointUVW{r3.Vector{math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV, r2.Point{-1, -1}},
+ {pointUVW{r3.Vector{-math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0), 0}}, axisV, r2.Point{-1, 1}},
+ {pointUVW{r3.Vector{-6.66134e-16, math.Sqrt(4.0 / 3.0), -math.Sqrt(4.0 / 3.0)}}, axisU, r2.Point{1, 1}},
+ }
+
+ for _, test := range tests {
+ if got := test.a.exitPoint(test.exitAxis); !r2PointsApproxEquals(got, test.want, epsilon) {
+ t.Errorf("%v.exitPoint() = %v, want %v", test.a, got, test.want)
+ }
+ }
+}
+
+// testClipToPaddedFace performs a comprehensive set of tests across all faces and
+// with random padding for the given points.
+//
+// We do this by defining an (x,y) coordinate system for the plane containing AB,
+// and converting points along the great circle AB to angles in the range
+// [-Pi, Pi]. We then accumulate the angle intervals spanned by each
+// clipped edge; the union over all 6 faces should approximately equal the
+// interval covered by the original edge.
+func testClipToPaddedFace(t *testing.T, a, b Point) {
+ a = Point{a.Normalize()}
+ b = Point{b.Normalize()}
+ if a.Vector == b.Mul(-1) {
+ return
+ }
+
+ norm := Point{a.PointCross(b).Normalize()}
+ aTan := Point{norm.Cross(a.Vector)}
+
+ padding := 0.0
+ if !oneIn(10) {
+ padding = 1e-10 * math.Pow(1e-5, randomFloat64())
+ }
+
+ xAxis := a
+ yAxis := aTan
+
+ // Given the points A and B, we expect all angles generated from the clipping
+ // to fall within this range.
+ expectedAngles := s1.Interval{0, float64(a.Angle(b.Vector))}
+ if expectedAngles.IsInverted() {
+ expectedAngles = s1.Interval{expectedAngles.Hi, expectedAngles.Lo}
+ }
+ maxAngles := expectedAngles.Expanded(faceClipErrorRadians)
+ var actualAngles s1.Interval
+
+ for face := 0; face < 6; face++ {
+ aUV, bUV, intersects := ClipToPaddedFace(a, b, face, padding)
+ if !intersects {
+ continue
+ }
+
+ aClip := Point{faceUVToXYZ(face, aUV.X, aUV.Y).Normalize()}
+ bClip := Point{faceUVToXYZ(face, bUV.X, bUV.Y).Normalize()}
+
+ desc := fmt.Sprintf("on face %d, a=%v, b=%v, aClip=%v, bClip=%v,", face, a, b, aClip, bClip)
+
+ if got := math.Abs(aClip.Dot(norm.Vector)); got > faceClipErrorRadians {
+ t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, aClip, norm, got, faceClipErrorRadians)
+ }
+ if got := math.Abs(bClip.Dot(norm.Vector)); got > faceClipErrorRadians {
+ t.Errorf("%s abs(%v.Dot(%v)) = %v, want <= %v", desc, bClip, norm, got, faceClipErrorRadians)
+ }
+
+ if float64(aClip.Angle(a.Vector)) > faceClipErrorRadians {
+ if got := math.Max(math.Abs(aUV.X), math.Abs(aUV.Y)); !float64Eq(got, 1+padding) {
+ t.Errorf("%s the largest component of %v = %v, want %v", desc, aUV, got, 1+padding)
+ }
+ }
+ if float64(bClip.Angle(b.Vector)) > faceClipErrorRadians {
+ if got := math.Max(math.Abs(bUV.X), math.Abs(bUV.Y)); !float64Eq(got, 1+padding) {
+ t.Errorf("%s the largest component of %v = %v, want %v", desc, bUV, got, 1+padding)
+ }
+ }
+
+ aAngle := math.Atan2(aClip.Dot(yAxis.Vector), aClip.Dot(xAxis.Vector))
+ bAngle := math.Atan2(bClip.Dot(yAxis.Vector), bClip.Dot(xAxis.Vector))
+
+ // Rounding errors may cause bAngle to be slightly less than aAngle.
+ // We handle this by constructing the interval with FromPointPair,
+ // which is okay since the interval length is much less than math.Pi.
+ faceAngles := s1.IntervalFromEndpoints(aAngle, bAngle)
+ if faceAngles.IsInverted() {
+ faceAngles = s1.Interval{faceAngles.Hi, faceAngles.Lo}
+ }
+ if !maxAngles.ContainsInterval(faceAngles) {
+ t.Errorf("%s %v.ContainsInterval(%v) = false, but should have contained this interval", desc, maxAngles, faceAngles)
+ }
+ actualAngles = actualAngles.Union(faceAngles)
+ }
+ if !actualAngles.Expanded(faceClipErrorRadians).ContainsInterval(expectedAngles) {
+ t.Errorf("the union of all angle segments should be larger than the expected angle")
+ }
+}
+
+func TestEdgeutilFaceClipping(t *testing.T) {
+ // Start with a few simple cases.
+ // An edge that is entirely contained within one cube face:
+ testClipToPaddedFace(t, Point{r3.Vector{1, -0.5, -0.5}}, Point{r3.Vector{1, 0.5, 0.5}})
+ testClipToPaddedFace(t, Point{r3.Vector{1, 0.5, 0.5}}, Point{r3.Vector{1, -0.5, -0.5}})
+ // An edge that crosses one cube edge:
+ testClipToPaddedFace(t, Point{r3.Vector{1, 0, 0}}, Point{r3.Vector{0, 1, 0}})
+ testClipToPaddedFace(t, Point{r3.Vector{0, 1, 0}}, Point{r3.Vector{1, 0, 0}})
+ // An edge that crosses two opposite edges of face 0:
+ testClipToPaddedFace(t, Point{r3.Vector{0.75, 0, -1}}, Point{r3.Vector{0.75, 0, 1}})
+ testClipToPaddedFace(t, Point{r3.Vector{0.75, 0, 1}}, Point{r3.Vector{0.75, 0, -1}})
+ // An edge that crosses two adjacent edges of face 2:
+ testClipToPaddedFace(t, Point{r3.Vector{1, 0, 0.75}}, Point{r3.Vector{0, 1, 0.75}})
+ testClipToPaddedFace(t, Point{r3.Vector{0, 1, 0.75}}, Point{r3.Vector{1, 0, 0.75}})
+ // An edges that crosses three cube edges (four faces):
+ testClipToPaddedFace(t, Point{r3.Vector{1, 0.9, 0.95}}, Point{r3.Vector{-1, 0.95, 0.9}})
+ testClipToPaddedFace(t, Point{r3.Vector{-1, 0.95, 0.9}}, Point{r3.Vector{1, 0.9, 0.95}})
+
+ // Comprehensively test edges that are difficult to handle, especially those
+ // that nearly follow one of the 12 cube edges.
+ biunit := r2.Rect{r1.Interval{-1, 1}, r1.Interval{-1, 1}}
+
+ for i := 0; i < 1000; i++ {
+ // Choose two adjacent cube corners P and Q.
+ face := randomUniformInt(6)
+ i := randomUniformInt(4)
+ j := (i + 1) & 3
+ p := Point{faceUVToXYZ(face, biunit.Vertices()[i].X, biunit.Vertices()[i].Y)}
+ q := Point{faceUVToXYZ(face, biunit.Vertices()[j].X, biunit.Vertices()[j].Y)}
+
+ // Now choose two points that are nearly in the plane of PQ, preferring
+ // points that are near cube corners, face midpoints, or edge midpoints.
+ a := perturbedCornerOrMidpoint(p, q)
+ b := perturbedCornerOrMidpoint(p, q)
+ testClipToPaddedFace(t, a, b)
+ }
+}
+
+// getFraction returns the fraction t of the given point X on the line AB such that
+// x = (1-t)*a + t*b. Returns 0 if A = B.
+func getFraction(t *testing.T, x, a, b r2.Point) float64 {
+ // A bound for the error in edge clipping plus the error in the calculation
+ // (which is similar to EdgeIntersectsRect).
+ errorDist := (edgeClipErrorUVDist + intersectsRectErrorUVDist)
+ if a == b {
+ return 0.0
+ }
+ dir := b.Sub(a).Normalize()
+ if got := math.Abs(x.Sub(a).Dot(dir.Ortho())); got > errorDist {
+ t.Errorf("getFraction(%v, %v, %v) = %v, which exceeds errorDist %v", x, a, b, got, errorDist)
+ }
+ return x.Sub(a).Dot(dir)
+}
+
+// randomPointFromInterval returns a randomly selected point from the given interval
+// with one of three possible choices. All cases have reasonable probability for any
+// interval. The choices are: randomly choose a value inside the interval, choose a
+// value outside the interval, or select one of the two endpoints.
+func randomPointFromInterval(clip r1.Interval) float64 {
+ if oneIn(5) {
+ if oneIn(2) {
+ return clip.Lo
+ }
+ return clip.Hi
+ }
+
+ switch randomUniformInt(3) {
+ case 0:
+ return clip.Lo - randomFloat64()
+ case 1:
+ return clip.Hi + randomFloat64()
+ default:
+ return clip.Lo + randomFloat64()*clip.Length()
+ }
+}
+
+// Given a rectangle "clip", choose a point that may lie in the rectangle interior, along an extended edge, exactly at a vertex, or in one of the eight regions exterior to "clip" that are separated by its extended edges. Also sometimes return points that are exactly on one of the extended diagonals of "clip". All cases are reasonably likely to occur for any given rectangle "clip".
+func chooseRectEndpoint(clip r2.Rect) r2.Point {
+ if oneIn(10) {
+ // Return a point on one of the two extended diagonals.
+ diag := randomUniformInt(2)
+ t := randomUniformFloat64(-1, 2)
+ return clip.Vertices()[diag].Mul(1 - t).Add(clip.Vertices()[diag+2].Mul(t))
+ }
+ return r2.Point{randomPointFromInterval(clip.X), randomPointFromInterval(clip.Y)}
+}
+
+// Choose a random point in the rectangle defined by points A and B, sometimes
+// returning a point on the edge AB or the points A and B themselves.
+func choosePointInRect(a, b r2.Point) r2.Point {
+ if oneIn(5) {
+ if oneIn(2) {
+ return a
+ }
+ return b
+ }
+
+ if oneIn(3) {
+ return a.Add(b.Sub(a).Mul(randomFloat64()))
+ }
+ return r2.Point{randomUniformFloat64(a.X, b.X), randomUniformFloat64(a.Y, b.Y)}
+}
+
+// Given a point P representing a possibly clipped endpoint A of an edge AB,
+// verify that clip contains P, and that if clipping occurred (i.e., P != A)
+// then P is on the boundary of clip.
+func checkPointOnBoundary(t *testing.T, p, a r2.Point, clip r2.Rect) {
+ if got := clip.ContainsPoint(p); !got {
+ t.Errorf("%v.ContainsPoint(%v) = %v, want true", clip, p, got)
+ }
+ if p != a {
+ p1 := r2.Point{math.Nextafter(p.X, a.X), math.Nextafter(p.Y, a.Y)}
+ if got := clip.ContainsPoint(p1); got {
+ t.Errorf("%v.ContainsPoint(%v) = %v, want false", clip, p1, got)
+ }
+ }
+}
+
+func TestEdgeutilEdgeClipping(t *testing.T) {
+ // A bound for the error in edge clipping plus the error in the
+ // EdgeIntersectsRect calculation below.
+ errorDist := (edgeClipErrorUVDist + intersectsRectErrorUVDist)
+ testRects := []r2.Rect{
+ // Test clipping against random rectangles.
+ r2.RectFromPoints(
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
+ r2.RectFromPoints(
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
+ r2.RectFromPoints(
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
+ r2.RectFromPoints(
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
+ r2.RectFromPoints(
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)},
+ r2.Point{randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1)}),
+
+ // Also clip against one-dimensional, singleton, and empty rectangles.
+ r2.Rect{r1.Interval{-0.7, -0.7}, r1.Interval{0.3, 0.35}},
+ r2.Rect{r1.Interval{0.2, 0.5}, r1.Interval{0.3, 0.3}},
+ r2.Rect{r1.Interval{-0.7, 0.3}, r1.Interval{0, 0}},
+ r2.RectFromPoints(r2.Point{0.3, 0.8}),
+ r2.EmptyRect(),
+ }
+
+ for _, r := range testRects {
+ for i := 0; i < 1000; i++ {
+ a := chooseRectEndpoint(r)
+ b := chooseRectEndpoint(r)
+
+ aClip, bClip, intersects := ClipEdge(a, b, r)
+ if !intersects {
+ if edgeIntersectsRect(a, b, r.ExpandedByMargin(-errorDist)) {
+ t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = true, want false", a, b, r, -errorDist)
+ }
+ } else {
+ if !edgeIntersectsRect(a, b, r.ExpandedByMargin(errorDist)) {
+ t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = false, want true", a, b, r, errorDist)
+ }
+
+ // Check that the clipped points lie on the edge AB, and
+ // that the points have the expected order along the segment AB.
+ if gotA, gotB := getFraction(t, aClip, a, b), getFraction(t, bClip, a, b); gotA > gotB {
+ t.Errorf("getFraction(%v,%v,%v) = %v, getFraction(%v, %v, %v) = %v; %v < %v = false, want true", aClip, a, b, gotA, bClip, a, b, gotB, gotA, gotB)
+ }
+
+ // Check that the clipped portion of AB is as large as possible.
+ checkPointOnBoundary(t, aClip, a, r)
+ checkPointOnBoundary(t, bClip, b, r)
+ }
+
+ // Choose an random initial bound to pass to clipEdgeBound.
+ initialClip := r2.RectFromPoints(choosePointInRect(a, b), choosePointInRect(a, b))
+ bound := clippedEdgeBound(a, b, initialClip)
+ if bound.IsEmpty() {
+ // Precondition of clipEdgeBound not met
+ continue
+ }
+ maxBound := bound.Intersection(r)
+ if bound, intersects := clipEdgeBound(a, b, r, bound); !intersects {
+ if edgeIntersectsRect(a, b, maxBound.ExpandedByMargin(-errorDist)) {
+ t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = true, want false", a, b, maxBound.ExpandedByMargin(-errorDist), -errorDist)
+ }
+ } else {
+ if !edgeIntersectsRect(a, b, maxBound.ExpandedByMargin(errorDist)) {
+ t.Errorf("edgeIntersectsRect(%v, %v, %v.ExpandedByMargin(%v) = false, want true", a, b, maxBound.ExpandedByMargin(errorDist), errorDist)
+ }
+ // check that the bound is as large as possible.
+ ai := 0
+ if a.X > b.X {
+ ai = 1
+ }
+ aj := 0
+ if a.Y > b.Y {
+ aj = 1
+ }
+ checkPointOnBoundary(t, bound.VertexIJ(ai, aj), a, maxBound)
+ checkPointOnBoundary(t, bound.VertexIJ(1-ai, 1-aj), b, maxBound)
+ }
+ }
+ }
+}
+
+func TestCheckDistance(t *testing.T) {
+ // Uncomment once Distance / UpdateMinDistance are implemented.
+ //var zeroChordAngle s1.ChordAngle
+ tests := []struct {
+ x, a, b r3.Vector
+ distRad float64
+ want r3.Vector
+ }{
+ {
+ x: r3.Vector{1, 0, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: 0,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{0, 1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: 0,
+ want: r3.Vector{0, 1, 0},
+ },
+ {
+ x: r3.Vector{1, 3, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: 0,
+ want: r3.Vector{1, 3, 0},
+ },
+ {
+ x: r3.Vector{0, 0, 1},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{0, 0, -1},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{-1, -1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: 0.75 * math.Pi,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{0, 1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{1, 1, 0},
+ distRad: math.Pi / 4,
+ want: r3.Vector{1, 1, 0},
+ },
+ {
+ x: r3.Vector{0, -1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{1, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{0, -1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{-1, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{1, 0, 0},
+ },
+ {
+ x: r3.Vector{-1, -1, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{-1, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{-1, 1, 0},
+ },
+ {
+ x: r3.Vector{1, 1, 1},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: math.Asin(math.Sqrt(1.0 / 3.0)),
+ want: r3.Vector{1, 1, 0},
+ },
+ {
+ x: r3.Vector{1, 1, -1},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{0, 1, 0},
+ distRad: math.Asin(math.Sqrt(1.0 / 3.0)),
+ want: r3.Vector{1, 1, 0}},
+ {
+ x: r3.Vector{-1, 0, 0},
+ a: r3.Vector{1, 1, 0},
+ b: r3.Vector{1, 1, 0},
+ distRad: 0.75 * math.Pi,
+ want: r3.Vector{1, 1, 0},
+ },
+ {
+ x: r3.Vector{0, 0, -1},
+ a: r3.Vector{1, 1, 0},
+ b: r3.Vector{1, 1, 0},
+ distRad: math.Pi / 2,
+ want: r3.Vector{1, 1, 0},
+ },
+ {
+ x: r3.Vector{-1, 0, 0},
+ a: r3.Vector{1, 0, 0},
+ b: r3.Vector{1, 0, 0},
+ distRad: math.Pi,
+ want: r3.Vector{1, 0, 0},
+ },
+ }
+
+ for _, test := range tests {
+ x := Point{test.x.Normalize()}
+ a := Point{test.a.Normalize()}
+ b := Point{test.b.Normalize()}
+ want := Point{test.want.Normalize()}
+
+ if d := DistanceFromSegment(x, a, b).Radians(); !float64Near(d, test.distRad, 1e-15) {
+ t.Errorf("DistanceFromSegment(%v, %v, %v) = %v, want %v", x, a, b, d, test.distRad)
+ }
+
+ closest := ClosestPoint(x, a, b)
+ if !closest.ApproxEqual(want) {
+ t.Errorf("ClosestPoint(%v, %v, %v) = %v, want %v", x, a, b, closest, want)
+ }
+
+ // Uncomment these once Distance / UpdateMinDistance are implemented.
+ //minDistance := zeroChordAngle
+ //if minDistance, ok := UpdateMinDistance(x, a, b, minDistance); ok {
+ // t.Errorf("UpdateMinDistance(%x, %v, %v, %v) = %v, want %v", x, a, b, zeroChordAngle, minDistance, zeroChordAngle)
+ //}
+ //
+ //minDistance = s1.InfChordAngle()
+ //if minDistance, ok := UpdateMinDistance(x, a, b, minDistance); !ok {
+ // t.Errorf("UpdateMinDistance(%x, %v, %v, %v) = %v, want %v", x, a, b, s1.InfChordAngle(), minDistance, s1.InfChordAngle())
+ //}
+ //
+ //if !float64Near(test.distRad, minDistance.Angle().Radians(), 1e-15) {
+ // t.Errorf("%v != %v", minDistance.Angle().Radians(), test.distRad)
+ //}
+ }
+}
+
+func TestEdgeUtilWedges(t *testing.T) {
+ // For simplicity, all of these tests use an origin of (0, 0, 1).
+ // This shouldn't matter as long as the lower-level primitives are
+ // implemented correctly.
+ ab1 := Point{r3.Vector{0, 0, 1}}
+
+ tests := []struct {
+ desc string
+ a0, a1, b0, b1 Point
+ contains bool
+ intersects bool
+ relation WedgeRel
+ }{
+ {
+ desc: "Intersection in one wedge",
+ a0: Point{r3.Vector{-1, 0, 10}},
+ a1: Point{r3.Vector{1, 2, 10}},
+ b0: Point{r3.Vector{0, 1, 10}},
+ b1: Point{r3.Vector{1, -2, 10}},
+ contains: false,
+ intersects: true,
+ relation: WedgeProperlyOverlaps,
+ },
+ {
+ desc: "Intersection in two wedges",
+ a0: Point{r3.Vector{-1, -1, 10}},
+ a1: Point{r3.Vector{1, -1, 10}},
+ b0: Point{r3.Vector{1, 0, 10}},
+ b1: Point{r3.Vector{-1, 1, 10}},
+ contains: false,
+ intersects: true,
+ relation: WedgeProperlyOverlaps,
+ },
+ {
+ desc: "Normal containment",
+ a0: Point{r3.Vector{-1, -1, 10}},
+ a1: Point{r3.Vector{1, -1, 10}},
+ b0: Point{r3.Vector{-1, 0, 10}},
+ b1: Point{r3.Vector{1, 0, 10}},
+ contains: true,
+ intersects: true,
+ relation: WedgeProperlyContains,
+ },
+ {
+ desc: "Containment with equality on one side",
+ a0: Point{r3.Vector{2, 1, 10}},
+ a1: Point{r3.Vector{-1, -1, 10}},
+ b0: Point{r3.Vector{2, 1, 10}},
+ b1: Point{r3.Vector{1, -5, 10}},
+ contains: true,
+ intersects: true,
+ relation: WedgeProperlyContains,
+ },
+ {
+ desc: "Containment with equality on the other side",
+ a0: Point{r3.Vector{2, 1, 10}},
+ a1: Point{r3.Vector{-1, -1, 10}},
+ b0: Point{r3.Vector{1, -2, 10}},
+ b1: Point{r3.Vector{-1, -1, 10}},
+ contains: true,
+ intersects: true,
+ relation: WedgeProperlyContains,
+ },
+ {
+ desc: "Containment with equality on both sides",
+ a0: Point{r3.Vector{-2, 3, 10}},
+ a1: Point{r3.Vector{4, -5, 10}},
+ b0: Point{r3.Vector{-2, 3, 10}},
+ b1: Point{r3.Vector{4, -5, 10}},
+ contains: true,
+ intersects: true,
+ relation: WedgeEquals,
+ },
+ {
+ desc: "Disjoint with equality on one side",
+ a0: Point{r3.Vector{-2, 3, 10}},
+ a1: Point{r3.Vector{4, -5, 10}},
+ b0: Point{r3.Vector{4, -5, 10}},
+ b1: Point{r3.Vector{-2, -3, 10}},
+ contains: false,
+ intersects: false,
+ relation: WedgeIsDisjoint,
+ },
+ {
+ desc: "Disjoint with equality on the other side",
+ a0: Point{r3.Vector{-2, 3, 10}},
+ a1: Point{r3.Vector{0, 5, 10}},
+ b0: Point{r3.Vector{4, -5, 10}},
+ b1: Point{r3.Vector{-2, 3, 10}},
+ contains: false,
+ intersects: false,
+ relation: WedgeIsDisjoint,
+ },
+ {
+ desc: "Disjoint with equality on both sides",
+ a0: Point{r3.Vector{-2, 3, 10}},
+ a1: Point{r3.Vector{4, -5, 10}},
+ b0: Point{r3.Vector{4, -5, 10}},
+ b1: Point{r3.Vector{-2, 3, 10}},
+ contains: false,
+ intersects: false,
+ relation: WedgeIsDisjoint,
+ },
+ {
+ desc: "B contains A with equality on one side",
+ a0: Point{r3.Vector{2, 1, 10}},
+ a1: Point{r3.Vector{1, -5, 10}},
+ b0: Point{r3.Vector{2, 1, 10}},
+ b1: Point{r3.Vector{-1, -1, 10}},
+ contains: false,
+ intersects: true,
+ relation: WedgeIsProperlyContained,
+ },
+
+ {
+ desc: "B contains A with equality on the other side",
+ a0: Point{r3.Vector{2, 1, 10}},
+ a1: Point{r3.Vector{1, -5, 10}},
+ b0: Point{r3.Vector{-2, 1, 10}},
+ b1: Point{r3.Vector{1, -5, 10}},
+ contains: false,
+ intersects: true,
+ relation: WedgeIsProperlyContained,
+ },
+ }
+
+ for _, test := range tests {
+ if got := WedgeContains(test.a0, ab1, test.a1, test.b0, test.b1); got != test.contains {
+ t.Errorf("%s: WedgeContains(%v, %v, %v, %v, %v) = %t, want %t", test.desc, test.a0, ab1, test.a1, test.b0, test.b1, got, test.contains)
+ }
+ if got := WedgeIntersects(test.a0, ab1, test.a1, test.b0, test.b1); got != test.intersects {
+ t.Errorf("%s: WedgeIntersects(%v, %v, %v, %v, %v) = %t, want %t", test.desc, test.a0, ab1, test.a1, test.b0, test.b1, got, test.intersects)
+ }
+ if got := WedgeRelation(test.a0, ab1, test.a1, test.b0, test.b1); got != test.relation {
+ t.Errorf("%s: WedgeRelation(%v, %v, %v, %v, %v) = %v, want %v", test.desc, test.a0, ab1, test.a1, test.b0, test.b1, got, test.relation)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/latlng.go b/vendor/github.com/golang/geo/s2/latlng.go
index 55532c7..d0957cb 100644
--- a/vendor/github.com/golang/geo/s2/latlng.go
+++ b/vendor/github.com/golang/geo/s2/latlng.go
@@ -20,6 +20,7 @@ import (
"fmt"
"math"
+ "github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
@@ -87,7 +88,7 @@ func PointFromLatLng(ll LatLng) Point {
phi := ll.Lat.Radians()
theta := ll.Lng.Radians()
cosphi := math.Cos(phi)
- return PointFromCoords(math.Cos(theta)*cosphi, math.Sin(theta)*cosphi, math.Sin(phi))
+ return Point{r3.Vector{math.Cos(theta) * cosphi, math.Sin(theta) * cosphi, math.Sin(phi)}}
}
// LatLngFromPoint returns an LatLng for a given Point.
diff --git a/vendor/github.com/golang/geo/s2/latlng_test.go b/vendor/github.com/golang/geo/s2/latlng_test.go
new file mode 100644
index 0000000..54f96f8
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/latlng_test.go
@@ -0,0 +1,155 @@
+/*
+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/s1"
+)
+
+func TestLatLngNormalized(t *testing.T) {
+ tests := []struct {
+ desc string
+ pos LatLng
+ want LatLng
+ }{
+ {
+ desc: "Valid lat/lng",
+ pos: LatLngFromDegrees(21.8275043, 151.1979675),
+ want: LatLngFromDegrees(21.8275043, 151.1979675),
+ },
+ {
+ desc: "Valid lat/lng in the West",
+ pos: LatLngFromDegrees(21.8275043, -151.1979675),
+ want: LatLngFromDegrees(21.8275043, -151.1979675),
+ },
+ {
+ desc: "Beyond the North pole",
+ pos: LatLngFromDegrees(95, 151.1979675),
+ want: LatLngFromDegrees(90, 151.1979675),
+ },
+ {
+ desc: "Beyond the South pole",
+ pos: LatLngFromDegrees(-95, 151.1979675),
+ want: LatLngFromDegrees(-90, 151.1979675),
+ },
+ {
+ desc: "At the date line (from East)",
+ pos: LatLngFromDegrees(21.8275043, 180),
+ want: LatLngFromDegrees(21.8275043, 180),
+ },
+ {
+ desc: "At the date line (from West)",
+ pos: LatLngFromDegrees(21.8275043, -180),
+ want: LatLngFromDegrees(21.8275043, -180),
+ },
+ {
+ desc: "Across the date line going East",
+ pos: LatLngFromDegrees(21.8275043, 181.0012),
+ want: LatLngFromDegrees(21.8275043, -178.9988),
+ },
+ {
+ desc: "Across the date line going West",
+ pos: LatLngFromDegrees(21.8275043, -181.0012),
+ want: LatLngFromDegrees(21.8275043, 178.9988),
+ },
+ {
+ desc: "All wrong",
+ pos: LatLngFromDegrees(256, 256),
+ want: LatLngFromDegrees(90, -104),
+ },
+ }
+
+ for _, test := range tests {
+ got := test.pos.Normalized()
+ if !got.IsValid() {
+ t.Errorf("%s: A LatLng should be valid after normalization but isn't: %v", test.desc, got)
+ } else if got.Distance(test.want) > 1e-13*s1.Degree {
+ t.Errorf("%s: %v.Normalized() = %v, want %v", test.desc, test.pos, got, test.want)
+ }
+ }
+}
+
+func TestLatLngString(t *testing.T) {
+ const expected string = "[1.4142136, -2.2360680]"
+ s := LatLngFromDegrees(math.Sqrt2, -math.Sqrt(5)).String()
+ if s != expected {
+ t.Errorf("LatLng{√2, -√5}.String() = %q, want %q", s, expected)
+ }
+}
+
+func TestLatLngPointConversion(t *testing.T) {
+ // All test cases here have been verified against the C++ S2 implementation.
+ tests := []struct {
+ lat, lng float64 // degrees
+ x, y, z float64
+ }{
+ {0, 0, 1, 0, 0},
+ {90, 0, 6.12323e-17, 0, 1},
+ {-90, 0, 6.12323e-17, 0, -1},
+ {0, 180, -1, 1.22465e-16, 0},
+ {0, -180, -1, -1.22465e-16, 0},
+ {90, 180, -6.12323e-17, 7.4988e-33, 1},
+ {90, -180, -6.12323e-17, -7.4988e-33, 1},
+ {-90, 180, -6.12323e-17, 7.4988e-33, -1},
+ {-90, -180, -6.12323e-17, -7.4988e-33, -1},
+ {-81.82750430354997, 151.19796752929685,
+ -0.12456788151479525, 0.0684875268284729, -0.989844584550441},
+ }
+ for _, test := range tests {
+ ll := LatLngFromDegrees(test.lat, test.lng)
+ p := PointFromLatLng(ll)
+ // TODO(mikeperrow): Port Point.ApproxEquals, then use here.
+ if !float64Eq(p.X, test.x) || !float64Eq(p.Y, test.y) || !float64Eq(p.Z, test.z) {
+ t.Errorf("PointFromLatLng({%v°, %v°}) = %v, want %v, %v, %v",
+ test.lat, test.lng, p, test.x, test.y, test.z)
+ }
+ ll = LatLngFromPoint(p)
+ // We need to be careful here, since if the latitude is +/- 90, any longitude
+ // is now a valid conversion.
+ isPolar := (test.lat == 90 || test.lat == -90)
+ if !float64Eq(ll.Lat.Degrees(), test.lat) ||
+ (!isPolar && (!float64Eq(ll.Lng.Degrees(), test.lng))) {
+ t.Errorf("Converting ll %v,%v to point (%v) and back gave %v.",
+ test.lat, test.lng, p, ll)
+ }
+ }
+}
+
+func TestLatLngDistance(t *testing.T) {
+ // Based on C++ S2LatLng::TestDistance.
+ tests := []struct {
+ lat1, lng1, lat2, lng2 float64
+ want, tolerance float64
+ }{
+ {90, 0, 90, 0, 0, 0},
+ {-37, 25, -66, -155, 77, 1e-13},
+ {0, 165, 0, -80, 115, 1e-13},
+ {47, -127, -47, 53, 180, 2e-6},
+ }
+ for _, test := range tests {
+ ll1 := LatLngFromDegrees(test.lat1, test.lng1)
+ ll2 := LatLngFromDegrees(test.lat2, test.lng2)
+ d := ll1.Distance(ll2).Degrees()
+ if math.Abs(d-test.want) > test.tolerance {
+ t.Errorf("LatLng{%v, %v}.Distance(LatLng{%v, %v}).Degrees() = %v, want %v",
+ test.lat1, test.lng1, test.lat2, test.lng2, d, test.want)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/loop.go b/vendor/github.com/golang/geo/s2/loop.go
index 4d54860..253e7d8 100644
--- a/vendor/github.com/golang/geo/s2/loop.go
+++ b/vendor/github.com/golang/geo/s2/loop.go
@@ -211,6 +211,26 @@ func (l Loop) Edge(i int) (a, b Point) {
return l.Vertex(i), l.Vertex(i + 1)
}
+// dimension returns the dimension of the geometry represented by this Loop.
+func (l Loop) dimension() dimension { return polygonGeometry }
+
+// numChains reports the number of contiguous edge chains in the Loop.
+func (l Loop) numChains() int {
+ if l.isEmptyOrFull() {
+ return 0
+ }
+ return 1
+}
+
+// chainStart returns the id of the first edge in the i-th edge chain in this Loop.
+func (l Loop) chainStart(i int) int {
+ if i == 0 {
+ return 0
+ }
+
+ return l.NumEdges()
+}
+
// IsEmpty reports true if this is the special "empty" loop that contains no points.
func (l Loop) IsEmpty() bool {
return l.isEmptyOrFull() && !l.ContainsOrigin()
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.
+}
diff --git a/vendor/github.com/golang/geo/s2/matrix3x3.go b/vendor/github.com/golang/geo/s2/matrix3x3.go
index 1f78d5d..048419f 100644
--- a/vendor/github.com/golang/geo/s2/matrix3x3.go
+++ b/vendor/github.com/golang/geo/s2/matrix3x3.go
@@ -18,6 +18,8 @@ package s2
import (
"fmt"
+
+ "github.com/golang/geo/r3"
)
// matrix3x3 represents a traditional 3x3 matrix of floating point values.
@@ -27,12 +29,12 @@ type matrix3x3 [3][3]float64
// col returns the given column as a Point.
func (m *matrix3x3) col(col int) Point {
- return PointFromCoords(m[0][col], m[1][col], m[2][col])
+ return Point{r3.Vector{m[0][col], m[1][col], m[2][col]}}
}
// row returns the given row as a Point.
func (m *matrix3x3) row(row int) Point {
- return PointFromCoords(m[row][0], m[row][1], m[row][2])
+ return Point{r3.Vector{m[row][0], m[row][1], m[row][2]}}
}
// setCol sets the specified column to the value in the given Point.
@@ -65,11 +67,11 @@ func (m *matrix3x3) scale(f float64) *matrix3x3 {
// mul returns the multiplication of m by the Point p and converts the
// resulting 1x3 matrix into a Point.
func (m *matrix3x3) mul(p Point) Point {
- return PointFromCoords(
- m[0][0]*p.X+m[0][1]*p.Y+m[0][2]*p.Z,
- m[1][0]*p.X+m[1][1]*p.Y+m[1][2]*p.Z,
- m[2][0]*p.X+m[2][1]*p.Y+m[2][2]*p.Z,
- )
+ return Point{r3.Vector{
+ m[0][0]*p.X + m[0][1]*p.Y + m[0][2]*p.Z,
+ m[1][0]*p.X + m[1][1]*p.Y + m[1][2]*p.Z,
+ m[2][0]*p.X + m[2][1]*p.Y + m[2][2]*p.Z,
+ }}
}
// det returns the determinant of this matrix.
diff --git a/vendor/github.com/golang/geo/s2/matrix3x3_test.go b/vendor/github.com/golang/geo/s2/matrix3x3_test.go
new file mode 100644
index 0000000..b833f3f
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/matrix3x3_test.go
@@ -0,0 +1,494 @@
+/*
+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/r3"
+)
+
+func TestCol(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ col int
+ want Point
+ }{
+ {&matrix3x3{}, 0, OriginPoint()},
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 0,
+ Point{r3.Vector{1, 4, 7}},
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 2,
+ Point{r3.Vector{3, 6, 9}},
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.col(test.col); !got.ApproxEqual(test.want) {
+ t.Errorf("%v.col(%d) = %v, want %v", test.have, test.col, got, test.want)
+ }
+ }
+}
+
+func TestRow(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ row int
+ want Point
+ }{
+ {&matrix3x3{}, 0, OriginPoint()},
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 0,
+ Point{r3.Vector{1, 2, 3}},
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 2,
+ Point{r3.Vector{7, 8, 9}},
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.row(test.row); !got.ApproxEqual(test.want) {
+ t.Errorf("%v.row(%d) = %v, want %v", test.have, test.row, got, test.want)
+ }
+ }
+}
+
+func TestSetCol(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ col int
+ point Point
+ want *matrix3x3
+ }{
+ {
+ &matrix3x3{},
+ 0,
+ Point{r3.Vector{1, 1, 0}},
+ &matrix3x3{
+ {1, 0, 0},
+ {1, 0, 0},
+ {0, 0, 0},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 2,
+ Point{r3.Vector{1, 1, 0}},
+ &matrix3x3{
+ {1, 2, 1},
+ {4, 5, 1},
+ {7, 8, 0},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.setCol(test.col, test.point); !matricesApproxEqual(got, test.want) {
+ t.Errorf("%v.setCol(%d, %v) = %v, want %v", test.have, test.col, test.point, got, test.want)
+ }
+ }
+}
+
+func TestSetRow(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ row int
+ point Point
+ want *matrix3x3
+ }{
+ {
+ &matrix3x3{},
+ 0,
+ Point{r3.Vector{1, 1, 0}},
+ &matrix3x3{
+ {1, 1, 0},
+ {0, 0, 0},
+ {0, 0, 0},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 2,
+ Point{r3.Vector{1, 1, 0}},
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {1, 1, 0},
+ },
+ },
+ }
+ for _, test := range tests {
+ if got := test.have.setRow(test.row, test.point); !matricesApproxEqual(got, test.want) {
+ t.Errorf("%v.setRow(%d, %v) = %v, want %v", test.have, test.row, test.point, got, test.want)
+ }
+ }
+}
+
+func TestScale(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ scale float64
+ want *matrix3x3
+ }{
+ {
+ &matrix3x3{},
+ 0,
+ &matrix3x3{},
+ },
+ {
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ 0,
+ &matrix3x3{},
+ },
+ {
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ 1,
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ 5,
+ &matrix3x3{
+ {5, 5, 5},
+ {5, 5, 5},
+ {5, 5, 5},
+ },
+ },
+ {
+ &matrix3x3{
+ {-2, 2, -3},
+ {-1, 1, 3},
+ {2, 0, -1},
+ },
+ 2.75,
+ &matrix3x3{
+ {-5.5, 5.5, -8.25},
+ {-2.75, 2.75, 8.25},
+ {5.5, 0, -2.75},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.scale(test.scale); !matricesApproxEqual(got, test.want) {
+ t.Errorf("%v.scale(%f) = %v, want %v", test.have, test.scale, got, test.want)
+ }
+ }
+}
+
+func TestMul(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ point Point
+ want Point
+ }{
+ {&matrix3x3{}, Point{}, Point{}},
+ {
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ Point{},
+ Point{},
+ },
+ {
+ // Identity times something gives back the something
+ &matrix3x3{
+ {1, 0, 0},
+ {0, 1, 0},
+ {0, 0, 1},
+ },
+ Point{},
+ Point{},
+ },
+ {
+ // Identity times something gives back the something
+ &matrix3x3{
+ {1, 0, 0},
+ {0, 1, 0},
+ {0, 0, 1},
+ },
+ Point{r3.Vector{1, 2, 3}},
+ Point{r3.Vector{1, 2, 3}},
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ Point{r3.Vector{1, 1, 1}},
+ Point{r3.Vector{6, 15, 24}},
+ },
+ }
+ for _, test := range tests {
+ if got := test.have.mul(test.point); !got.ApproxEqual(test.want) {
+ t.Errorf("%v.mul(%v) = %v, want %v", test.have, test.point, got, test.want)
+ }
+ }
+}
+
+func TestDet(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ want float64
+ }{
+ {
+ &matrix3x3{},
+ 0,
+ },
+ {
+ // Matrix of all the same values has det of 0.
+ &matrix3x3{
+ {1, 1, 1},
+ {1, 1, 1},
+ {1, 1, 1},
+ },
+ 0,
+ },
+ {
+ // Identity matrix has det of 1.
+ &matrix3x3{
+ {1, 0, 0},
+ {0, 1, 0},
+ {0, 0, 1},
+ },
+ 1,
+ },
+ {
+ &matrix3x3{
+ {-2, 2, -3},
+ {-1, 1, 3},
+ {2, 0, -1},
+ },
+ 18,
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ 0,
+ },
+ {
+ &matrix3x3{
+ {9, 8, 7},
+ {6, 5, 4},
+ {3, 2, 1},
+ },
+ 0,
+ },
+ {
+ &matrix3x3{
+ {1.74, math.E, 42},
+ {math.Pi, math.Sqrt2, math.Ln10},
+ {3, math.SqrtPhi, 9.8976},
+ },
+ -56.838525224123096,
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.det(); !float64Eq(got, test.want) {
+ t.Errorf("%v.det() = %v, want %v", test.have, got, test.want)
+ }
+ }
+}
+
+func TestTranspose(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ want *matrix3x3
+ }{
+ {&matrix3x3{}, &matrix3x3{}},
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ &matrix3x3{
+ {1, 4, 7},
+ {2, 5, 8},
+ {3, 6, 9},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 0, 0},
+ {0, 2, 0},
+ {0, 0, 3},
+ },
+ &matrix3x3{
+ {1, 0, 0},
+ {0, 2, 0},
+ {0, 0, 3},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {0, 4, 5},
+ {0, 0, 6},
+ },
+ &matrix3x3{
+ {1, 0, 0},
+ {2, 4, 0},
+ {3, 5, 6},
+ },
+ },
+ {
+ &matrix3x3{
+ {1, 1, 1},
+ {0, 0, 0},
+ {0, 0, 0},
+ },
+ &matrix3x3{
+ {1, 0, 0},
+ {1, 0, 0},
+ {1, 0, 0},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.transpose().transpose(); !matricesApproxEqual(got, test.have) {
+ t.Errorf("%v.transpose().transpose() = %v, want %v", test.have, got, test.have)
+ }
+
+ if got := test.have.transpose(); !matricesApproxEqual(got, test.want) {
+ t.Errorf("%v.transpose() = %v, want %v", test.have, got, test.want)
+ }
+
+ }
+}
+
+func TestString(t *testing.T) {
+ tests := []struct {
+ have *matrix3x3
+ want string
+ }{
+ {
+ &matrix3x3{
+ {1, 2, 3},
+ {4, 5, 6},
+ {7, 8, 9},
+ },
+ `[ 1.0000 2.0000 3.0000 ] [ 4.0000 5.0000 6.0000 ] [ 7.0000 8.0000 9.0000 ]`,
+ },
+ {
+ &matrix3x3{
+ {1, 4, 7},
+ {2, 5, 8},
+ {3, 6, 9},
+ },
+ `[ 1.0000 4.0000 7.0000 ] [ 2.0000 5.0000 8.0000 ] [ 3.0000 6.0000 9.0000 ]`,
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.have.String(); got != test.want {
+ t.Errorf("%v.String() = %v, want %v", test.have, got, test.want)
+ }
+ }
+}
+
+func TestFrames(t *testing.T) {
+ z := PointFromCoords(0.2, 0.5, -3.3)
+ m := getFrame(z)
+
+ if !m.col(0).IsUnit() {
+ t.Errorf("col(0) of frame not unit length")
+ }
+ if !m.col(1).IsUnit() {
+ t.Errorf("col(1) of frame not unit length")
+ }
+ if !float64Eq(m.det(), 1) {
+ t.Errorf("determinant of frame = %v, want %v", m.det(), 1)
+ }
+
+ tests := []struct {
+ a Point
+ b Point
+ }{
+ {m.col(2), z},
+
+ {toFrame(m, m.col(0)), Point{r3.Vector{1, 0, 0}}},
+ {toFrame(m, m.col(1)), Point{r3.Vector{0, 1, 0}}},
+ {toFrame(m, m.col(2)), Point{r3.Vector{0, 0, 1}}},
+
+ {fromFrame(m, Point{r3.Vector{1, 0, 0}}), m.col(0)},
+ {fromFrame(m, Point{r3.Vector{0, 1, 0}}), m.col(1)},
+ {fromFrame(m, Point{r3.Vector{0, 0, 1}}), m.col(2)},
+ }
+
+ for _, test := range tests {
+ if !pointsApproxEquals(test.a, test.b, epsilon) {
+ t.Errorf("%v != %v", test.a, test.b)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/metric_test.go b/vendor/github.com/golang/geo/s2/metric_test.go
new file mode 100644
index 0000000..bdab9df
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/metric_test.go
@@ -0,0 +1,109 @@
+/*
+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"
+)
+
+func TestMetric(t *testing.T) {
+ if got := MinWidthMetric.MaxLevel(0.001256); got != 9 {
+ t.Errorf("MinWidthMetric.MaxLevel(0.001256) = %d, want 9", got)
+ }
+
+ // Check that the maximum aspect ratio of an individual cell is consistent
+ // with the global minimums and maximums.
+ if MaxEdgeAspect < 1 {
+ t.Errorf("MaxEdgeAspect = %v, want >= 1", MaxEdgeAspect)
+ }
+ if got := MaxEdgeMetric.Deriv / MinEdgeMetric.Deriv; MaxEdgeAspect > got {
+ t.Errorf("Edge Aspect: %v/%v = %v, want <= %v", MaxEdgeMetric.Deriv, MinEdgeMetric.Deriv, got, MaxDiagAspect)
+ }
+ if MaxDiagAspect < 1 {
+ t.Errorf("MaxDiagAspect = %v, want >= 1", MaxDiagAspect)
+ }
+ if got := MaxDiagMetric.Deriv / MinDiagMetric.Deriv; MaxDiagAspect > got {
+ t.Errorf("Diag Aspect: %v/%v = %v, want <= %v", MaxDiagMetric.Deriv, MinDiagMetric.Deriv, got, MaxDiagAspect)
+ }
+
+ // Check that area is consistent with edge and width.
+ if got := MinWidthMetric.Deriv*MinEdgeMetric.Deriv - 1e-15; MinAreaMetric.Deriv < got {
+ t.Errorf("Min Area: %v*%v = %v, want >= %v", MinWidthMetric.Deriv, MinEdgeMetric.Deriv, got, MinAreaMetric.Deriv)
+ }
+ if got := MaxWidthMetric.Deriv*MaxEdgeMetric.Deriv + 1e-15; MaxAreaMetric.Deriv > got {
+ t.Errorf("Max Area: %v*%v = %v, want <= %v", MaxWidthMetric.Deriv, MaxEdgeMetric.Deriv, got, MaxAreaMetric.Deriv)
+ }
+
+ for level := -2; level <= maxLevel+3; level++ {
+ width := MinWidthMetric.Deriv * math.Pow(2, float64(-level))
+ if level >= maxLevel+3 {
+ width = 0
+ }
+
+ // Check boundary cases (exactly equal to a threshold value).
+ expected := int(math.Max(0, math.Min(maxLevel, float64(level))))
+
+ if MinWidthMetric.MinLevel(width) != expected {
+ t.Errorf("MinWidthMetric.MinLevel(%v) = %v, want %v", width, MinWidthMetric.MinLevel(width), expected)
+ }
+ if MinWidthMetric.MaxLevel(width) != expected {
+ t.Errorf("MinWidthMetric.MaxLevel(%v) = %v, want %v", width, MinWidthMetric.MaxLevel(width), expected)
+ }
+ if MinWidthMetric.ClosestLevel(width) != expected {
+ t.Errorf("MinWidthMetric.ClosestLevel(%v) = %v, want %v", width, MinWidthMetric.ClosestLevel(width), expected)
+ }
+
+ // Also check non-boundary cases.
+ if got := MinWidthMetric.MinLevel(1.2 * width); got != expected {
+ t.Errorf("non-boundary MinWidthMetric.MinLevel(%v) = %v, want %v", 1.2*width, got, expected)
+ }
+ if got := MinWidthMetric.MaxLevel(0.8 * width); got != expected {
+ t.Errorf("non-boundary MinWidthMetric.MaxLevel(%v) = %v, want %v", 0.8*width, got, expected)
+ }
+ if got := MinWidthMetric.ClosestLevel(1.2 * width); got != expected {
+ t.Errorf("non-boundary larger MinWidthMetric.ClosestLevel(%v) = %v, want %v", 1.2*width, got, expected)
+ }
+ if got := MinWidthMetric.ClosestLevel(0.8 * width); got != expected {
+ t.Errorf("non-boundary smaller MinWidthMetric.ClosestLevel(%v) = %v, want %v", 0.8*width, got, expected)
+ }
+ }
+}
+
+func TestMetricSizeRelations(t *testing.T) {
+ // check that min <= avg <= max for each metric.
+ tests := []struct {
+ min Metric
+ avg Metric
+ max Metric
+ }{
+ {MinAngleSpanMetric, AvgAngleSpanMetric, MaxAngleSpanMetric},
+ {MinWidthMetric, AvgWidthMetric, MaxWidthMetric},
+ {MinEdgeMetric, AvgEdgeMetric, MaxEdgeMetric},
+ {MinDiagMetric, AvgDiagMetric, MaxDiagMetric},
+ {MinAreaMetric, AvgAreaMetric, MaxAreaMetric},
+ }
+
+ for _, test := range tests {
+ if test.min.Deriv > test.avg.Deriv {
+ t.Errorf("Min %v > Avg %v", test.min.Deriv, test.avg.Deriv)
+ }
+ if test.avg.Deriv > test.max.Deriv {
+ t.Errorf("Avg %v > Max %v", test.avg.Deriv, test.max.Deriv)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/paddedcell_test.go b/vendor/github.com/golang/geo/s2/paddedcell_test.go
new file mode 100644
index 0000000..00a16d4
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/paddedcell_test.go
@@ -0,0 +1,197 @@
+/*
+Copyright 2016 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/r2"
+)
+
+func TestPaddedCellMethods(t *testing.T) {
+ // Test the PaddedCell methods that have approximate Cell equivalents.
+ for i := 0; i < 1000; i++ {
+ cid := randomCellID()
+ padding := math.Pow(1e-15, randomFloat64())
+ cell := CellFromCellID(cid)
+ pCell := PaddedCellFromCellID(cid, padding)
+
+ if cell.id != pCell.id {
+ t.Errorf("%v.id = %v, want %v", pCell, pCell.id, cell.id)
+ }
+ if cell.id.Level() != pCell.Level() {
+ t.Errorf("%v.Level() = %v, want %v", pCell, pCell.Level(), cell.id.Level())
+ }
+
+ if padding != pCell.Padding() {
+ t.Errorf("%v.Padding() = %v, want %v", pCell, pCell.Padding(), padding)
+ }
+
+ if got, want := pCell.Bound(), cell.BoundUV().ExpandedByMargin(padding); got != want {
+ t.Errorf("%v.BoundUV() = %v, want %v", pCell, got, want)
+ }
+
+ r := r2.RectFromPoints(cell.id.centerUV()).ExpandedByMargin(padding)
+ if r != pCell.Middle() {
+ t.Errorf("%v.Middle() = %v, want %v", pCell, pCell.Middle(), r)
+ }
+
+ if cell.id.Point() != pCell.Center() {
+ t.Errorf("%v.Center() = %v, want %v", pCell, pCell.Center(), cell.id.Point())
+ }
+ if cid.IsLeaf() {
+ continue
+ }
+
+ children, ok := cell.Children()
+ if !ok {
+ t.Errorf("%v.Children() failed but should not have", cell)
+ continue
+ }
+ for pos := 0; pos < 4; pos++ {
+ i, j := pCell.ChildIJ(pos)
+
+ cellChild := children[pos]
+ pCellChild := PaddedCellFromParentIJ(pCell, i, j)
+ if cellChild.id != pCellChild.id {
+ t.Errorf("%v.id = %v, want %v", pCellChild, pCellChild.id, cellChild.id)
+ }
+ if cellChild.id.Level() != pCellChild.Level() {
+ t.Errorf("%v.Level() = %v, want %v", pCellChild, pCellChild.Level(), cellChild.id.Level())
+ }
+
+ if padding != pCellChild.Padding() {
+ t.Errorf("%v.Padding() = %v, want %v", pCellChild, pCellChild.Padding(), padding)
+ }
+
+ if got, want := pCellChild.Bound(), cellChild.BoundUV().ExpandedByMargin(padding); got != want {
+ t.Errorf("%v.BoundUV() = %v, want %v", pCellChild, got, want)
+ }
+
+ r := r2.RectFromPoints(cellChild.id.centerUV()).ExpandedByMargin(padding)
+ if got := pCellChild.Middle(); !r.ApproxEquals(got) {
+ t.Errorf("%v.Middle() = %v, want %v", pCellChild, got, r)
+ }
+
+ if cellChild.id.Point() != pCellChild.Center() {
+ t.Errorf("%v.Center() = %v, want %v", pCellChild, pCellChild.Center(), cellChild.id.Point())
+ }
+
+ }
+ }
+}
+
+func TestPaddedCellEntryExitVertices(t *testing.T) {
+ for i := 0; i < 1000; i++ {
+ id := randomCellID()
+ unpadded := PaddedCellFromCellID(id, 0)
+ padded := PaddedCellFromCellID(id, 0.5)
+
+ // Check that entry/exit vertices do not depend on padding.
+ if unpadded.EntryVertex() != padded.EntryVertex() {
+ t.Errorf("entry vertex should not depend on padding; %v != %v", unpadded.EntryVertex(), padded.EntryVertex())
+ }
+
+ if unpadded.ExitVertex() != padded.ExitVertex() {
+ t.Errorf("exit vertex should not depend on padding; %v != %v", unpadded.ExitVertex(), padded.ExitVertex())
+ }
+
+ // Check that the exit vertex of one cell is the same as the entry vertex
+ // of the immediately following cell. This also tests wrapping from the
+ // end to the start of the CellID curve with high probability.
+ if got := PaddedCellFromCellID(id.NextWrap(), 0).EntryVertex(); unpadded.ExitVertex() != got {
+ t.Errorf("PaddedCellFromCellID(%v.NextWrap(), 0).EntryVertex() = %v, want %v", id, got, unpadded.ExitVertex())
+ }
+
+ // Check that the entry vertex of a cell is the same as the entry vertex
+ // of its first child, and similarly for the exit vertex.
+ if id.IsLeaf() {
+ continue
+ }
+ if got := PaddedCellFromCellID(id.Children()[0], 0).EntryVertex(); unpadded.EntryVertex() != got {
+ t.Errorf("PaddedCellFromCellID(%v.Children()[0], 0).EntryVertex() = %v, want %v", id, got, unpadded.EntryVertex())
+ }
+ if got := PaddedCellFromCellID(id.Children()[3], 0).ExitVertex(); unpadded.ExitVertex() != got {
+ t.Errorf("PaddedCellFromCellID(%v.Children()[3], 0).ExitVertex() = %v, want %v", id, got, unpadded.ExitVertex())
+ }
+ }
+}
+
+func TestPaddedCellShrinkToFit(t *testing.T) {
+ for iter := 0; iter < 1000; iter++ {
+ // Start with the desired result and work backwards.
+ result := randomCellID()
+ resultUV := result.boundUV()
+ sizeUV := resultUV.Size()
+
+ // Find the biggest rectangle that fits in "result" after padding.
+ // (These calculations ignore numerical errors.)
+ maxPadding := 0.5 * math.Min(sizeUV.X, sizeUV.Y)
+ padding := maxPadding * randomFloat64()
+ maxRect := resultUV.ExpandedByMargin(-padding)
+
+ // Start with a random subset of the maximum rectangle.
+ a := r2.Point{
+ randomUniformFloat64(maxRect.X.Lo, maxRect.X.Hi),
+ randomUniformFloat64(maxRect.Y.Lo, maxRect.Y.Hi),
+ }
+ b := r2.Point{
+ randomUniformFloat64(maxRect.X.Lo, maxRect.X.Hi),
+ randomUniformFloat64(maxRect.Y.Lo, maxRect.Y.Hi),
+ }
+
+ if !result.IsLeaf() {
+ // If the result is not a leaf cell, we must ensure that no child of
+ // result also satisfies the conditions of ShrinkToFit(). We do this
+ // by ensuring that rect intersects at least two children of result
+ // (after padding).
+ useY := oneIn(2)
+ center := result.centerUV().X
+ if useY {
+ center = result.centerUV().Y
+ }
+
+ // Find the range of coordinates that are shared between child cells
+ // along that axis.
+ shared := r1.Interval{center - padding, center + padding}
+ if useY {
+ shared = shared.Intersection(maxRect.Y)
+ } else {
+ shared = shared.Intersection(maxRect.X)
+ }
+ mid := randomUniformFloat64(shared.Lo, shared.Hi)
+
+ if useY {
+ a.Y = randomUniformFloat64(maxRect.Y.Lo, mid)
+ b.Y = randomUniformFloat64(mid, maxRect.Y.Hi)
+ } else {
+ a.X = randomUniformFloat64(maxRect.X.Lo, mid)
+ b.X = randomUniformFloat64(mid, maxRect.X.Hi)
+ }
+ }
+ rect := r2.RectFromPoints(a, b)
+
+ // Choose an arbitrary ancestor as the PaddedCell.
+ initialID := result.Parent(randomUniformInt(result.Level() + 1))
+ pCell := PaddedCellFromCellID(initialID, padding)
+ if got := pCell.ShrinkToFit(rect); got != result {
+ t.Errorf("%v.ShrinkToFit(%v) = %v, want %v", pCell, rect, got, result)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/point.go b/vendor/github.com/golang/geo/s2/point.go
index 2b300cd..4eb4980 100644
--- a/vendor/github.com/golang/geo/s2/point.go
+++ b/vendor/github.com/golang/geo/s2/point.go
@@ -24,7 +24,6 @@ import (
)
// Point represents a point on the unit sphere as a normalized 3D vector.
-// Points are guaranteed to be close to normalized.
// Fields should be treated as read-only. Use one of the factory methods for creation.
type Point struct {
r3.Vector
@@ -59,8 +58,7 @@ func OriginPoint() Point {
// PointCross returns a Point that is orthogonal to both p and op. This is similar to
// p.Cross(op) (the true cross product) except that it does a better job of
// ensuring orthogonality when the Point is nearly parallel to op, it returns
-// a non-zero result even when p == op or p == -op and the result is a Point,
-// so it will have norm 1.
+// a non-zero result even when p == op or p == -op and the result is a Point.
//
// It satisfies the following properties (f == PointCross):
//
@@ -73,13 +71,14 @@ func (p Point) PointCross(op Point) Point {
// but PointCross more accurately describes how this method is used.
x := p.Add(op.Vector).Cross(op.Sub(p.Vector))
- if x.ApproxEqual(r3.Vector{0, 0, 0}) {
+ // Compare exactly to the 0 vector.
+ if x == (r3.Vector{}) {
// The only result that makes sense mathematically is to return zero, but
// we find it more convenient to return an arbitrary orthogonal vector.
return Point{p.Ortho()}
}
- return Point{x.Normalize()}
+ return Point{x}
}
// OrderedCCW returns true if the edges OA, OB, and OC are encountered in that
@@ -277,13 +276,34 @@ func regularPointsForFrame(frame matrix3x3, radius s1.Angle, numVertices int) []
for i := 0; i < numVertices; i++ {
angle := float64(i) * radianStep
- p := PointFromCoords(r*math.Cos(angle), r*math.Sin(angle), z)
+ p := Point{r3.Vector{r * math.Cos(angle), r * math.Sin(angle), z}}
vertices = append(vertices, Point{fromFrame(frame, p).Normalize()})
}
return vertices
}
+// CapBound returns a bounding cap for this point.
+func (p Point) CapBound() Cap {
+ return CapFromPoint(p)
+}
+
+// RectBound returns a bounding latitude-longitude rectangle from this point.
+func (p Point) RectBound() Rect {
+ return RectFromLatLng(LatLngFromPoint(p))
+}
+
+// ContainsCell returns false as Points do not contain any other S2 types.
+func (p Point) ContainsCell(c Cell) bool { return false }
+
+// IntersectsCell reports whether this Point intersects the given cell.
+func (p Point) IntersectsCell(c Cell) bool {
+ return c.ContainsPoint(p)
+}
+
+// Contains reports if this Point contains the other Point.
+func (p Point) Contains(other Point) bool { return p == other }
+
// TODO: Differences from C++
// Rotate
// Angle
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)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/polygon.go b/vendor/github.com/golang/geo/s2/polygon.go
index 352cc23..f04be04 100644
--- a/vendor/github.com/golang/geo/s2/polygon.go
+++ b/vendor/github.com/golang/geo/s2/polygon.go
@@ -62,6 +62,9 @@ type Polygon struct {
// numVertices keeps the running total of all of the vertices of the contained loops.
numVertices int
+ // numEdges tracks the total number of edges in all the loops in this polygon.
+ numEdges int
+
// bound is a conservative bound on all points contained by this loop.
// If l.ContainsPoint(P), then l.bound.ContainsPoint(P).
bound Rect
@@ -71,6 +74,11 @@ type Polygon struct {
// has been expanded sufficiently to account for this error, i.e.
// if A.Contains(B), then A.subregionBound.Contains(B.bound).
subregionBound Rect
+
+ // A slice where element i is the cumulative number of edges in the
+ // preceding loops in the polygon.
+ // This field is used for polygons that have a large number of loops.
+ cumulativeEdges []int
}
// PolygonFromLoops constructs a polygon from the given hierarchically nested
@@ -87,15 +95,25 @@ func PolygonFromLoops(loops []*Loop) *Polygon {
if len(loops) > 1 {
panic("s2.PolygonFromLoops for multiple loops is not yet implemented")
}
- return &Polygon{
+
+ p := &Polygon{
loops: loops,
// TODO(roberts): This is explicitly set as depth of 0 for the one loop in
// the polygon. When multiple loops are supported, fix this to set the depths.
- loopDepths: []int{0},
- numVertices: len(loops[0].Vertices()), // TODO(roberts): Once multi-loop is supported, fix this.
- bound: EmptyRect(),
- subregionBound: EmptyRect(),
+ loopDepths: []int{0},
+ numVertices: len(loops[0].Vertices()), // TODO(roberts): Once multi-loop is supported, fix this.
+ // TODO(roberts): Compute these bounds.
+ bound: loops[0].RectBound(),
+ subregionBound: EmptyRect(),
+ cumulativeEdges: make([]int, len(loops)),
}
+
+ for i, l := range loops {
+ p.cumulativeEdges[i] = p.numEdges
+ p.numEdges += len(l.Vertices())
+ }
+
+ return p
}
// FullPolygon returns a special "full" polygon.
@@ -104,10 +122,11 @@ func FullPolygon() *Polygon {
loops: []*Loop{
FullLoop(),
},
- loopDepths: []int{0},
- numVertices: len(FullLoop().Vertices()),
- bound: FullRect(),
- subregionBound: FullRect(),
+ loopDepths: []int{0},
+ numVertices: len(FullLoop().Vertices()),
+ bound: FullRect(),
+ subregionBound: FullRect(),
+ cumulativeEdges: []int{0},
}
}
@@ -209,3 +228,23 @@ func (p *Polygon) RectBound() Rect { return p.bound }
// IntersectsCell reports whether the polygon intersects the given cell.
// TODO(roberts)
//func (p *Polygon) IntersectsCell(c Cell) bool { ... }
+
+// dimension returns the dimension of the geometry represented by this Polygon.
+func (p *Polygon) dimension() dimension { return polygonGeometry }
+
+// numChains reports the number of contiguous edge chains in the Polygon.
+func (p *Polygon) numChains() int {
+ if p.IsFull() {
+ return 0
+ }
+
+ return p.NumLoops()
+}
+
+// chainStart returns the id of the first edge in the i-th edge chain in this Polygon.
+func (p *Polygon) chainStart(i int) int {
+ if i == p.NumLoops() {
+ return p.numEdges
+ }
+ return p.cumulativeEdges[i]
+}
diff --git a/vendor/github.com/golang/geo/s2/polygon_test.go b/vendor/github.com/golang/geo/s2/polygon_test.go
new file mode 100644
index 0000000..bf38365
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/polygon_test.go
@@ -0,0 +1,199 @@
+/*
+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 (
+ "testing"
+)
+
+func TestPolygonEmptyAndFull(t *testing.T) {
+ emptyPolygon := &Polygon{}
+
+ if !emptyPolygon.IsEmpty() {
+ t.Errorf("empty polygon should be empty")
+ }
+ if emptyPolygon.IsFull() {
+ t.Errorf("empty polygon should not be full")
+ }
+ /*
+ // TODO(roberts): Uncomment when Polygon finishes the Shape interface.
+ if emptyPolygon.ContainsOrigin() {
+ t.Errorf("emptyPolygon.ContainsOrigin() = true, want false")
+ }
+ if got, want := emptyPolygon.NumEdges(), 0; got != want {
+ t.Errorf("emptyPolygon.NumEdges() = %v, want %v", got, want)
+ }
+ */
+ if got := emptyPolygon.dimension(); got != polygonGeometry {
+ t.Errorf("emptyPolygon.dimension() = %v, want %v", got, polygonGeometry)
+ }
+ if got, want := emptyPolygon.numChains(), 0; got != want {
+ t.Errorf("emptyPolygon.numChains() = %v, want %v", got, want)
+ }
+
+ fullPolygon := FullPolygon()
+ if fullPolygon.IsEmpty() {
+ t.Errorf("full polygon should not be emtpy")
+ }
+ if !fullPolygon.IsFull() {
+ t.Errorf("full polygon should be full")
+ }
+ /*
+ // TODO(roberts): Uncomment when Polygon finishes the Shape interface.
+ if !fullPolygon.ContainsOrigin() {
+ t.Errorf("fullPolygon.ContainsOrigin() = false, want true")
+ }
+ if got, want := fullPolygon.NumEdges(), 0; got != want {
+ t.Errorf("fullPolygon.NumEdges() = %v, want %v", got, want)
+ }
+ */
+ if got := fullPolygon.dimension(); got != polygonGeometry {
+ t.Errorf("emptyPolygon.dimension() = %v, want %v", got, polygonGeometry)
+ }
+ if got, want := fullPolygon.numChains(), 0; got != want {
+ t.Errorf("emptyPolygon.numChains() = %v, want %v", got, want)
+ }
+}
+
+func TestPolygonShape(t *testing.T) {
+ // TODO(roberts): Once Polygon implements Shape uncomment this test.
+ /*
+ p := &Polygon{}
+ shape := Shape(p)
+ if p.NumVertices() != shape.NumEdges() {
+ t.Errorf("the number of vertices in a polygon should equal the number of edges")
+ }
+ if p.NumLoops() != shape.numChains() {
+ t.Errorf("the number of loops in a polygon should equal the number of chains")
+ }
+ e := 0
+ for i, l := range p.loops {
+ if e != shape.chainStart(i) {
+ t.Errorf("the edge if of the start of loop(%d) should equal the sum of vertices so far in the polygon. got %d, want %d", i, shape.chainStart(i), e)
+ }
+ for j := 0; j < len(l.Vertices()); j++ {
+ v0, v1 := shape.Edge(e)
+ // TODO(roberts): Update once Loop implements orientedVertex.
+ //if l.orientedVertex(j) != v0 {
+ if l.Vertex(j) != v0 {
+ t.Errorf("l.Vertex(%d) = %v, want %v", j, l.Vertex(j), v0)
+ }
+ // TODO(roberts): Update once Loop implements orientedVertex.
+ //if l.orientedVertex(j+1) != v1 {
+ if l.Vertex(j+1) != v1 {
+ t.Errorf("l.Vertex(%d) = %v, want %v", j+1, l.Vertex(j+1), v1)
+ }
+ e++
+ }
+ if e != shape.chainStart(i+1) {
+ t.Errorf("the edge id of the start of the next loop(%d+1) should equal the sum of vertices so far in the polygon. got %d, want %d", i, shape.chainStart(i+1), e)
+ }
+ }
+ if shape.dimension() != polygonGeometry {
+ t.Errorf("polygon.dimension() = %v, want %v", shape.dimension() , polygonGeometry)
+ }
+ if !shape.HasInterior() {
+ t.Errorf("polygons should always have interiors")
+ }
+ */
+}
+
+func TestPolygonLoop(t *testing.T) {
+ full := FullPolygon()
+ if full.NumLoops() != 1 {
+ t.Errorf("full polygon should have one loop")
+ }
+
+ l := &Loop{}
+ p1 := PolygonFromLoops([]*Loop{l})
+ if p1.NumLoops() != 1 {
+ t.Errorf("polygon with one loop should have one loop")
+ }
+ if p1.Loop(0) != l {
+ t.Errorf("polygon with one loop should return it")
+ }
+
+ // TODO: When multiple loops are supported, add more test cases.
+}
+
+func TestPolygonParent(t *testing.T) {
+ p1 := PolygonFromLoops([]*Loop{&Loop{}})
+ tests := []struct {
+ p *Polygon
+ have int
+ want int
+ ok bool
+ }{
+ {FullPolygon(), 0, -1, false},
+ {p1, 0, -1, false},
+
+ // TODO: When multiple loops are supported, add more test cases to
+ // more fully show the parent levels.
+ }
+
+ for _, test := range tests {
+ if got, ok := test.p.Parent(test.have); ok != test.ok || got != test.want {
+ t.Errorf("%v.Parent(%d) = %d,%v, want %d,%v", test.p, test.have, got, ok, test.want, test.ok)
+ }
+ }
+}
+
+func TestPolygonLastDescendant(t *testing.T) {
+ p1 := PolygonFromLoops([]*Loop{&Loop{}})
+
+ tests := []struct {
+ p *Polygon
+ have int
+ want int
+ }{
+ {FullPolygon(), 0, 0},
+ {FullPolygon(), -1, 0},
+
+ {p1, 0, 0},
+ {p1, -1, 0},
+
+ // TODO: When multiple loops are supported, add more test cases.
+ }
+
+ for _, test := range tests {
+ if got := test.p.LastDescendant(test.have); got != test.want {
+ t.Errorf("%v.LastDescendant(%d) = %d, want %d", test.p, test.have, got, test.want)
+ }
+ }
+}
+
+func TestPolygonLoopIsHoleAndLoopSign(t *testing.T) {
+ if FullPolygon().loopIsHole(0) {
+ t.Errorf("the full polygons only loop should not be a hole")
+ }
+ if FullPolygon().loopSign(0) != 1 {
+ t.Errorf("the full polygons only loop should be postitive")
+ }
+
+ loop := LoopFromPoints(parsePoints("30:20, 40:20, 39:43, 33:35"))
+ p := PolygonFromLoops([]*Loop{loop})
+
+ if p.loopIsHole(0) {
+ t.Errorf("first loop in a polygon should not start out as a hole")
+ }
+ if p.loopSign(0) != 1 {
+ t.Errorf("first loop in a polygon should start out as positive")
+ }
+
+ // TODO: When multiple loops are supported, add more test cases to
+ // more fully show the parent levels.
+}
diff --git a/vendor/github.com/golang/geo/s2/polyline.go b/vendor/github.com/golang/geo/s2/polyline.go
index 6535ede..12abc85 100644
--- a/vendor/github.com/golang/geo/s2/polyline.go
+++ b/vendor/github.com/golang/geo/s2/polyline.go
@@ -154,6 +154,26 @@ func (p Polyline) Edge(i int) (a, b Point) {
return p[i], p[i+1]
}
+// dimension returns the dimension of the geometry represented by this Polyline.
+func (p Polyline) dimension() dimension { return polylineGeometry }
+
+// numChains reports the number of contiguous edge chains in this Polyline.
+func (p Polyline) numChains() int {
+ if p.NumEdges() >= 1 {
+ return 1
+ }
+ return 0
+}
+
+// chainStart returns the id of the first edge in the i-th edge chain in this Polyline.
+func (p Polyline) chainStart(i int) int {
+ if i == 0 {
+ return 0
+ }
+
+ return p.NumEdges()
+}
+
// HasInterior returns false as Polylines are not closed.
func (p Polyline) HasInterior() bool {
return false
diff --git a/vendor/github.com/golang/geo/s2/polyline_test.go b/vendor/github.com/golang/geo/s2/polyline_test.go
new file mode 100644
index 0000000..6c9a5ee
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/polyline_test.go
@@ -0,0 +1,144 @@
+/*
+Copyright 2016 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"
+)
+
+func TestPolylineBasics(t *testing.T) {
+ empty := Polyline{}
+ if empty.RectBound() != EmptyRect() {
+ t.Errorf("empty.RectBound() = %v, want %v", empty.RectBound(), EmptyRect())
+ }
+ if len(empty) != 0 {
+ t.Errorf("empty Polyline should have no vertices")
+ }
+ empty.Reverse()
+ if len(empty) != 0 {
+ t.Errorf("reveresed empty Polyline should have no vertices")
+ }
+
+ latlngs := []LatLng{
+ LatLngFromDegrees(0, 0),
+ LatLngFromDegrees(0, 90),
+ LatLngFromDegrees(0, 180),
+ }
+
+ semiEquator := PolylineFromLatLngs(latlngs)
+ //if got, want := semiEquator.Interpolate(0.5), Point{r3.Vector{0, 1, 0}}; !got.ApproxEqual(want) {
+ // t.Errorf("semiEquator.Interpolate(0.5) = %v, want %v", got, want)
+ //}
+ semiEquator.Reverse()
+ if got, want := semiEquator[2], (Point{r3.Vector{1, 0, 0}}); !got.ApproxEqual(want) {
+ t.Errorf("semiEquator[2] = %v, want %v", got, want)
+ }
+}
+
+func TestPolylineShape(t *testing.T) {
+ var shape Shape = makePolyline("0:0, 1:0, 1:1, 2:1")
+ if got, want := shape.NumEdges(), 3; got != want {
+ t.Errorf("%v.NumEdges() = %v, want %d", shape, got, want)
+ }
+
+ if got, want := shape.numChains(), 1; got != want {
+ t.Errorf("%v.numChains() = %d, want %d", shape, got, want)
+ }
+ if got, want := shape.chainStart(0), 0; got != want {
+ t.Errorf("%v.chainStart(0) = %d, want %d", shape, got, want)
+ }
+ if got, want := shape.chainStart(1), 3; got != want {
+ t.Errorf("%v.chainStart(1) = %d, want %d", shape, got, want)
+ }
+
+ v2, v3 := shape.Edge(2)
+ if want := PointFromLatLng(LatLngFromDegrees(1, 1)); !v2.ApproxEqual(want) {
+ t.Errorf("%v.Edge(%d) point A = %v want %v", shape, 2, v2, want)
+ }
+ if want := PointFromLatLng(LatLngFromDegrees(2, 1)); !v3.ApproxEqual(want) {
+ t.Errorf("%v.Edge(%d) point B = %v want %v", shape, 2, v3, want)
+ }
+
+ if shape.HasInterior() {
+ t.Errorf("polylines should not have an interior")
+ }
+ if shape.ContainsOrigin() {
+ t.Errorf("polylines should not contain the origin")
+ }
+
+ if shape.dimension() != polylineGeometry {
+ t.Errorf("polylines should have PolylineGeometry")
+ }
+
+ empty := &Polyline{}
+ if got, want := empty.NumEdges(), 0; got != want {
+ t.Errorf("%v.NumEdges() = %d, want %d", empty, got, want)
+ }
+ if got, want := empty.numChains(), 0; got != want {
+ t.Errorf("%v.numChains() = %d, want %d", empty, got, want)
+ }
+}
+
+func TestPolylineLengthAndCentroid(t *testing.T) {
+ // Construct random great circles and divide them randomly into segments.
+ // Then make sure that the length and centroid are correct. Note that
+ // because of the way the centroid is computed, it does not matter how
+ // we split the great circle into segments.
+
+ for i := 0; i < 100; i++ {
+ // Choose a coordinate frame for the great circle.
+ f := randomFrame()
+
+ var line Polyline
+ for theta := 0.0; theta < 2*math.Pi; theta += math.Pow(randomFloat64(), 10) {
+ p := Point{f.row(0).Mul(math.Cos(theta)).Add(f.row(1).Mul(math.Sin(theta)))}
+ if len(line) == 0 || !p.ApproxEqual(line[len(line)-1]) {
+ line = append(line, p)
+ }
+ }
+
+ // Close the circle.
+ line = append(line, line[0])
+
+ length := line.Length()
+ if got, want := math.Abs(length.Radians()-2*math.Pi), 2e-14; got > want {
+ t.Errorf("%v.Length() = %v, want < %v", line, got, want)
+ }
+
+ centroid := line.Centroid()
+ if got, want := centroid.Norm(), 2e-14; got > want {
+ t.Errorf("%v.Norm() = %v, want < %v", centroid, got, want)
+ }
+ }
+}
+
+func TestPolylineIntersectsCell(t *testing.T) {
+ pline := Polyline{
+ Point{r3.Vector{1, -1.1, 0.8}.Normalize()},
+ Point{r3.Vector{1, -0.8, 1.1}.Normalize()},
+ }
+
+ for face := 0; face < 6; face++ {
+ cell := CellFromCellID(CellIDFromFace(face))
+ if got, want := pline.IntersectsCell(cell), face&1 == 0; got != want {
+ t.Errorf("%v.IntersectsCell(%v) = %v, want %v", pline, cell, got, want)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/predicates_test.go b/vendor/github.com/golang/geo/s2/predicates_test.go
new file mode 100644
index 0000000..4e4a11c
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/predicates_test.go
@@ -0,0 +1,314 @@
+/*
+Copyright 2016 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"
+)
+
+func TestPredicatesSign(t *testing.T) {
+ tests := []struct {
+ p1x, p1y, p1z, p2x, p2y, p2z, p3x, p3y, p3z float64
+ want bool
+ }{
+ {1, 0, 0, 0, 1, 0, 0, 0, 1, true},
+ {0, 1, 0, 0, 0, 1, 1, 0, 0, true},
+ {0, 0, 1, 1, 0, 0, 0, 1, 0, true},
+ {1, 1, 0, 0, 1, 1, 1, 0, 1, true},
+ {-3, -1, 4, 2, -1, -3, 1, -2, 0, true},
+
+ // All degenerate cases of Sign(). Let M_1, M_2, ... be the sequence of
+ // submatrices whose determinant sign is tested by that function. Then the
+ // i-th test below is a 3x3 matrix M (with rows A, B, C) such that:
+ //
+ // det(M) = 0
+ // det(M_j) = 0 for j < i
+ // det(M_i) != 0
+ // A < B < C in lexicographic order.
+ // det(M_1) = b0*c1 - b1*c0
+ {-3, -1, 0, -2, 1, 0, 1, -2, 0, false},
+ // det(M_2) = b2*c0 - b0*c2
+ {-6, 3, 3, -4, 2, -1, -2, 1, 4, false},
+ // det(M_3) = b1*c2 - b2*c1
+ {0, -1, -1, 0, 1, -2, 0, 2, 1, false},
+ // From this point onward, B or C must be zero, or B is proportional to C.
+ // det(M_4) = c0*a1 - c1*a0
+ {-1, 2, 7, 2, 1, -4, 4, 2, -8, false},
+ // det(M_5) = c0
+ {-4, -2, 7, 2, 1, -4, 4, 2, -8, false},
+ // det(M_6) = -c1
+ {0, -5, 7, 0, -4, 8, 0, -2, 4, false},
+ // det(M_7) = c2*a0 - c0*a2
+ {-5, -2, 7, 0, 0, -2, 0, 0, -1, false},
+ // det(M_8) = c2
+ {0, -2, 7, 0, 0, 1, 0, 0, 2, false},
+ }
+
+ for _, test := range tests {
+ p1 := Point{r3.Vector{test.p1x, test.p1y, test.p1z}}
+ p2 := Point{r3.Vector{test.p2x, test.p2y, test.p2z}}
+ p3 := Point{r3.Vector{test.p3x, test.p3y, test.p3z}}
+ result := Sign(p1, p2, p3)
+ if result != test.want {
+ t.Errorf("Sign(%v, %v, %v) = %v, want %v", p1, p2, p3, result, test.want)
+ }
+ if test.want {
+ // For these cases we can test the reversibility condition
+ result = Sign(p3, p2, p1)
+ if result == test.want {
+ t.Errorf("Sign(%v, %v, %v) = %v, want %v", p3, p2, p1, result, !test.want)
+ }
+ }
+ }
+}
+
+// Points used in the various RobustSign tests.
+var (
+ // The following points happen to be *exactly collinear* along a line that it
+ // approximate tangent to the surface of the unit sphere. In fact, C is the
+ // exact midpoint of the line segment AB. All of these points are close
+ // enough to unit length to satisfy r3.Vector.IsUnit().
+ poA = Point{r3.Vector{0.72571927877036835, 0.46058825605889098, 0.51106749730504852}}
+ poB = Point{r3.Vector{0.7257192746638208, 0.46058826573818168, 0.51106749441312738}}
+ poC = Point{r3.Vector{0.72571927671709457, 0.46058826089853633, 0.51106749585908795}}
+
+ // The points "x1" and "x2" are exactly proportional, i.e. they both lie
+ // on a common line through the origin. Both points are considered to be
+ // normalized, and in fact they both satisfy (x == x.Normalize()).
+ // Therefore the triangle (x1, x2, -x1) consists of three distinct points
+ // that all lie on a common line through the origin.
+ x1 = Point{r3.Vector{0.99999999999999989, 1.4901161193847655e-08, 0}}
+ x2 = Point{r3.Vector{1, 1.4901161193847656e-08, 0}}
+
+ // Here are two more points that are distinct, exactly proportional, and
+ // that satisfy (x == x.Normalize()).
+ x3 = Point{r3.Vector{1, 1, 1}.Normalize()}
+ x4 = Point{x3.Mul(0.99999999999999989)}
+
+ // The following three points demonstrate that Normalize() is not idempotent, i.e.
+ // y0.Normalize() != y0.Normalize().Normalize(). Both points are exactly proportional.
+ y0 = Point{r3.Vector{1, 1, 0}}
+ y1 = Point{y0.Normalize()}
+ y2 = Point{y1.Normalize()}
+)
+
+func TestPredicatesRobustSignEqualities(t *testing.T) {
+ tests := []struct {
+ p1, p2 Point
+ want bool
+ }{
+ {Point{poC.Sub(poA.Vector)}, Point{poB.Sub(poC.Vector)}, true},
+ {x1, Point{x1.Normalize()}, true},
+ {x2, Point{x2.Normalize()}, true},
+ {x3, Point{x3.Normalize()}, true},
+ {x4, Point{x4.Normalize()}, true},
+ {x3, x4, false},
+ {y1, y2, false},
+ {y2, Point{y2.Normalize()}, true},
+ }
+
+ for _, test := range tests {
+ if got := test.p1.Vector == test.p2.Vector; got != test.want {
+ t.Errorf("Testing equality for RobustSign. %v = %v, got %v want %v", test.p1, test.p2, got, test.want)
+ }
+ }
+}
+
+func TestPredicatesRobustSign(t *testing.T) {
+ x := Point{r3.Vector{1, 0, 0}}
+ y := Point{r3.Vector{0, 1, 0}}
+ z := Point{r3.Vector{0, 0, 1}}
+
+ tests := []struct {
+ p1, p2, p3 Point
+ want Direction
+ }{
+ // Simple collinear points test cases.
+ // a == b != c
+ {x, x, z, Indeterminate},
+ // a != b == c
+ {x, y, y, Indeterminate},
+ // c == a != b
+ {z, x, z, Indeterminate},
+ // CCW
+ {x, y, z, CounterClockwise},
+ // CW
+ {z, y, x, Clockwise},
+
+ // Edge cases:
+ // The following points happen to be *exactly collinear* along a line that it
+ // approximate tangent to the surface of the unit sphere. In fact, C is the
+ // exact midpoint of the line segment AB. All of these points are close
+ // enough to unit length to satisfy S2::IsUnitLength().
+ {
+ // Until we get ExactSign, this will only return Indeterminate.
+ // It should be Clockwise.
+ poA, poB, poC, Indeterminate,
+ },
+
+ // The points "x1" and "x2" are exactly proportional, i.e. they both lie
+ // on a common line through the origin. Both points are considered to be
+ // normalized, and in fact they both satisfy (x == x.Normalize()).
+ // Therefore the triangle (x1, x2, -x1) consists of three distinct points
+ // that all lie on a common line through the origin.
+ {
+ // Until we get ExactSign, this will only return Indeterminate.
+ // It should be CounterClockwise.
+ x1, x2, Point{x1.Mul(-1.0)}, Indeterminate,
+ },
+
+ // Here are two more points that are distinct, exactly proportional, and
+ // that satisfy (x == x.Normalize()).
+ {
+ // Until we get ExactSign, this will only return Indeterminate.
+ // It should be Clockwise.
+ x3, x4, Point{x3.Mul(-1.0)}, Indeterminate,
+ },
+
+ // The following points demonstrate that Normalize() is not idempotent,
+ // i.e. y0.Normalize() != y0.Normalize().Normalize(). Both points satisfy
+ // S2::IsNormalized(), though, and the two points are exactly proportional.
+ {
+ // Until we get ExactSign, this will only return Indeterminate.
+ // It should be CounterClockwise.
+ y1, y2, Point{y1.Mul(-1.0)}, Indeterminate,
+ },
+ }
+
+ for _, test := range tests {
+ result := RobustSign(test.p1, test.p2, test.p3)
+ if result != test.want {
+ t.Errorf("RobustSign(%v, %v, %v) got %v, want %v",
+ test.p1, test.p2, test.p3, result, test.want)
+ }
+ // Test RobustSign(b,c,a) == RobustSign(a,b,c) for all a,b,c
+ rotated := RobustSign(test.p2, test.p3, test.p1)
+ if rotated != result {
+ t.Errorf("RobustSign(%v, %v, %v) vs Rotated RobustSign(%v, %v, %v) got %v, want %v",
+ test.p1, test.p2, test.p3, test.p2, test.p3, test.p1, rotated, result)
+ }
+ // Test RobustSign(c,b,a) == -RobustSign(a,b,c) for all a,b,c
+ want := Clockwise
+ if result == Clockwise {
+ want = CounterClockwise
+ } else if result == Indeterminate {
+ want = Indeterminate
+ }
+ reversed := RobustSign(test.p3, test.p2, test.p1)
+ if reversed != want {
+ t.Errorf("RobustSign(%v, %v, %v) vs Reversed RobustSign(%v, %v, %v) got %v, want %v",
+ test.p1, test.p2, test.p3, test.p3, test.p2, test.p1, reversed, -1*result)
+ }
+ }
+
+ // Test cases that should not be indeterminate.
+ /*
+ Uncomment these tests once RobustSign is completed.
+ if got := RobustSign(poA, poB, poC); got == Indeterminate {
+ t.Errorf("RobustSign(%v,%v,%v) = %v, want not Indeterminate", poA, poA, poA, got)
+ }
+ if got := RobustSign(x1, x2, Point{x1.Mul(-1)}); got == Indeterminate {
+ t.Errorf("RobustSign(%v,%v,%v) = %v, want not Indeterminate", x1, x2, x1.Mul(-1), got)
+ }
+ if got := RobustSign(x3, x4, Point{x3.Mul(-1)}); got == Indeterminate {
+ t.Errorf("RobustSign(%v,%v,%v) = %v, want not Indeterminate", x3, x4, x3.Mul(-1), got)
+ }
+ if got := RobustSign(y1, y2, Point{y1.Mul(-1)}); got == Indeterminate {
+ t.Errorf("RobustSign(%v,%v,%v) = %v, want not Indeterminate", x1, x2, y1.Mul(-1), got)
+ }
+ */
+}
+
+func TestPredicatesStableSignFailureRate(t *testing.T) {
+ const earthRadiusKm = 6371.01
+ const iters = 1000
+
+ // Verify that stableSign is able to handle most cases where the three
+ // points are as collinear as possible. (For reference, triageSign fails
+ // almost 100% of the time on this test.)
+ //
+ // Note that the failure rate *decreases* as the points get closer together,
+ // and the decrease is approximately linear. For example, the failure rate
+ // is 0.4% for collinear points spaced 1km apart, but only 0.0004% for
+ // collinear points spaced 1 meter apart.
+ //
+ // 1km spacing: < 1% (actual is closer to 0.4%)
+ // 10km spacing: < 10% (actual is closer to 4%)
+ want := 0.01
+ spacing := 1.0
+
+ // Estimate the probability that stableSign will not be able to compute
+ // the determinant sign of a triangle A, B, C consisting of three points
+ // that are as collinear as possible and spaced the given distance apart
+ // by counting up the times it returns Indeterminate.
+ failureCount := 0
+ m := math.Tan(spacing / earthRadiusKm)
+ for iter := 0; iter < iters; iter++ {
+ f := randomFrame()
+ a := f.col(0)
+ x := f.col(1)
+
+ b := Point{a.Sub(x.Mul(m)).Normalize()}
+ c := Point{a.Add(x.Mul(m)).Normalize()}
+ sign := stableSign(a, b, c)
+ if sign != Indeterminate {
+ // TODO(roberts): Once exactSign is implemented, uncomment this case.
+ //if got := exactSign(a, b, c, true); got != sign {
+ // t.Errorf("exactSign(%v, %v, %v, true) = %v, want %v", a, b, c, got, sign)
+ //}
+ } else {
+ failureCount++
+ }
+ }
+
+ rate := float64(failureCount) / float64(iters)
+ if rate >= want {
+ t.Errorf("stableSign failure rate for spacing %v km = %v, want %v", spacing, rate, want)
+ }
+}
+
+func BenchmarkSign(b *testing.B) {
+ p1 := Point{r3.Vector{-3, -1, 4}}
+ p2 := Point{r3.Vector{2, -1, -3}}
+ p3 := Point{r3.Vector{1, -2, 0}}
+ for i := 0; i < b.N; i++ {
+ Sign(p1, p2, p3)
+ }
+}
+
+// BenchmarkRobustSignSimple runs the benchmark for points that satisfy the first
+// checks in RobustSign to compare the performance to that of Sign().
+func BenchmarkRobustSignSimple(b *testing.B) {
+ p1 := Point{r3.Vector{-3, -1, 4}}
+ p2 := Point{r3.Vector{2, -1, -3}}
+ p3 := Point{r3.Vector{1, -2, 0}}
+ for i := 0; i < b.N; i++ {
+ RobustSign(p1, p2, p3)
+ }
+}
+
+// BenchmarkRobustSignNearCollinear runs the benchmark for points that are almost but not
+// quite collinear, so the tests have to use most of the calculations of RobustSign
+// before getting to an answer.
+func BenchmarkRobustSignNearCollinear(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ RobustSign(poA, poB, poC)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/rect.go b/vendor/github.com/golang/geo/s2/rect.go
index 134dc7e..6b5496a 100644
--- a/vendor/github.com/golang/geo/s2/rect.go
+++ b/vendor/github.com/golang/geo/s2/rect.go
@@ -21,6 +21,7 @@ import (
"math"
"github.com/golang/geo/r1"
+ "github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
@@ -239,7 +240,7 @@ func (r Rect) CapBound() Cap {
poleZ = 1
poleAngle = math.Pi/2 - r.Lat.Lo
}
- poleCap := CapFromCenterAngle(PointFromCoords(0, 0, poleZ), s1.Angle(poleAngle)*s1.Radian)
+ poleCap := CapFromCenterAngle(Point{r3.Vector{0, 0, poleZ}}, s1.Angle(poleAngle)*s1.Radian)
// For bounding rectangles that span 180 degrees or less in longitude, the
// maximum cap size is achieved at one of the rectangle vertices. For
@@ -298,14 +299,14 @@ func intersectsLatEdge(a, b Point, lat s1.Angle, lng s1.Interval) bool {
// the sphere. They can intersect a straight edge in 0, 1, or 2 points.
// First, compute the normal to the plane AB that points vaguely north.
- z := a.PointCross(b)
+ z := Point{a.PointCross(b).Normalize()}
if z.Z < 0 {
z = Point{z.Mul(-1)}
}
// Extend this to an orthonormal frame (x,y,z) where x is the direction
// where the great circle through AB achieves its maximium latitude.
- y := z.PointCross(PointFromCoords(0, 0, 1))
+ y := Point{z.PointCross(PointFromCoords(0, 0, 1)).Normalize()}
x := y.Cross(z.Vector)
// Compute the angle "theta" from the x-axis (in the x-y plane defined
diff --git a/vendor/github.com/golang/geo/s2/rect_test.go b/vendor/github.com/golang/geo/s2/rect_test.go
new file mode 100644
index 0000000..1bf3f47
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/rect_test.go
@@ -0,0 +1,862 @@
+/*
+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/r1"
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+func TestRectEmptyAndFull(t *testing.T) {
+ tests := []struct {
+ rect Rect
+ valid bool
+ empty bool
+ full bool
+ point bool
+ }{
+ {EmptyRect(), true, true, false, false},
+ {FullRect(), true, false, true, false},
+ }
+
+ for _, test := range tests {
+ if got := test.rect.IsValid(); got != test.valid {
+ t.Errorf("%v.IsValid() = %v, want %v", test.rect, got, test.valid)
+ }
+ if got := test.rect.IsEmpty(); got != test.empty {
+ t.Errorf("%v.IsEmpty() = %v, want %v", test.rect, got, test.empty)
+ }
+ if got := test.rect.IsFull(); got != test.full {
+ t.Errorf("%v.IsFull() = %v, want %v", test.rect, got, test.full)
+ }
+ if got := test.rect.IsPoint(); got != test.point {
+ t.Errorf("%v.IsPoint() = %v, want %v", test.rect, got, test.point)
+ }
+ }
+}
+
+func TestRectArea(t *testing.T) {
+ tests := []struct {
+ rect Rect
+ want float64
+ }{
+ {Rect{}, 0},
+ {FullRect(), 4 * math.Pi},
+ {Rect{r1.Interval{0, math.Pi / 2}, s1.Interval{0, math.Pi / 2}}, math.Pi / 2},
+ }
+ for _, test := range tests {
+ if got := test.rect.Area(); !float64Eq(got, test.want) {
+ t.Errorf("%v.Area() = %v, want %v", test.rect, got, test.want)
+ }
+ }
+}
+
+func TestRectString(t *testing.T) {
+ const want = "[Lo[-90.0000000, -180.0000000], Hi[90.0000000, 180.0000000]]"
+ if s := FullRect().String(); s != want {
+ t.Errorf("FullRect().String() = %q, want %q", s, want)
+ }
+}
+
+func TestRectFromLatLng(t *testing.T) {
+ ll := LatLngFromDegrees(23, 47)
+ got := RectFromLatLng(ll)
+ if got.Center() != ll {
+ t.Errorf("RectFromLatLng(%v).Center() = %v, want %v", ll, got.Center(), ll)
+ }
+ if !got.IsPoint() {
+ t.Errorf("RectFromLatLng(%v) = %v, want a point", ll, got)
+ }
+}
+
+func rectFromDegrees(latLo, lngLo, latHi, lngHi float64) Rect {
+ // Convenience method to construct a rectangle. This method is
+ // intentionally *not* in the S2LatLngRect interface because the
+ // argument order is ambiguous, but is fine for the test.
+ return Rect{
+ Lat: r1.Interval{
+ Lo: (s1.Angle(latLo) * s1.Degree).Radians(),
+ Hi: (s1.Angle(latHi) * s1.Degree).Radians(),
+ },
+ Lng: s1.IntervalFromEndpoints(
+ (s1.Angle(lngLo) * s1.Degree).Radians(),
+ (s1.Angle(lngHi) * s1.Degree).Radians(),
+ ),
+ }
+}
+
+func TestRectFromCenterSize(t *testing.T) {
+ tests := []struct {
+ center, size LatLng
+ want Rect
+ }{
+ {
+ LatLngFromDegrees(80, 170),
+ LatLngFromDegrees(40, 60),
+ rectFromDegrees(60, 140, 90, -160),
+ },
+ {
+ LatLngFromDegrees(10, 40),
+ LatLngFromDegrees(210, 400),
+ FullRect(),
+ },
+ {
+ LatLngFromDegrees(-90, 180),
+ LatLngFromDegrees(20, 50),
+ rectFromDegrees(-90, 155, -80, -155),
+ },
+ }
+ for _, test := range tests {
+ if got := RectFromCenterSize(test.center, test.size); !rectsApproxEqual(got, test.want, epsilon, epsilon) {
+ t.Errorf("RectFromCenterSize(%v,%v) was %v, want %v", test.center, test.size, got, test.want)
+ }
+ }
+}
+
+func TestRectAddPoint(t *testing.T) {
+ tests := []struct {
+ input Rect
+ point LatLng
+ want Rect
+ }{
+ {
+ Rect{r1.EmptyInterval(), s1.EmptyInterval()},
+ LatLngFromDegrees(0, 0),
+ rectFromDegrees(0, 0, 0, 0),
+ },
+ {
+ rectFromDegrees(0, 0, 0, 0),
+ LatLng{0 * s1.Radian, (-math.Pi / 2) * s1.Radian},
+ rectFromDegrees(0, -90, 0, 0),
+ },
+ {
+ rectFromDegrees(0, -90, 0, 0),
+ LatLng{(math.Pi / 4) * s1.Radian, (-math.Pi) * s1.Radian},
+ rectFromDegrees(0, -180, 45, 0),
+ },
+ {
+ rectFromDegrees(0, -180, 45, 0),
+ LatLng{(math.Pi / 2) * s1.Radian, 0 * s1.Radian},
+ rectFromDegrees(0, -180, 90, 0),
+ },
+ }
+ for _, test := range tests {
+ if got, want := test.input.AddPoint(test.point), test.want; !rectsApproxEqual(got, want, epsilon, epsilon) {
+ t.Errorf("%v.AddPoint(%v) was %v, want %v", test.input, test.point, got, want)
+ }
+ }
+}
+func TestRectVertex(t *testing.T) {
+ r1 := Rect{r1.Interval{0, math.Pi / 2}, s1.IntervalFromEndpoints(-math.Pi, 0)}
+ tests := []struct {
+ r Rect
+ i int
+ want LatLng
+ }{
+ {r1, 0, LatLng{0, math.Pi}},
+ {r1, 1, LatLng{0, 0}},
+ {r1, 2, LatLng{math.Pi / 2, 0}},
+ {r1, 3, LatLng{math.Pi / 2, math.Pi}},
+ }
+
+ for _, test := range tests {
+ if got := test.r.Vertex(test.i); got != test.want {
+ t.Errorf("%v.Vertex(%d) = %v, want %v", test.r, test.i, got, test.want)
+ }
+ }
+}
+func TestRectVertexCCWOrder(t *testing.T) {
+ for i := 0; i < 4; i++ {
+ lat := math.Pi / 4 * float64(i-2)
+ lng := math.Pi/2*float64(i-2) + 0.2
+ r := Rect{
+ r1.Interval{lat, lat + math.Pi/4},
+ s1.Interval{
+ math.Remainder(lng, 2*math.Pi),
+ math.Remainder(lng+math.Pi/2, 2*math.Pi),
+ },
+ }
+
+ for k := 0; k < 4; k++ {
+ if !Sign(PointFromLatLng(r.Vertex((k-1)&3)), PointFromLatLng(r.Vertex(k)), PointFromLatLng(r.Vertex((k+1)&3))) {
+ t.Errorf("%v.Vertex(%v), vertices were not in CCW order", r, k)
+ }
+ }
+ }
+}
+
+func TestRectContainsLatLng(t *testing.T) {
+ tests := []struct {
+ input Rect
+ ll LatLng
+ want bool
+ }{
+ {
+ rectFromDegrees(0, -180, 90, 0),
+ LatLngFromDegrees(30, -45),
+ true,
+ },
+ {
+ rectFromDegrees(0, -180, 90, 0),
+ LatLngFromDegrees(30, 45),
+ false,
+ },
+ {
+ rectFromDegrees(0, -180, 90, 0),
+ LatLngFromDegrees(0, -180),
+ true,
+ },
+ {
+ rectFromDegrees(0, -180, 90, 0),
+ LatLngFromDegrees(90, 0),
+ true,
+ },
+ }
+ for _, test := range tests {
+ if got, want := test.input.ContainsLatLng(test.ll), test.want; got != want {
+ t.Errorf("%v.ContainsLatLng(%v) was %v, want %v", test.input, test.ll, got, want)
+ }
+ }
+}
+
+func TestRectExpanded(t *testing.T) {
+ tests := []struct {
+ input Rect
+ margin LatLng
+ want Rect
+ }{
+ {
+ rectFromDegrees(70, 150, 80, 170),
+ LatLngFromDegrees(20, 30),
+ rectFromDegrees(50, 120, 90, -160),
+ },
+ {
+ EmptyRect(),
+ LatLngFromDegrees(20, 30),
+ EmptyRect(),
+ },
+ {
+ FullRect(),
+ LatLngFromDegrees(500, 500),
+ FullRect(),
+ },
+ {
+ rectFromDegrees(-90, 170, 10, 20),
+ LatLngFromDegrees(30, 80),
+ rectFromDegrees(-90, -180, 40, 180),
+ },
+
+ // Negative margins.
+ {
+ rectFromDegrees(10, -50, 60, 70),
+ LatLngFromDegrees(-10, -10),
+ rectFromDegrees(20, -40, 50, 60),
+ },
+ {
+ rectFromDegrees(-20, -180, 20, 180),
+ LatLngFromDegrees(-10, -10),
+ rectFromDegrees(-10, -180, 10, 180),
+ },
+ {
+ rectFromDegrees(-20, -180, 20, 180),
+ LatLngFromDegrees(-30, -30),
+ EmptyRect(),
+ },
+ {
+ rectFromDegrees(-90, 10, 90, 11),
+ LatLngFromDegrees(-10, -10),
+ EmptyRect(),
+ },
+ {
+ rectFromDegrees(-90, 10, 90, 100),
+ LatLngFromDegrees(-10, -10),
+ rectFromDegrees(-80, 20, 80, 90),
+ },
+ {
+ EmptyRect(),
+ LatLngFromDegrees(-50, -500),
+ EmptyRect(),
+ },
+ {
+ FullRect(),
+ LatLngFromDegrees(-50, -50),
+ rectFromDegrees(-40, -180, 40, 180),
+ },
+
+ // Mixed margins.
+ {
+ rectFromDegrees(10, -50, 60, 70),
+ LatLngFromDegrees(-10, 30),
+ rectFromDegrees(20, -80, 50, 100),
+ },
+ {
+ rectFromDegrees(-20, -180, 20, 180),
+ LatLngFromDegrees(10, -500),
+ rectFromDegrees(-30, -180, 30, 180),
+ },
+ {
+ rectFromDegrees(-90, -180, 80, 180),
+ LatLngFromDegrees(-30, 500),
+ rectFromDegrees(-60, -180, 50, 180),
+ },
+ {
+ rectFromDegrees(-80, -100, 80, 150),
+ LatLngFromDegrees(30, -50),
+ rectFromDegrees(-90, -50, 90, 100),
+ },
+ {
+ rectFromDegrees(0, -180, 50, 180),
+ LatLngFromDegrees(-30, 500),
+ EmptyRect(),
+ },
+ {
+ rectFromDegrees(-80, 10, 70, 20),
+ LatLngFromDegrees(30, -200),
+ EmptyRect(),
+ },
+ {
+ EmptyRect(),
+ LatLngFromDegrees(100, -100),
+ EmptyRect(),
+ },
+ {
+ FullRect(),
+ LatLngFromDegrees(100, -100),
+ FullRect(),
+ },
+ }
+ for _, test := range tests {
+ if got, want := test.input.expanded(test.margin), test.want; !rectsApproxEqual(got, want, epsilon, epsilon) {
+ t.Errorf("%v.Expanded(%v) was %v, want %v", test.input, test.margin, got, want)
+ }
+ }
+}
+
+func TestRectPolarClosure(t *testing.T) {
+ tests := []struct {
+ r Rect
+ want Rect
+ }{
+ {
+ rectFromDegrees(-89, 0, 89, 1),
+ rectFromDegrees(-89, 0, 89, 1),
+ },
+ {
+ rectFromDegrees(-90, -30, -45, 100),
+ rectFromDegrees(-90, -180, -45, 180),
+ },
+ {
+ rectFromDegrees(89, 145, 90, 146),
+ rectFromDegrees(89, -180, 90, 180),
+ },
+ {
+ rectFromDegrees(-90, -145, 90, -144),
+ FullRect(),
+ },
+ }
+ for _, test := range tests {
+ if got := test.r.PolarClosure(); !rectsApproxEqual(got, test.want, epsilon, epsilon) {
+ t.Errorf("%v.PolarClosure() was %v, want %v", test.r, got, test.want)
+ }
+ }
+}
+
+func TestRectCapBound(t *testing.T) {
+ tests := []struct {
+ r Rect
+ want Cap
+ }{
+ { // Bounding cap at center is smaller.
+ rectFromDegrees(-45, -45, 45, 45),
+ CapFromCenterHeight(Point{r3.Vector{1, 0, 0}}, 0.5),
+ },
+ { // Bounding cap at north pole is smaller.
+ rectFromDegrees(88, -80, 89, 80),
+ CapFromCenterAngle(Point{r3.Vector{0, 0, 1}}, s1.Angle(2)*s1.Degree),
+ },
+ { // Longitude span > 180 degrees.
+ rectFromDegrees(-30, -150, -10, 50),
+ CapFromCenterAngle(Point{r3.Vector{0, 0, -1}}, s1.Angle(80)*s1.Degree),
+ },
+ }
+ for _, test := range tests {
+ if got := test.r.CapBound(); !test.want.ApproxEqual(got) {
+ t.Errorf("%v.CapBound() was %v, want %v", test.r, got, test.want)
+ }
+ }
+}
+
+func TestRectIntervalOps(t *testing.T) {
+ // Rectangle that covers one-quarter of the sphere.
+ rect := rectFromDegrees(0, -180, 90, 0)
+
+ // Test operations where one rectangle consists of a single point.
+ rectMid := rectFromDegrees(45, -90, 45, -90)
+ rect180 := rectFromDegrees(0, -180, 0, -180)
+ northPole := rectFromDegrees(90, 0, 90, 0)
+
+ tests := []struct {
+ rect Rect
+ other Rect
+ contains bool
+ intersects bool
+ union Rect
+ intersection Rect
+ }{
+ {
+ rect: rect,
+ other: rectMid,
+ contains: true,
+ intersects: true,
+ union: rect,
+ intersection: rectMid,
+ },
+ {
+ rect: rect,
+ other: rect180,
+ contains: true,
+ intersects: true,
+ union: rect,
+ intersection: rect180,
+ },
+ {
+ rect: rect,
+ other: northPole,
+ contains: true,
+ intersects: true,
+ union: rect,
+ intersection: northPole,
+ },
+ {
+ rect: rect,
+ other: rectFromDegrees(-10, -1, 1, 20),
+ contains: false,
+ intersects: true,
+ union: rectFromDegrees(-10, 180, 90, 20),
+ intersection: rectFromDegrees(0, -1, 1, 0),
+ },
+ {
+ rect: rect,
+ other: rectFromDegrees(-10, -1, 0, 20),
+ contains: false,
+ intersects: true,
+ union: rectFromDegrees(-10, 180, 90, 20),
+ intersection: rectFromDegrees(0, -1, 0, 0),
+ },
+ {
+ rect: rect,
+ other: rectFromDegrees(-10, 0, 1, 20),
+ contains: false,
+ intersects: true,
+ union: rectFromDegrees(-10, 180, 90, 20),
+ intersection: rectFromDegrees(0, 0, 1, 0),
+ },
+ {
+ rect: rectFromDegrees(-15, -160, -15, -150),
+ other: rectFromDegrees(20, 145, 25, 155),
+ contains: false,
+ intersects: false,
+ union: rectFromDegrees(-15, 145, 25, -150),
+ intersection: EmptyRect(),
+ },
+ {
+ rect: rectFromDegrees(70, -10, 90, -140),
+ other: rectFromDegrees(60, 175, 80, 5),
+ contains: false,
+ intersects: true,
+ union: rectFromDegrees(60, -180, 90, 180),
+ intersection: rectFromDegrees(70, 175, 80, 5),
+ },
+
+ // Check that the intersection of two rectangles that overlap in latitude
+ // but not longitude is valid, and vice versa.
+ {
+ rect: rectFromDegrees(12, 30, 60, 60),
+ other: rectFromDegrees(0, 0, 30, 18),
+ contains: false,
+ intersects: false,
+ union: rectFromDegrees(0, 0, 60, 60),
+ intersection: EmptyRect(),
+ },
+ {
+ rect: rectFromDegrees(0, 0, 18, 42),
+ other: rectFromDegrees(30, 12, 42, 60),
+ contains: false,
+ intersects: false,
+ union: rectFromDegrees(0, 0, 42, 60),
+ intersection: EmptyRect(),
+ },
+ }
+ for _, test := range tests {
+ if got := test.rect.Contains(test.other); got != test.contains {
+ t.Errorf("%v.Contains(%v) = %t, want %t", test.rect, test.other, got, test.contains)
+ }
+
+ if got := test.rect.Intersects(test.other); got != test.intersects {
+ t.Errorf("%v.Intersects(%v) = %t, want %t", test.rect, test.other, got, test.intersects)
+ }
+
+ if got := test.rect.Union(test.other) == test.rect; test.rect.Contains(test.other) != got {
+ t.Errorf("%v.Union(%v) == %v = %t, want %t",
+ test.rect, test.other, test.other, got, test.rect.Contains(test.other),
+ )
+ }
+
+ if got := test.rect.Intersection(test.other).IsEmpty(); test.rect.Intersects(test.other) == got {
+ t.Errorf("%v.Intersection(%v).IsEmpty() = %t, want %t",
+ test.rect, test.other, got, test.rect.Intersects(test.other))
+ }
+
+ if got := test.rect.Union(test.other); got != test.union {
+ t.Errorf("%v.Union(%v) = %v, want %v", test.rect, test.other, got, test.union)
+ }
+
+ if got := test.rect.Intersection(test.other); got != test.intersection {
+ t.Errorf("%v.Intersection(%v) = %v, want %v", test.rect, test.other, got, test.intersection)
+ }
+ }
+}
+
+func TestRectCellOps(t *testing.T) {
+ cell0 := CellFromPoint(Point{r3.Vector{1 + 1e-12, 1, 1}})
+ v0 := LatLngFromPoint(cell0.Vertex(0))
+
+ cell202 := CellFromCellID(CellIDFromFacePosLevel(2, 0, 2))
+ bound202 := cell202.RectBound()
+
+ tests := []struct {
+ r Rect
+ c Cell
+ contains bool
+ intersects bool
+ }{
+ // Special cases
+ {
+ r: EmptyRect(),
+ c: CellFromCellID(CellIDFromFacePosLevel(3, 0, 0)),
+ contains: false,
+ intersects: false,
+ },
+ {
+ r: FullRect(),
+ c: CellFromCellID(CellIDFromFacePosLevel(2, 0, 0)),
+ contains: true,
+ intersects: true,
+ },
+ {
+ r: FullRect(),
+ c: CellFromCellID(CellIDFromFacePosLevel(5, 0, 25)),
+ contains: true,
+ intersects: true,
+ },
+ // This rectangle includes the first quadrant of face 0. It's expanded
+ // slightly because cell bounding rectangles are slightly conservative.
+ {
+ r: rectFromDegrees(-45.1, -45.1, 0.1, 0.1),
+ c: CellFromCellID(CellIDFromFacePosLevel(0, 0, 0)),
+ contains: false,
+ intersects: true,
+ },
+ {
+ r: rectFromDegrees(-45.1, -45.1, 0.1, 0.1),
+ c: CellFromCellID(CellIDFromFacePosLevel(0, 0, 1)),
+ contains: true,
+ intersects: true,
+ },
+ {
+ r: rectFromDegrees(-45.1, -45.1, 0.1, 0.1),
+ c: CellFromCellID(CellIDFromFacePosLevel(1, 0, 1)),
+ contains: false,
+ intersects: false,
+ },
+ // This rectangle intersects the first quadrant of face 0.
+ {
+ r: rectFromDegrees(-10, -45, 10, 0),
+ c: CellFromCellID(CellIDFromFacePosLevel(0, 0, 0)),
+ contains: false,
+ intersects: true,
+ },
+ {
+ r: rectFromDegrees(-10, -45, 10, 0),
+ c: CellFromCellID(CellIDFromFacePosLevel(0, 0, 1)),
+ contains: false,
+ intersects: true,
+ },
+ {
+ r: rectFromDegrees(-10, -45, 10, 0),
+ c: CellFromCellID(CellIDFromFacePosLevel(1, 0, 1)),
+ contains: false,
+ intersects: false,
+ },
+ // Rectangle consisting of a single point.
+ {
+ r: rectFromDegrees(4, 4, 4, 4),
+ c: CellFromCellID(CellIDFromFace(0)),
+ contains: false,
+ intersects: true,
+ },
+ // Rectangles that intersect the bounding rectangle of a face
+ // but not the face itself.
+ {
+ r: rectFromDegrees(41, -87, 42, -79),
+ c: CellFromCellID(CellIDFromFace(2)),
+ contains: false,
+ intersects: false,
+ },
+ {
+ r: rectFromDegrees(-41, 160, -40, -160),
+ c: CellFromCellID(CellIDFromFace(5)),
+ contains: false,
+ intersects: false,
+ },
+ {
+ // This is the leaf cell at the top right hand corner of face 0.
+ // It has two angles of 60 degrees and two of 120 degrees.
+ r: rectFromDegrees(v0.Lat.Degrees()-1e-8,
+ v0.Lng.Degrees()-1e-8,
+ v0.Lat.Degrees()-2e-10,
+ v0.Lng.Degrees()+1e-10),
+ c: cell0,
+ contains: false,
+ intersects: false,
+ },
+ {
+ // Rectangles that intersect a face but where no vertex of one region
+ // is contained by the other region. The first one passes through
+ // a corner of one of the face cells.
+ r: rectFromDegrees(-37, -70, -36, -20),
+ c: CellFromCellID(CellIDFromFace(5)),
+ contains: false,
+ intersects: true,
+ },
+ {
+ // These two intersect like a diamond and a square.
+ r: rectFromDegrees(bound202.Lo().Lat.Degrees()+3,
+ bound202.Lo().Lng.Degrees()+3,
+ bound202.Hi().Lat.Degrees()-3,
+ bound202.Hi().Lng.Degrees()-3),
+ c: cell202,
+ contains: false,
+ intersects: true,
+ },
+ {
+ // from a bug report
+ r: rectFromDegrees(34.2572864, 135.2673642, 34.2707907, 135.2995742),
+ c: CellFromCellID(0x6007500000000000),
+ contains: false,
+ intersects: true,
+ },
+ }
+
+ for _, test := range tests {
+ if got := test.r.ContainsCell(test.c); got != test.contains {
+ t.Errorf("%v.ContainsCell(%v) = %t, want %t", test.r, test.c, got, test.contains)
+ }
+
+ if got := test.r.IntersectsCell(test.c); got != test.intersects {
+ t.Errorf("%v.IntersectsCell(%v) = %t, want %t", test.r, test.c, got, test.intersects)
+ }
+ }
+
+}
+
+func TestRectContainsPoint(t *testing.T) {
+ r1 := rectFromDegrees(0, -180, 90, 0)
+
+ tests := []struct {
+ r Rect
+ p Point
+ want bool
+ }{
+ {r1, Point{r3.Vector{0.5, -0.3, 0.1}}, true},
+ {r1, Point{r3.Vector{0.5, 0.2, 0.1}}, false},
+ }
+ for _, test := range tests {
+ if got, want := test.r.ContainsPoint(test.p), test.want; got != want {
+ t.Errorf("%v.ContainsPoint(%v) was %v, want %v", test.r, test.p, got, want)
+ }
+ }
+}
+
+func TestRectIntersectsLatEdge(t *testing.T) {
+ tests := []struct {
+ a, b Point
+ lat s1.Angle
+ lngLo s1.Angle
+ lngHi s1.Angle
+ want bool
+ }{
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ lat: 41 * s1.Degree,
+ lngLo: -87 * s1.Degree,
+ lngHi: -79 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ lat: 42 * s1.Degree,
+ lngLo: -87 * s1.Degree,
+ lngHi: -79 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, -1}},
+ b: Point{r3.Vector{1, 1, 0}},
+ lat: -3 * s1.Degree,
+ lngLo: -1 * s1.Degree,
+ lngHi: 23 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{1, 0, 1}},
+ b: Point{r3.Vector{1, -1, 0}},
+ lat: -28 * s1.Degree,
+ lngLo: 69 * s1.Degree,
+ lngHi: 115 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{0, 1, 0}},
+ b: Point{r3.Vector{1, -1, -1}},
+ lat: 44 * s1.Degree,
+ lngLo: 60 * s1.Degree,
+ lngHi: 177 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{0, 1, 1}},
+ b: Point{r3.Vector{0, 1, -1}},
+ lat: -25 * s1.Degree,
+ lngLo: -74 * s1.Degree,
+ lngHi: -165 * s1.Degree,
+ want: true,
+ },
+ {
+ a: Point{r3.Vector{1, 0, 0}},
+ b: Point{r3.Vector{0, 0, -1}},
+ lat: -4 * s1.Degree,
+ lngLo: -152 * s1.Degree,
+ lngHi: 171 * s1.Degree,
+ want: true,
+ },
+ // from a bug report
+ {
+ a: Point{r3.Vector{-0.589375791872893683986945, 0.583248451588733285433364, 0.558978908075738245564423}},
+ b: Point{r3.Vector{-0.587388131301997518107783, 0.581281455376392863776402, 0.563104832905072516524569}},
+ lat: 34.2572864 * s1.Degree,
+ lngLo: 2.3608609 * s1.Radian,
+ lngHi: 2.3614230 * s1.Radian,
+ want: true,
+ },
+ }
+
+ for _, test := range tests {
+ if got := intersectsLatEdge(test.a, test.b, test.lat, s1.Interval{float64(test.lngLo), float64(test.lngHi)}); got != test.want {
+ t.Errorf("intersectsLatEdge(%v, %v, %v, {%v, %v}) = %t, want %t",
+ test.a, test.b, test.lat, test.lngLo, test.lngHi, got, test.want)
+ }
+ }
+}
+
+func TestRectIntersectsLngEdge(t *testing.T) {
+ tests := []struct {
+ a, b Point
+ latLo s1.Angle
+ latHi s1.Angle
+ lng s1.Angle
+ want bool
+ }{
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ latLo: 41 * s1.Degree,
+ latHi: 42 * s1.Degree,
+ lng: -79 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ latLo: 41 * s1.Degree,
+ latHi: 42 * s1.Degree,
+ lng: -87 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ latLo: 42 * s1.Degree,
+ latHi: 41 * s1.Degree,
+ lng: 79 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, 1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ latLo: 41 * s1.Degree,
+ latHi: 42 * s1.Degree,
+ lng: 87 * s1.Degree,
+ want: false,
+ },
+ {
+ a: Point{r3.Vector{0, -1, -1}},
+ b: Point{r3.Vector{-1, 0, -1}},
+ latLo: -87 * s1.Degree,
+ latHi: 13 * s1.Degree,
+ lng: -143 * s1.Degree,
+ want: true,
+ },
+ {
+ a: Point{r3.Vector{1, 1, -1}},
+ b: Point{r3.Vector{1, -1, 1}},
+ latLo: -64 * s1.Degree,
+ latHi: 13 * s1.Degree,
+ lng: 40 * s1.Degree,
+ want: true,
+ },
+ {
+ a: Point{r3.Vector{1, 1, 0}},
+ b: Point{r3.Vector{-1, 0, -1}},
+ latLo: -64 * s1.Degree,
+ latHi: 56 * s1.Degree,
+ lng: 151 * s1.Degree,
+ want: true,
+ },
+ {
+ a: Point{r3.Vector{-1, -1, 0}},
+ b: Point{r3.Vector{1, -1, -1}},
+ latLo: -50 * s1.Degree,
+ latHi: 18 * s1.Degree,
+ lng: -84 * s1.Degree,
+ want: true,
+ },
+ }
+
+ for _, test := range tests {
+ if got := intersectsLngEdge(test.a, test.b, r1.Interval{float64(test.latLo), float64(test.latHi)}, test.lng); got != test.want {
+ t.Errorf("intersectsLngEdge(%v, %v, {%v, %v}, %v) = %v, want %v",
+ test.a, test.b, test.latLo, test.latHi, test.lng, got, test.want)
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/region.go b/vendor/github.com/golang/geo/s2/region.go
index f1e127e..b520c07 100644
--- a/vendor/github.com/golang/geo/s2/region.go
+++ b/vendor/github.com/golang/geo/s2/region.go
@@ -44,6 +44,7 @@ var (
_ Region = Cap{}
_ Region = Cell{}
_ Region = (*CellUnion)(nil)
+ _ Region = Point{}
//_ Region = (*Polygon)(nil)
_ Region = Polyline{}
_ Region = Rect{}
diff --git a/vendor/github.com/golang/geo/s2/regioncoverer_test.go b/vendor/github.com/golang/geo/s2/regioncoverer_test.go
new file mode 100644
index 0000000..8353bfb
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/regioncoverer_test.go
@@ -0,0 +1,151 @@
+/*
+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"
+ "math/rand"
+ "reflect"
+ "testing"
+)
+
+func TestCovererRandomCells(t *testing.T) {
+ rc := &RegionCoverer{MinLevel: 0, MaxLevel: 30, LevelMod: 1, MaxCells: 1}
+
+ // Test random cell ids at all levels.
+ for i := 0; i < 10000; i++ {
+ id := CellID(randomUint64())
+ for !id.IsValid() {
+ id = CellID(randomUint64())
+ }
+ covering := rc.Covering(Region(CellFromCellID(id)))
+ if len(covering) != 1 {
+ t.Errorf("Iteration %d, cell ID token %s, got covering size = %d, want covering size = 1", i, id.ToToken(), len(covering))
+ }
+ if (covering)[0] != id {
+ t.Errorf("Iteration %d, cell ID token %s, got covering = %v, want covering = %v", i, id.ToToken(), covering, id)
+ }
+ }
+}
+
+// checkCovering reports whether covering is a valid cover for the region.
+func checkCovering(t *testing.T, rc *RegionCoverer, r Region, covering CellUnion, interior bool) {
+ // Keep track of how many cells have the same rc.MinLevel ancestor.
+ minLevelCells := map[CellID]int{}
+ var tempCover CellUnion
+ for _, ci := range covering {
+ level := ci.Level()
+ if level < rc.MinLevel {
+ t.Errorf("CellID(%s).Level() = %d, want >= %d", ci.ToToken(), level, rc.MinLevel)
+ }
+ if level > rc.MaxLevel {
+ t.Errorf("CellID(%s).Level() = %d, want <= %d", ci.ToToken(), level, rc.MaxLevel)
+ }
+ if rem := (level - rc.MinLevel) % rc.LevelMod; rem != 0 {
+ t.Errorf("(CellID(%s).Level() - MinLevel) mod LevelMod = %d, want = %d", ci.ToToken(), rem, 0)
+ }
+ tempCover = append(tempCover, ci)
+ minLevelCells[ci.Parent(rc.MinLevel)]++
+ }
+ if len(covering) > rc.MaxCells {
+ // If the covering has more than the requested number of cells, then check
+ // that the cell count cannot be reduced by using the parent of some cell.
+ for ci, count := range minLevelCells {
+ if count > 1 {
+ t.Errorf("Min level CellID %s, count = %d, want = %d", ci.ToToken(), count, 1)
+ }
+ }
+ }
+ if interior {
+ for _, ci := range covering {
+ if !r.ContainsCell(CellFromCellID(ci)) {
+ t.Errorf("Region(%v).ContainsCell(%v) = %t, want = %t", r, CellFromCellID(ci), false, true)
+ }
+ }
+ } else {
+ tempCover.Normalize()
+ checkCoveringTight(t, r, tempCover, true, 0)
+ }
+}
+
+// checkCoveringTight checks that "cover" completely covers the given region.
+// If "checkTight" is true, also checks that it does not contain any cells that
+// do not intersect the given region. ("id" is only used internally.)
+func checkCoveringTight(t *testing.T, r Region, cover CellUnion, checkTight bool, id CellID) {
+ if !id.IsValid() {
+ for f := 0; f < 6; f++ {
+ checkCoveringTight(t, r, cover, checkTight, CellIDFromFace(f))
+ }
+ return
+ }
+
+ if !r.IntersectsCell(CellFromCellID(id)) {
+ // If region does not intersect id, then neither should the covering.
+ if got := cover.IntersectsCellID(id); checkTight && got {
+ t.Errorf("CellUnion(%v).IntersectsCellID(%s) = %t; want = %t", cover, id.ToToken(), got, false)
+ }
+ } else if !cover.ContainsCellID(id) {
+ // The region may intersect id, but we can't assert that the covering
+ // intersects id because we may discover that the region does not actually
+ // intersect upon further subdivision. (IntersectsCell is not exact.)
+ if got := r.ContainsCell(CellFromCellID(id)); got {
+ t.Errorf("Region(%v).ContainsCell(%v) = %t; want = %t", r, CellFromCellID(id), got, false)
+ }
+ if got := id.IsLeaf(); got {
+ t.Errorf("CellID(%s).IsLeaf() = %t; want = %t", id.ToToken(), got, false)
+ }
+
+ for child := id.ChildBegin(); child != id.ChildEnd(); child = child.Next() {
+ checkCoveringTight(t, r, cover, checkTight, child)
+ }
+ }
+}
+
+func TestCovererRandomCaps(t *testing.T) {
+ rc := &RegionCoverer{}
+ for i := 0; i < 1000; i++ {
+ rc.MinLevel = int(rand.Int31n(maxLevel + 1))
+ rc.MaxLevel = int(rand.Int31n(maxLevel + 1))
+ for rc.MinLevel > rc.MaxLevel {
+ rc.MinLevel = int(rand.Int31n(maxLevel + 1))
+ rc.MaxLevel = int(rand.Int31n(maxLevel + 1))
+ }
+ rc.LevelMod = int(1 + rand.Int31n(3))
+ rc.MaxCells = int(skewedInt(10))
+
+ maxArea := math.Min(4*math.Pi, float64(3*rc.MaxCells+1)*AvgAreaMetric.Value(rc.MinLevel))
+ r := Region(randomCap(0.1*AvgAreaMetric.Value(maxLevel), maxArea))
+
+ covering := rc.Covering(r)
+ checkCovering(t, rc, r, covering, false)
+ interior := rc.InteriorCovering(r)
+ checkCovering(t, rc, r, interior, true)
+
+ // Check that Covering is deterministic.
+ covering2 := rc.Covering(r)
+ if !reflect.DeepEqual(covering, covering2) {
+ t.Errorf("Iteration %d, got covering = %v, want covering = %v", i, covering2, covering)
+ }
+
+ // Also check Denormalize. The denormalized covering
+ // may still be different and smaller than "covering" because
+ // s2.RegionCoverer does not guarantee that it will not output all four
+ // children of the same parent.
+ covering.Denormalize(rc.MinLevel, rc.LevelMod)
+ checkCovering(t, rc, r, covering, false)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/s2_test.go b/vendor/github.com/golang/geo/s2/s2_test.go
new file mode 100644
index 0000000..444479d
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/s2_test.go
@@ -0,0 +1,413 @@
+/*
+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 (
+ "fmt"
+ "math"
+ "math/rand"
+ "strconv"
+ "strings"
+
+ "github.com/golang/geo/r2"
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+// float64Eq reports whether the two values are within the default epsilon.
+func float64Eq(x, y float64) bool { return float64Near(x, y, epsilon) }
+
+// float64Near reports whether the two values are within the given epsilon.
+func float64Near(x, y, ε float64) bool {
+ return math.Abs(x-y) <= ε
+}
+
+// TODO(roberts): Add in flag to allow specifying the random seed for repeatable tests.
+
+// kmToAngle converts a distance on the Earth's surface to an angle.
+func kmToAngle(km float64) s1.Angle {
+ // The Earth's mean radius in kilometers (according to NASA).
+ const earthRadiusKm = 6371.01
+ return s1.Angle(km / earthRadiusKm)
+}
+
+// randomBits returns a 64-bit random unsigned integer whose lowest "num" are random, and
+// whose other bits are zero.
+func randomBits(num uint32) uint64 {
+ // Make sure the request is for not more than 63 bits.
+ if num > 63 {
+ num = 63
+ }
+ return uint64(rand.Int63()) & ((1 << num) - 1)
+}
+
+// Return a uniformly distributed 64-bit unsigned integer.
+func randomUint64() uint64 {
+ return uint64(rand.Int63() | (rand.Int63() << 63))
+}
+
+// Return a uniformly distributed 32-bit unsigned integer.
+func randomUint32() uint32 {
+ return uint32(randomBits(32))
+}
+
+// randomFloat64 returns a uniformly distributed value in the range [0,1).
+// Note that the values returned are all multiples of 2**-53, which means that
+// not all possible values in this range are returned.
+func randomFloat64() float64 {
+ const randomFloatBits = 53
+ return math.Ldexp(float64(randomBits(randomFloatBits)), -randomFloatBits)
+}
+
+// randomUniformInt returns a uniformly distributed integer in the range [0,n).
+// NOTE: This is replicated here to stay in sync with how the C++ code generates
+// uniform randoms. (instead of using Go's math/rand package directly).
+func randomUniformInt(n int) int {
+ return int(randomFloat64() * float64(n))
+}
+
+// randomUniformFloat64 returns a uniformly distributed value in the range [min, max).
+func randomUniformFloat64(min, max float64) float64 {
+ return min + randomFloat64()*(max-min)
+}
+
+// oneIn returns true with a probability of 1/n.
+func oneIn(n int) bool {
+ return randomUniformInt(n) == 0
+}
+
+// randomPoint returns a random unit-length vector.
+func randomPoint() Point {
+ return PointFromCoords(randomUniformFloat64(-1, 1),
+ randomUniformFloat64(-1, 1), randomUniformFloat64(-1, 1))
+}
+
+// randomFrame returns a right-handed coordinate frame (three orthonormal vectors) for
+// a randomly generated point.
+func randomFrame() *matrix3x3 {
+ return randomFrameAtPoint(randomPoint())
+}
+
+// randomFrameAtPoint returns a right-handed coordinate frame using the given
+// point as the z-axis. The x- and y-axes are computed such that (x,y,z) is a
+// right-handed coordinate frame (three orthonormal vectors).
+func randomFrameAtPoint(z Point) *matrix3x3 {
+ x := Point{z.Cross(randomPoint().Vector).Normalize()}
+ y := Point{z.Cross(x.Vector).Normalize()}
+
+ m := &matrix3x3{}
+ m.setCol(0, x)
+ m.setCol(1, y)
+ m.setCol(2, z)
+ return m
+}
+
+// randomCellIDForLevel returns a random CellID at the given level.
+// The distribution is uniform over the space of cell ids, but only
+// approximately uniform over the surface of the sphere.
+func randomCellIDForLevel(level int) CellID {
+ face := randomUniformInt(numFaces)
+ pos := randomUint64() & uint64((1<<posBits)-1)
+ return CellIDFromFacePosLevel(face, pos, level)
+}
+
+// randomCellID returns a random CellID at a randomly chosen
+// level. The distribution is uniform over the space of cell ids,
+// but only approximately uniform over the surface of the sphere.
+func randomCellID() CellID {
+ return randomCellIDForLevel(randomUniformInt(maxLevel + 1))
+}
+
+// parsePoint returns an Point from the latitude-longitude coordinate in degrees
+// in the given string, or the origin if the string was invalid.
+// e.g., "-20:150"
+func parsePoint(s string) Point {
+ p := parsePoints(s)
+ if len(p) > 0 {
+ return p[0]
+ }
+
+ return Point{r3.Vector{0, 0, 0}}
+}
+
+// parseRect returns the minimal bounding Rect that contains the one or more
+// latitude-longitude coordinates in degrees in the given string.
+// Examples of input:
+// "-20:150" // one point
+// "-20:150, -20:151, -19:150" // three points
+func parseRect(s string) Rect {
+ var rect Rect
+ lls := parseLatLngs(s)
+ if len(lls) > 0 {
+ rect = RectFromLatLng(lls[0])
+ }
+
+ for _, ll := range lls[1:] {
+ rect = rect.AddPoint(ll)
+ }
+
+ return rect
+}
+
+// parseLatLngs splits up a string of lat:lng points and returns the list of parsed
+// entries.
+func parseLatLngs(s string) []LatLng {
+ pieces := strings.Split(s, ",")
+ var lls []LatLng
+ for _, piece := range pieces {
+ piece = strings.TrimSpace(piece)
+
+ // Skip empty strings.
+ if piece == "" {
+ continue
+ }
+
+ p := strings.Split(piece, ":")
+ if len(p) != 2 {
+ panic(fmt.Sprintf("invalid input string for parseLatLngs: %q", piece))
+ }
+
+ lat, err := strconv.ParseFloat(p[0], 64)
+ if err != nil {
+ panic(fmt.Sprintf("invalid float in parseLatLngs: %q, err: %v", p[0], err))
+ }
+
+ lng, err := strconv.ParseFloat(p[1], 64)
+ if err != nil {
+ panic(fmt.Sprintf("invalid float in parseLatLngs: %q, err: %v", p[1], err))
+ }
+
+ lls = append(lls, LatLngFromDegrees(lat, lng))
+ }
+ return lls
+}
+
+// parsePoints takes a string of lat:lng points and returns the set of Points it defines.
+func parsePoints(s string) []Point {
+ lls := parseLatLngs(s)
+ points := make([]Point, len(lls))
+ for i, ll := range lls {
+ points[i] = PointFromLatLng(ll)
+ }
+ return points
+}
+
+// makeLoop constructs a loop from a comma separated string of lat:lng
+// coordinates in degrees. Example of the input format:
+// "-20:150, 10:-120, 0.123:-170.652"
+// The special strings "empty" or "full" create an empty or full loop respectively.
+func makeLoop(s string) *Loop {
+ if s == "full" {
+ return FullLoop()
+ }
+ if s == "empty" {
+ return EmptyLoop()
+ }
+
+ return LoopFromPoints(parsePoints(s))
+}
+
+// makePolygon constructs a polygon from the set of semicolon separated CSV
+// strings of lat:lng points defining each loop in the polygon. If the normalize
+// flag is set to true, loops are normalized by inverting them
+// if necessary so that they enclose at most half of the unit sphere.
+//
+// Examples of the input format:
+// "10:20, 90:0, 20:30" // one loop
+// "10:20, 90:0, 20:30; 5.5:6.5, -90:-180, -15.2:20.3" // two loops
+// "" // the empty polygon (consisting of no loops)
+// "full" // the full polygon (consisting of one full loop)
+// "empty" // **INVALID** (a polygon consisting of one empty loop)
+func makePolygon(s string, normalize bool) *Polygon {
+ strs := strings.Split(s, ";")
+ var loops []*Loop
+ for _, str := range strs {
+ if str == "" {
+ continue
+ }
+ loop := makeLoop(strings.TrimSpace(str))
+ if normalize {
+ // TODO(roberts): Uncomment once Normalize is implemented.
+ // loop.Normalize()
+ }
+ loops = append(loops, loop)
+ }
+ return PolygonFromLoops(loops)
+}
+
+// makePolyline constructs a Polyline from the given string of lat:lng values.
+func makePolyline(s string) Polyline {
+ return Polyline(parsePoints(s))
+}
+
+// concentricLoopsPolygon constructs a polygon with the specified center as a
+// number of concentric loops and vertices per loop.
+func concentricLoopsPolygon(center Point, numLoops, verticesPerLoop int) *Polygon {
+ var loops []*Loop
+ for li := 0; li < numLoops; li++ {
+ radius := s1.Angle(0.005 * float64(li+1) / float64(numLoops))
+ loops = append(loops, RegularLoop(center, radius, verticesPerLoop))
+ }
+ return PolygonFromLoops(loops)
+}
+
+// skewedInt returns a number in the range [0,2^max_log-1] with bias towards smaller numbers.
+func skewedInt(maxLog int) int {
+ base := uint32(rand.Int31n(int32(maxLog + 1)))
+ return int(randomBits(31) & ((1 << base) - 1))
+}
+
+// randomCap returns a cap with a random axis such that the log of its area is
+// uniformly distributed between the logs of the two given values. The log of
+// the cap angle is also approximately uniformly distributed.
+func randomCap(minArea, maxArea float64) Cap {
+ capArea := maxArea * math.Pow(minArea/maxArea, randomFloat64())
+ return CapFromCenterArea(randomPoint(), capArea)
+}
+
+// pointsApproxEquals reports whether the two points are within the given distance
+// of each other. This is the same as Point.ApproxEquals but permits specifying
+// the epsilon.
+func pointsApproxEquals(a, b Point, epsilon float64) bool {
+ return float64(a.Vector.Angle(b.Vector)) <= epsilon
+}
+
+var (
+ rectErrorLat = 10 * dblEpsilon
+ rectErrorLng = dblEpsilon
+)
+
+// r2PointsApproxEqual reports whether the two points are within the given epsilon.
+func r2PointsApproxEquals(a, b r2.Point, epsilon float64) bool {
+ return float64Near(a.X, b.X, epsilon) && float64Near(a.Y, b.Y, epsilon)
+}
+
+// rectsApproxEqual reports whether the two rect are within the given tolerances
+// at each corner from each other. The tolerances are specific to each axis.
+func rectsApproxEqual(a, b Rect, tolLat, tolLng float64) bool {
+ return math.Abs(a.Lat.Lo-b.Lat.Lo) < tolLat &&
+ math.Abs(a.Lat.Hi-b.Lat.Hi) < tolLat &&
+ math.Abs(a.Lng.Lo-b.Lng.Lo) < tolLng &&
+ math.Abs(a.Lng.Hi-b.Lng.Hi) < tolLng
+}
+
+// matricesApproxEqual reports whether all cells in both matrices are equal within
+// the default floating point epsilon.
+func matricesApproxEqual(m1, m2 *matrix3x3) bool {
+ return float64Eq(m1[0][0], m2[0][0]) &&
+ float64Eq(m1[0][1], m2[0][1]) &&
+ float64Eq(m1[0][2], m2[0][2]) &&
+
+ float64Eq(m1[1][0], m2[1][0]) &&
+ float64Eq(m1[1][1], m2[1][1]) &&
+ float64Eq(m1[1][2], m2[1][2]) &&
+
+ float64Eq(m1[2][0], m2[2][0]) &&
+ float64Eq(m1[2][1], m2[2][1]) &&
+ float64Eq(m1[2][2], m2[2][2])
+}
+
+// samplePointFromRect returns a point chosen uniformly at random (with respect
+// to area on the sphere) from the given rectangle.
+func samplePointFromRect(rect Rect) Point {
+ // First choose a latitude uniformly with respect to area on the sphere.
+ sinLo := math.Sin(rect.Lat.Lo)
+ sinHi := math.Sin(rect.Lat.Hi)
+ lat := math.Asin(randomUniformFloat64(sinLo, sinHi))
+
+ // Now choose longitude uniformly within the given range.
+ lng := rect.Lng.Lo + randomFloat64()*rect.Lng.Length()
+
+ return PointFromLatLng(LatLng{s1.Angle(lat), s1.Angle(lng)}.Normalized())
+}
+
+// samplePointFromCap returns a point chosen uniformly at random (with respect
+// to area) from the given cap.
+func samplePointFromCap(c Cap) Point {
+ // We consider the cap axis to be the "z" axis. We choose two other axes to
+ // complete the coordinate frame.
+ m := getFrame(c.Center())
+
+ // The surface area of a spherical cap is directly proportional to its
+ // height. First we choose a random height, and then we choose a random
+ // point along the circle at that height.
+ h := randomFloat64() * c.Height()
+ theta := 2 * math.Pi * randomFloat64()
+ r := math.Sqrt(h * (2 - h))
+
+ // The result should already be very close to unit-length, but we might as
+ // well make it accurate as possible.
+ return Point{fromFrame(m, PointFromCoords(math.Cos(theta)*r, math.Sin(theta)*r, 1-h)).Normalize()}
+}
+
+// perturbATowardsB returns a point that has been shifted some distance towards the
+// second point based on a random number.
+func perturbATowardsB(a, b Point) Point {
+ choice := randomFloat64()
+ if choice < 0.1 {
+ return a
+ }
+ if choice < 0.3 {
+ // Return a point that is exactly proportional to A and that still
+ // satisfies IsUnitLength().
+ for {
+ b := Point{a.Mul(2 - a.Norm() + 5*(randomFloat64()-0.5)*dblEpsilon)}
+ if !b.ApproxEqual(a) && b.IsUnit() {
+ return b
+ }
+ }
+ }
+ if choice < 0.5 {
+ // Return a point such that the distance squared to A will underflow.
+ return InterpolateAtDistance(1e-300, a, b)
+ }
+ // Otherwise return a point whose distance from A is near dblEpsilon such
+ // that the log of the pdf is uniformly distributed.
+ distance := dblEpsilon * 1e-5 * math.Pow(1e6, randomFloat64())
+ return InterpolateAtDistance(s1.Angle(distance), a, b)
+}
+
+// perturbedCornerOrMidpoint returns a Point from a line segment whose endpoints are
+// difficult to handle correctly. Given two adjacent cube vertices P and Q,
+// it returns either an edge midpoint, face midpoint, or corner vertex that is
+// in the plane of PQ and that has been perturbed slightly. It also sometimes
+// returns a random point from anywhere on the sphere.
+func perturbedCornerOrMidpoint(p, q Point) Point {
+ a := p.Mul(float64(randomUniformInt(3) - 1)).Add(q.Mul(float64(randomUniformInt(3) - 1)))
+ if oneIn(10) {
+ // This perturbation often has no effect except on coordinates that are
+ // zero, in which case the perturbed value is so small that operations on
+ // it often result in underflow.
+ a = a.Add(randomPoint().Mul(math.Pow(1e-300, randomFloat64())))
+ } else if oneIn(2) {
+ // For coordinates near 1 (say > 0.5), this perturbation yields values
+ // that are only a few representable values away from the initial value.
+ a = a.Add(randomPoint().Mul(4 * dblEpsilon))
+ } else {
+ // A perturbation whose magnitude is in the range [1e-25, 1e-10].
+ a = a.Add(randomPoint().Mul(1e-10 * math.Pow(1e-15, randomFloat64())))
+ }
+
+ if a.Norm2() < math.SmallestNonzeroFloat64 {
+ // If a.Norm2() is denormalized, Normalize() loses too much precision.
+ return perturbedCornerOrMidpoint(p, q)
+ }
+ return Point{a}
+}
+
+// TODO:
+// Most of the other s2 testing methods.
diff --git a/vendor/github.com/golang/geo/s2/s2_test_test.go b/vendor/github.com/golang/geo/s2/s2_test_test.go
new file mode 100644
index 0000000..30baf49
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/s2_test_test.go
@@ -0,0 +1,196 @@
+/*
+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 (
+ "reflect"
+ "testing"
+
+ "github.com/golang/geo/r1"
+ "github.com/golang/geo/r3"
+ "github.com/golang/geo/s1"
+)
+
+func TestKmToAngle(t *testing.T) {
+ const earthRadiusKm = 6371.01
+
+ tests := []struct {
+ have float64
+ want s1.Angle
+ }{
+ {0.0, 0.0},
+ {1.0, 0.00015696098420815537 * s1.Radian},
+ {earthRadiusKm, 1.0 * s1.Radian},
+ {-1.0, -0.00015696098420815537 * s1.Radian},
+ {-10000.0, -1.5696098420815536300 * s1.Radian},
+ {1e9, 156960.984208155363007 * s1.Radian},
+ }
+ for _, test := range tests {
+ if got := kmToAngle(test.have); !float64Eq(float64(got), float64(test.want)) {
+ t.Errorf("kmToAngle(%f) = %0.20f, want %0.20f", test.have, got, test.want)
+ }
+ }
+
+}
+
+func TestParsePoint(t *testing.T) {
+ tests := []struct {
+ have string
+ want Point
+ }{
+ {"0:0", Point{r3.Vector{1, 0, 0}}},
+ {"90:0", Point{r3.Vector{6.123233995736757e-17, 0, 1}}},
+ {"91:0", Point{r3.Vector{-0.017452406437283473, -0, 0.9998476951563913}}},
+ {"179.99:0", Point{r3.Vector{-0.9999999847691292, -0, 0.00017453292431344843}}},
+ {"180:0", Point{r3.Vector{-1, -0, 1.2246467991473515e-16}}},
+ {"181.0:0", Point{r3.Vector{-0.9998476951563913, -0, -0.017452406437283637}}},
+ {"-45:0", Point{r3.Vector{0.7071067811865476, 0, -0.7071067811865475}}},
+ {"0:0.01", Point{r3.Vector{0.9999999847691292, 0.00017453292431333684, 0}}},
+ {"0:30", Point{r3.Vector{0.8660254037844387, 0.49999999999999994, 0}}},
+ {"0:45", Point{r3.Vector{0.7071067811865476, 0.7071067811865475, 0}}},
+ {"0:90", Point{r3.Vector{6.123233995736757e-17, 1, 0}}},
+ {"30:30", Point{r3.Vector{0.7500000000000001, 0.4330127018922193, 0.49999999999999994}}},
+ {"-30:30", Point{r3.Vector{0.7500000000000001, 0.4330127018922193, -0.49999999999999994}}},
+ {"180:90", Point{r3.Vector{-6.123233995736757e-17, -1, 1.2246467991473515e-16}}},
+ {"37.4210:-122.0866, 37.4231:-122.0819", Point{r3.Vector{-0.4218751185559026, -0.6728760966593905, 0.6076669670863027}}},
+ }
+ for _, test := range tests {
+ if got := parsePoint(test.have); !got.ApproxEqual(test.want) {
+ t.Errorf("parsePoint(%s) = %v, want %v", test.have, got, test.want)
+ }
+ }
+}
+
+func TestParseRect(t *testing.T) {
+ tests := []struct {
+ have string
+ want Rect
+ }{
+ {"0:0", Rect{}},
+ {
+ "1:1",
+ Rect{
+ r1.Interval{float64(s1.Degree), float64(s1.Degree)},
+ s1.Interval{float64(s1.Degree), float64(s1.Degree)},
+ },
+ },
+ {
+ "1:1, 2:2, 3:3",
+ Rect{
+ r1.Interval{float64(s1.Degree), 3 * float64(s1.Degree)},
+ s1.Interval{float64(s1.Degree), 3 * float64(s1.Degree)},
+ },
+ },
+ {
+ "-90:-180, 90:180",
+ Rect{
+ r1.Interval{-90 * float64(s1.Degree), 90 * float64(s1.Degree)},
+ s1.Interval{180 * float64(s1.Degree), -180 * float64(s1.Degree)},
+ },
+ },
+ {
+ "-89.99:0, 89.99:179.99",
+ Rect{
+ r1.Interval{-89.99 * float64(s1.Degree), 89.99 * float64(s1.Degree)},
+ s1.Interval{0, 179.99 * float64(s1.Degree)},
+ },
+ },
+ {
+ "-89.99:-179.99, 89.99:179.99",
+ Rect{
+ r1.Interval{-89.99 * float64(s1.Degree), 89.99 * float64(s1.Degree)},
+ s1.Interval{179.99 * float64(s1.Degree), -179.99 * float64(s1.Degree)},
+ },
+ },
+ {
+ "37.4210:-122.0866, 37.4231:-122.0819",
+ Rect{
+ r1.Interval{float64(s1.Degree * 37.4210), float64(s1.Degree * 37.4231)},
+ s1.Interval{float64(s1.Degree * -122.0866), float64(s1.Degree * -122.0819)},
+ },
+ },
+ {
+ "-876.54:-654.43, 963.84:2468.35",
+ Rect{
+ r1.Interval{-876.54 * float64(s1.Degree), -876.54 * float64(s1.Degree)},
+ s1.Interval{-654.43 * float64(s1.Degree), -654.43 * float64(s1.Degree)},
+ },
+ },
+ }
+ for _, test := range tests {
+ if got := parseRect(test.have); got != test.want {
+ t.Errorf("parseRect(%s) = %v, want %v", test.have, got, test.want)
+ }
+ }
+}
+
+func TestParseLatLngs(t *testing.T) {
+ tests := []struct {
+ have string
+ want []LatLng
+ }{
+ {"0:0", []LatLng{{0, 0}}},
+ {
+ "37.4210:-122.0866, 37.4231:-122.0819",
+ []LatLng{
+ {s1.Degree * 37.4210, s1.Degree * -122.0866},
+ {s1.Degree * 37.4231, s1.Degree * -122.0819},
+ },
+ },
+ }
+ for _, test := range tests {
+ got := parseLatLngs(test.have)
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("parseLatLngs(%s) = %v, want %v", test.have, got, test.want)
+ }
+ }
+}
+
+func TestParsePoints(t *testing.T) {
+ tests := []struct {
+ have string
+ want []Point
+ }{
+ {"0:0", []Point{{r3.Vector{1, 0, 0}}}},
+ {" 0:0, ", []Point{{r3.Vector{1, 0, 0}}}},
+ {
+ "90:0,-90:0",
+ []Point{
+ {r3.Vector{6.123233995736757e-17, 0, 1}},
+ {r3.Vector{6.123233995736757e-17, 0, -1}},
+ },
+ },
+ {
+ "90:0, 0:90, -90:0, 0:-90",
+ []Point{
+ {r3.Vector{6.123233995736757e-17, 0, 1}},
+ {r3.Vector{6.123233995736757e-17, 1, 0}},
+ {r3.Vector{6.123233995736757e-17, 0, -1}},
+ {r3.Vector{6.123233995736757e-17, -1, 0}},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ got := parsePoints(test.have)
+ for i := range got { // assume we at least get the same number of points
+ if !got[i].ApproxEqual(test.want[i]) {
+ t.Errorf("parsePoints(%s): [%d]: got %v, want %v", test.have, i, got[i], test.want[i])
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/shapeindex.go b/vendor/github.com/golang/geo/s2/shapeindex.go
index 4bf15e5..c1c6f2e 100644
--- a/vendor/github.com/golang/geo/s2/shapeindex.go
+++ b/vendor/github.com/golang/geo/s2/shapeindex.go
@@ -20,16 +20,58 @@ import (
"github.com/golang/geo/r2"
)
-// Shape defines an interface for any s2 type that needs to be indexable.
+// dimension defines the types of geometry dimensions that a Shape supports.
+type dimension int
+
+const (
+ pointGeometry dimension = iota
+ polylineGeometry
+ polygonGeometry
+)
+
+// Shape defines an interface for any S2 type that needs to be indexable. A shape
+// is a collection of edges that optionally defines an interior. It can be used to
+// represent a set of points, a set of polylines, or a set of polygons.
type Shape interface {
// NumEdges returns the number of edges in this shape.
NumEdges() int
// Edge returns endpoints for the given edge index.
+ // Zero-length edges are allowed, and can be used to represent points.
Edge(i int) (a, b Point)
- // HasInterior returns true if this shape has an interior.
- // i.e. the Shape consists of one or more closed non-intersecting loops.
+ // numChains reports the number of contiguous edge chains in the shape.
+ // For example, a shape whose edges are [AB, BC, CD, AE, EF] would consist
+ // of two chains (AB,BC,CD and AE,EF). This method allows some algorithms
+ // to be optimized by skipping over edge chains that do not affect the output.
+ //
+ // Note that it is always acceptable to implement this method by returning
+ // NumEdges, i.e. every chain consists of a single edge.
+ numChains() int
+
+ // chainStart returns the id of the first edge in the i-th edge chain,
+ // and returns NumEdges when i == numChains. For example, if there are
+ // two chains AB,BC,CD and AE,EF, the chain starts would be [0, 3, 5].
+ //
+ // This requires the following:
+ // 0 <= i <= numChains()
+ // chainStart(0) == 0
+ // chainStart(i) < chainStart(i+1)
+ // chainStart(numChains()) == NumEdges()
+ chainStart(i int) int
+
+ // dimension returns the dimension of the geometry represented by this shape.
+ //
+ // Note that this method allows degenerate geometry of different dimensions
+ // to be distinguished, e.g. it allows a point to be distinguished from a
+ // polyline or polygon that has been simplified to a single point.
+ dimension() dimension
+
+ // HasInterior reports whether this shape has an interior. If so, it must be possible
+ // to assemble the edges into a collection of non-crossing loops. Edges may
+ // be returned in any order, and edges may be oriented arbitrarily with
+ // respect to the shape interior. (However, note that some Shape types
+ // may have stronger requirements.)
HasInterior() bool
// ContainsOrigin returns true if this shape contains s2.Origin.
@@ -39,7 +81,7 @@ type Shape interface {
// A minimal check for types that should satisfy the Shape interface.
var (
- _ Shape = Loop{}
+ _ Shape = &Loop{}
_ Shape = Polyline{}
)
@@ -147,38 +189,44 @@ type clippedEdge struct {
bound r2.Rect // Bounding box for the clipped portion
}
-// ShapeIndex indexes a set of Shapes, where a Shape is some collection of
-// edges. A shape can be as simple as a single edge, or as complex as a set of loops.
-// For Shapes that have interiors, the index makes it very fast to determine which
-// Shape(s) contain a given point or region.
+// ShapeIndex indexes a set of Shapes, where a Shape is some collection of edges
+// that optionally defines an interior. It can be used to represent a set of
+// points, a set of polylines, or a set of polygons. For Shapes that have
+// interiors, the index makes it very fast to determine which Shape(s) contain
+// a given point or region.
type ShapeIndex struct {
- // shapes maps all shapes to their index.
- shapes map[Shape]int32
+ // shapes is a map of shape ID to shape.
+ shapes map[int]Shape
maxEdgesPerCell int
// nextID tracks the next ID to hand out. IDs are not reused when shapes
// are removed from the index.
- nextID int32
+ nextID int
}
// NewShapeIndex creates a new ShapeIndex.
func NewShapeIndex() *ShapeIndex {
return &ShapeIndex{
maxEdgesPerCell: 10,
- shapes: make(map[Shape]int32),
+ shapes: make(map[int]Shape),
}
}
// Add adds the given shape to the index and assign an ID to it.
func (s *ShapeIndex) Add(shape Shape) {
- s.shapes[shape] = s.nextID
+ s.shapes[s.nextID] = shape
s.nextID++
}
// Remove removes the given shape from the index.
func (s *ShapeIndex) Remove(shape Shape) {
- delete(s.shapes, shape)
+ for k, v := range s.shapes {
+ if v == shape {
+ delete(s.shapes, k)
+ return
+ }
+ }
}
// Len reports the number of Shapes in this index.
@@ -188,14 +236,14 @@ func (s *ShapeIndex) Len() int {
// Reset clears the contents of the index and resets it to its original state.
func (s *ShapeIndex) Reset() {
- s.shapes = make(map[Shape]int32)
+ s.shapes = make(map[int]Shape)
s.nextID = 0
}
// NumEdges returns the number of edges in this index.
func (s *ShapeIndex) NumEdges() int {
numEdges := 0
- for shape := range s.shapes {
+ for _, shape := range s.shapes {
numEdges += shape.NumEdges()
}
return numEdges
diff --git a/vendor/github.com/golang/geo/s2/shapeindex_test.go b/vendor/github.com/golang/geo/s2/shapeindex_test.go
new file mode 100644
index 0000000..4f95cb1
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/shapeindex_test.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2016 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 (
+ "testing"
+)
+
+// testShape is a minimal implementation of the Shape interface for use in testing
+// until such time as there are other s2 types that implement it.
+type testShape struct {
+ a, b Point
+ edges int
+}
+
+func newTestShape() *testShape { return &testShape{} }
+func (s *testShape) NumEdges() int { return s.edges }
+func (s *testShape) Edge(id int) (a, b Point) { return s.a, s.b }
+func (s *testShape) dimension() dimension { return pointGeometry }
+func (s *testShape) numChains() int { return 0 }
+func (s *testShape) chainStart(i int) int { return 0 }
+func (s *testShape) HasInterior() bool { return false }
+func (s *testShape) ContainsOrigin() bool { return false }
+
+func TestShapeIndexBasics(t *testing.T) {
+ si := NewShapeIndex()
+ s := newTestShape()
+
+ if si.Len() != 0 {
+ t.Errorf("initial index should be empty after creation")
+ }
+ si.Add(s)
+
+ if si.Len() == 0 {
+ t.Errorf("index should not be empty after adding shape")
+ }
+
+ si.Reset()
+ if si.Len() != 0 {
+ t.Errorf("index should be empty after reset")
+ }
+}
+
+func TestShapeIndexCellBasics(t *testing.T) {
+ s := &shapeIndexCell{}
+
+ if len(s.shapes) != 0 {
+ t.Errorf("len(s.shapes) = %v, want %d", len(s.shapes), 0)
+ }
+
+ // create some clipped shapes to add.
+ c1 := &clippedShape{}
+ s.add(c1)
+
+ c2 := newClippedShape(7, 1)
+ s.add(c2)
+
+ c3 := &clippedShape{}
+ s.add(c3)
+
+ // look up the element at a given index
+ if got := s.shapes[1]; got != c2 {
+ t.Errorf("%v.shapes[%d] = %v, want %v", s, 1, got, c2)
+ }
+
+ // look for the clipped shape that is part of the given shape.
+ if got := s.findByID(7); got != c2 {
+ t.Errorf("%v.findByID(%v) = %v, want %v", s, 7, got, c2)
+ }
+}
diff --git a/vendor/github.com/golang/geo/s2/stuv_test.go b/vendor/github.com/golang/geo/s2/stuv_test.go
new file mode 100644
index 0000000..dfd2121
--- /dev/null
+++ b/vendor/github.com/golang/geo/s2/stuv_test.go
@@ -0,0 +1,320 @@
+/*
+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"
+)
+
+func TestSTUV(t *testing.T) {
+ if x := stToUV(uvToST(.125)); x != .125 {
+ t.Error("stToUV(uvToST(.125) == ", x)
+ }
+ if x := uvToST(stToUV(.125)); x != .125 {
+ t.Error("uvToST(stToUV(.125) == ", x)
+ }
+}
+
+func TestUVNorms(t *testing.T) {
+ step := 1 / 1024.0
+ for face := 0; face < 6; face++ {
+ for x := -1.0; x <= 1; x += step {
+ if !float64Eq(float64(faceUVToXYZ(face, x, -1).Cross(faceUVToXYZ(face, x, 1)).Angle(uNorm(face, x))), 0.0) {
+ t.Errorf("UNorm not orthogonal to the face(%d)", face)
+ }
+ if !float64Eq(float64(faceUVToXYZ(face, -1, x).Cross(faceUVToXYZ(face, 1, x)).Angle(vNorm(face, x))), 0.0) {
+ t.Errorf("VNorm not orthogonal to the face(%d)", face)
+ }
+ }
+ }
+}
+
+func TestFaceUVToXYZ(t *testing.T) {
+ // Check that each face appears exactly once.
+ var sum r3.Vector
+ for face := 0; face < 6; face++ {
+ center := faceUVToXYZ(face, 0, 0)
+ if !center.ApproxEqual(unitNorm(face).Vector) {
+ t.Errorf("faceUVToXYZ(%d, 0, 0) != unitNorm(%d), should be equal", face, face)
+ }
+ switch center.LargestComponent() {
+ case r3.XAxis:
+ if math.Abs(center.X) != 1 {
+ t.Errorf("%v.X = %v, want %v", center, math.Abs(center.X), 1)
+ }
+ case r3.YAxis:
+ if math.Abs(center.Y) != 1 {
+ t.Errorf("%v.Y = %v, want %v", center, math.Abs(center.Y), 1)
+ }
+ default:
+ if math.Abs(center.Z) != 1 {
+ t.Errorf("%v.Z = %v, want %v", center, math.Abs(center.Z), 1)
+ }
+ }
+ sum = sum.Add(center.Abs())
+
+ // Check that each face has a right-handed coordinate system.
+ if got := uAxis(face).Vector.Cross(vAxis(face).Vector).Dot(unitNorm(face).Vector); got != 1 {
+ t.Errorf("right-handed check failed. uAxis(%d).Cross(vAxis(%d)).Dot(unitNorm%v) = %d, want 1", face, face, face, got)
+ }
+
+ // Check that the Hilbert curves on each face combine to form a
+ // continuous curve over the entire cube.
+ // The Hilbert curve on each face starts at (-1,-1) and terminates
+ // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped).
+ var sign float64 = 1
+ if face&swapMask == 1 {
+ sign = -1
+ }
+ if faceUVToXYZ(face, sign, -sign) != faceUVToXYZ((face+1)%6, -1, -1) {
+ t.Errorf("faceUVToXYZ(%v, %v, %v) != faceUVToXYZ(%v, -1, -1)", face, sign, -sign, (face+1)%6)
+ }
+ }
+
+ // Adding up the absolute value all all the face normals should equal 2 on each axis.
+ if !sum.ApproxEqual(r3.Vector{2, 2, 2}) {
+ t.Errorf("sum of the abs of the 6 face norms should = %v, got %v", r3.Vector{2, 2, 2}, sum)
+ }
+}
+
+func TestFaceXYZToUV(t *testing.T) {
+ var (
+ point = Point{r3.Vector{1.1, 1.2, 1.3}}
+ pointNeg = Point{r3.Vector{-1.1, -1.2, -1.3}}
+ )
+
+ tests := []struct {
+ face int
+ point Point
+ u float64
+ v float64
+ ok bool
+ }{
+ {0, point, 1 + (1.0 / 11), 1 + (2.0 / 11), true},
+ {0, pointNeg, 0, 0, false},
+ {1, point, -11.0 / 12, 1 + (1.0 / 12), true},
+ {1, pointNeg, 0, 0, false},
+ {2, point, -11.0 / 13, -12.0 / 13, true},
+ {2, pointNeg, 0, 0, false},
+ {3, point, 0, 0, false},
+ {3, pointNeg, 1 + (2.0 / 11), 1 + (1.0 / 11), true},
+ {4, point, 0, 0, false},
+ {4, pointNeg, 1 + (1.0 / 12), -(11.0 / 12), true},
+ {5, point, 0, 0, false},
+ {5, pointNeg, -12.0 / 13, -11.0 / 13, true},
+ }
+
+ for _, test := range tests {
+ if u, v, ok := faceXYZToUV(test.face, test.point); !float64Eq(u, test.u) || !float64Eq(v, test.v) || ok != test.ok {
+ t.Errorf("faceXYZToUV(%d, %v) = %f, %f, %t, want %f, %f, %t", test.face, test.point, u, v, ok, test.u, test.v, test.ok)
+ }
+ }
+}
+
+func TestFaceXYZtoUVW(t *testing.T) {
+ var (
+ origin = Point{r3.Vector{0, 0, 0}}
+ posX = Point{r3.Vector{1, 0, 0}}
+ negX = Point{r3.Vector{-1, 0, 0}}
+ posY = Point{r3.Vector{0, 1, 0}}
+ negY = Point{r3.Vector{0, -1, 0}}
+ posZ = Point{r3.Vector{0, 0, 1}}
+ negZ = Point{r3.Vector{0, 0, -1}}
+ )
+
+ for face := 0; face < 6; face++ {
+ if got := faceXYZtoUVW(face, origin); got != origin {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, origin, got, origin)
+ }
+
+ if got := faceXYZtoUVW(face, uAxis(face)); got != posX {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, uAxis(face), got, posX)
+ }
+
+ if got := faceXYZtoUVW(face, Point{uAxis(face).Mul(-1)}); got != negX {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, uAxis(face).Mul(-1), got, negX)
+ }
+
+ if got := faceXYZtoUVW(face, vAxis(face)); got != posY {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, vAxis(face), got, posY)
+ }
+
+ if got := faceXYZtoUVW(face, Point{vAxis(face).Mul(-1)}); got != negY {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, vAxis(face).Mul(-1), got, negY)
+ }
+
+ if got := faceXYZtoUVW(face, unitNorm(face)); got != posZ {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, unitNorm(face), got, posZ)
+ }
+
+ if got := faceXYZtoUVW(face, Point{unitNorm(face).Mul(-1)}); got != negZ {
+ t.Errorf("faceXYZtoUVW(%d, %v) = %v, want %v", face, unitNorm(face).Mul(-1), got, negZ)
+ }
+ }
+}
+
+func TestUVWAxis(t *testing.T) {
+ for face := 0; face < 6; face++ {
+ // Check that the axes are consistent with faceUVtoXYZ.
+ if faceUVToXYZ(face, 1, 0).Sub(faceUVToXYZ(face, 0, 0)) != uAxis(face).Vector {
+ t.Errorf("face 1,0 - face 0,0 should equal uAxis")
+ }
+ if faceUVToXYZ(face, 0, 1).Sub(faceUVToXYZ(face, 0, 0)) != vAxis(face).Vector {
+ t.Errorf("faceUVToXYZ(%d, 0, 1).Sub(faceUVToXYZ(%d, 0, 0)) != vAxis(%d), should be equal.", face, face, face)
+ }
+ if faceUVToXYZ(face, 0, 0) != unitNorm(face).Vector {
+ t.Errorf("faceUVToXYZ(%d, 0, 0) != unitNorm(%d), should be equal", face, face)
+ }
+
+ // Check that every face coordinate frame is right-handed.
+ if got := uAxis(face).Vector.Cross(vAxis(face).Vector).Dot(unitNorm(face).Vector); got != 1 {
+ t.Errorf("right-handed check failed. got %d, want 1", got)
+ }
+
+ // Check that GetUVWAxis is consistent with GetUAxis, GetVAxis, GetNorm.
+ if uAxis(face) != uvwAxis(face, 0) {
+ t.Errorf("uAxis(%d) != uvwAxis(%d, 0), should be equal", face, face)
+ }
+ if vAxis(face) != uvwAxis(face, 1) {
+ t.Errorf("vAxis(%d) != uvwAxis(%d, 1), should be equal", face, face)
+ }
+ if unitNorm(face) != uvwAxis(face, 2) {
+ t.Errorf("unitNorm(%d) != uvwAxis(%d, 2), should be equal", face, face)
+ }
+ }
+}
+
+func TestSiTiSTRoundtrip(t *testing.T) {
+ // test int -> float -> int direction.
+ for i := 0; i < 1000; i++ {
+ si := uint64(randomUniformInt(maxSiTi))
+ if got := stToSiTi(siTiToST(si)); got != si {
+ t.Errorf("stToSiTi(siTiToST(%v)) = %v, want %v", si, got, si)
+ }
+ }
+ // test float -> int -> float direction.
+ for i := 0; i < 1000; i++ {
+ st := randomUniformFloat64(0, 1.0)
+ // this uses near not exact because there is some loss in precision
+ // when scaling down to the nearest 1/maxLevel and back.
+ if got := siTiToST(stToSiTi(st)); !float64Near(got, st, 1e-8) {
+ t.Errorf("siTiToST(stToSiTi(%v)) = %v, want %v", st, got, st)
+ }
+ }
+}
+
+func TestUVWFace(t *testing.T) {
+ // Check that uvwFace is consistent with uvwAxis.
+ for f := 0; f < 6; f++ {
+ for axis := 0; axis < 3; axis++ {
+ if got, want := face(uvwAxis(f, axis).Mul(-1)), uvwFace(f, axis, 0); got != want {
+ t.Errorf("face(%v) in positive direction = %v, want %v", uvwAxis(f, axis).Mul(-1), got, want)
+ }
+ if got, want := face(uvwAxis(f, axis).Vector), uvwFace(f, axis, 1); got != want {
+ t.Errorf("face(%v) in negative direction = %v, want %v", uvwAxis(f, axis), got, want)
+ }
+ }
+ }
+}
+
+func TestXYZToFaceSiTi(t *testing.T) {
+ for level := 0; level < maxLevel; level++ {
+ for i := 0; i < 1000; i++ {
+ ci := randomCellIDForLevel(level)
+ f, si, ti, gotLevel := xyzToFaceSiTi(ci.Point())
+ if gotLevel != level {
+ t.Errorf("level of CellID %v = %v, want %v", ci, gotLevel, level)
+ }
+ gotID := cellIDFromFaceIJ(f, int(si/2), int(ti/2)).Parent(level)
+ if gotID != ci {
+ t.Errorf("CellID = %b, want %b", gotID, ci)
+ }
+
+ // Test a point near the cell center but not equal to it.
+ pMoved := ci.Point().Add(r3.Vector{1e-13, 1e-13, 1e-13})
+ fMoved, siMoved, tiMoved, gotLevel := xyzToFaceSiTi(Point{pMoved})
+
+ if gotLevel != -1 {
+ t.Errorf("level of %v = %v, want %v", pMoved, gotLevel, -1)
+ }
+
+ if f != fMoved {
+ t.Errorf("face of %v = %v, want %v", pMoved, fMoved, f)
+ }
+
+ if si != siMoved {
+ t.Errorf("si of %v = %v, want %v", pMoved, siMoved, si)
+ }
+
+ if ti != tiMoved {
+ t.Errorf("ti of %v = %v, want %v", pMoved, tiMoved, ti)
+ }
+
+ // Finally, test some random (si,ti) values that may be at different
+ // levels, or not at a valid level at all (for example, si == 0).
+ faceRandom := randomUniformInt(numFaces)
+ var siRandom, tiRandom uint64
+ mask := -1 << uint64(maxLevel-level)
+ for siRandom > maxSiTi || tiRandom > maxSiTi {
+ siRandom = uint64(randomUint32() & uint32(mask))
+ tiRandom = uint64(randomUint32() & uint32(mask))
+ }
+
+ pRandom := faceSiTiToXYZ(faceRandom, siRandom, tiRandom)
+ f, si, ti, gotLevel = xyzToFaceSiTi(pRandom)
+
+ // The chosen point is on the edge of a top-level face cell.
+ if f != faceRandom {
+ if gotLevel != -1 {
+ t.Errorf("level of random CellID = %v, want %v", gotLevel, -1)
+ }
+ if got := si == 0 || si == maxSiTi || ti == 0 || ti == maxSiTi; !got {
+ t.Errorf("%v face %d, si = %v, want 0 || %v, ti = %v, want 0 || %v", f, faceRandom, si, maxSiTi, ti, maxSiTi)
+ }
+ continue
+ }
+
+ if siRandom != si {
+ t.Errorf("xyzToFaceSiTi(%v).si = %v, want %v", pRandom, siRandom, si)
+ }
+ if tiRandom != ti {
+ t.Errorf("xyzToFaceSiTi(%v).ti = %v, want %v", pRandom, tiRandom, ti)
+ }
+ if gotLevel >= 0 {
+ if got := cellIDFromFaceIJ(f, int(si/2), int(ti/2)).Parent(gotLevel).Point(); pRandom.ApproxEqual(got) {
+ t.Errorf("cellIDFromFaceIJ(%d, %d, %d, %d) = %v, want %v", f, int(si/2), int(ti/2), gotLevel, got, pRandom)
+ }
+ }
+ }
+ }
+}
+
+func TestXYZFaceSiTiRoundtrip(t *testing.T) {
+ for level := 0; level < maxLevel; level++ {
+ for i := 0; i < 1000; i++ {
+ ci := randomCellIDForLevel(level)
+ f, si, ti, _ := xyzToFaceSiTi(ci.Point())
+ op := faceSiTiToXYZ(f, si, ti)
+ if !ci.Point().ApproxEqual(op) {
+ t.Errorf("faceSiTiToXYZ(xyzToFaceSiTi(%v)) = %v, want %v", ci.Point(), op, ci.Point())
+ }
+ }
+ }
+}