func TestLoopBounds(t *testing.T) { if !(candyCane.RectBound().Lng.IsFull()) { t.Fatal("ttttt") } if !(s1.Angle(candyCane.RectBound().Lat.Lo).Degrees() < -20) { t.Fatal("") } if !(s1.Angle(candyCane.RectBound().Lat.Hi).Degrees() > 10) { t.Fatal("") } if !(smallNeCw.RectBound().IsFull()) { t.Fatal("") } if arctic80.RectBound() != RectFromLatLngLoHi(LatLngFromDegrees(80, -180), LatLngFromDegrees(90, 180)) { t.Fatal("") } if antarctic80.RectBound() != RectFromLatLngLoHi(LatLngFromDegrees(-90, -180), LatLngFromDegrees(-80, 180)) { t.Fatal("") } arctic80.Invert() // The highest latitude of each edge is attained at its midpoint. mid := Point{arctic80.Vertex(0).Add(arctic80.Vertex(1).Vector).Mul(0.5)} if math.Abs(s1.Angle(arctic80.RectBound().Lat.Hi).Radians()-LatLngFromPoint(mid).Lat.Radians()) > EPSILON { t.Fatal("") } arctic80.Invert() if !(southHemi.RectBound().Lng.IsFull()) { t.Fatal("") } if !(southHemi.RectBound().Lat == r1.IntervalFromPointPair(-math.Pi/2, 0)) { t.Fatal("") } }
func TestCapContains(t *testing.T) { tests := []struct { c1, c2 Cap want bool }{ {empty, empty, true}, {full, empty, true}, {full, full, true}, {empty, xAxis, false}, {full, xAxis, true}, {xAxis, full, false}, {xAxis, xAxis, true}, {xAxis, empty, 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) } } }
/** * A slightly more efficient version of getDistance() where the cross product * of the two endpoints has been precomputed. The cross product does not need * to be normalized, but should be computed using S2.robustCrossProd() for the * most accurate results. */ func getDistanceWithCross(x, a, b, aCrossB Point) s1.Angle { if !x.IsUnit() || !a.IsUnit() || !b.IsUnit() { panic("x, a and b need to be unit length") } // There are three cases. If X is located in the spherical wedge defined by // A, B, and the axis A x B, then the closest point is on the segment AB. // Otherwise the closest point is either A or B; the dividing line between // these two cases is the great circle passing through (A x B) and the // midpoint of AB. if simpleCCW(aCrossB, a, x) && simpleCCW(x, b, aCrossB) { // The closest point to X lies on the segment AB. We compute the distance // to the corresponding great circle. The result is accurate for small // distances but not necessarily for large distances (approaching Pi/2). sinDist := math.Abs(x.Dot(aCrossB.Vector)) / aCrossB.Norm() return s1.Angle(math.Asin(math.Min(1.0, sinDist))) } // Otherwise, the closest point is either A or B. The cheapest method is // just to compute the minimum of the two linear (as opposed to spherical) // distances and convert the result to an angle. Again, this method is // accurate for small but not large distances (approaching Pi). linearDist2 := math.Min(x.Sub(a.Vector).Norm2(), x.Sub(b.Vector).Norm2()) return s1.Angle(2 * math.Asin(math.Min(1.0, 0.5*math.Sqrt(linearDist2)))) }
func TestCapExpanded(t *testing.T) { cap50 := CapFromCenterAngle(xAxisPt, 50.0*s1.Degree) cap51 := CapFromCenterAngle(xAxisPt, 51.0*s1.Degree) if !empty.Expanded(s1.Angle(fullHeight)).IsEmpty() { t.Error("Expanding empty cap should return an empty cap") } if !full.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") } }
// Radius returns the cap's radius. 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))) }
func TestCapAddPoint(t *testing.T) { 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, PointFromCoords(-1, 0, 0), full}, {yAxis, PointFromCoords(0, -1, 0), full}, // Cap plus orthogonal axis equals half cap. {xAxis, PointFromCoords(0, 0, 1), CapFromCenterAngle(xAxisPt, s1.Angle(math.Pi/2.0))}, {xAxis, PointFromCoords(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{PointFromCoords(1, 0, 1).Normalize()}, s1.Angle(120.0)*s1.Degree), }, { hemi, PointFromCoords(0, -1, -1), CapFromCenterAngle(Point{PointFromCoords(1, 0, 1).Normalize()}, s1.Angle(120.0)*s1.Degree), }, { // This angle between this point and the center is acos(-sqrt(2/3)) hemi, PointFromCoords(-1, -1, -1), CapFromCenterAngle(Point{PointFromCoords(1, 0, 1).Normalize()}, s1.Angle(2.5261129449194)), }, {hemi, PointFromCoords(0, 1, 1), hemi}, {hemi, PointFromCoords(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 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 TestCapAddCap(t *testing.T) { tests := []struct { have Cap other Cap want Cap }{ // Identity cases. {empty, empty, empty}, {full, full, full}, // Anything plus empty equals itself. {full, empty, full}, {empty, full, full}, {xAxis, empty, xAxis}, {empty, xAxis, xAxis}, {yAxis, empty, yAxis}, {empty, yAxis, yAxis}, // Two halves make a whole. {xAxis, xComp, full}, // 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) } } }
// Distance returns the angle between two LatLngs. func (ll LatLng) Distance(ll2 LatLng) s1.Angle { // Haversine formula, as used in C++ S2LatLng::GetDistance. lat1, lat2 := ll.Lat.Radians(), ll2.Lat.Radians() lng1, lng2 := ll.Lng.Radians(), ll2.Lng.Radians() dlat := math.Sin(0.5 * (lat2 - lat1)) dlng := math.Sin(0.5 * (lng2 - lng1)) x := dlat*dlat + dlng*dlng*math.Cos(lat1)*math.Cos(lat2) return s1.Angle(2 * math.Atan2(math.Sqrt(x), math.Sqrt(math.Max(0, 1-x)))) }
/** * Returns the shortest distance from a point P to this loop, given as the * angle formed between P, the origin and the nearest point on the loop to P. * This angle in radians is equivalent to the arclength along the unit sphere. */ func (l *Loop) GetDistance(p Point) s1.Angle { normalized := Point{p.Normalize()} // The furthest point from p on the sphere is its antipode, which is an // angle of PI radians. This is an upper bound on the angle. minDistance := math.Pi for i := 0; i < l.NumVertices(); i++ { minDistance = math.Min(minDistance, getDistance(normalized, l.Vertex(i), l.Vertex(i+1)).Radians()) } return s1.Angle(minDistance) }
func TestRadiusToHeight(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.02185239926619}, {30.0 * s1.Degree, 0.13397459621556}, {45.0 * s1.Degree, 0.29289321881345}, {90.0 * s1.Degree, 1.0}, {179.99 * s1.Degree, 1.99999998476912}, {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.41614683654714}, {3.0 * s1.Radian, 1.98999249660044}, {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 TestCapGetRectBounds(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(PointFromCoords(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.") } }
var ( empty = EmptyCap() full = FullCap() defaultCap = EmptyCap() xAxisPt = PointFromCoords(1, 0, 0) yAxisPt = PointFromCoords(0, 1, 0) xAxis = CapFromPoint(xAxisPt) yAxis = CapFromPoint(yAxisPt) xComp = xAxis.Complement() hemi = CapFromCenterHeight(Point{PointFromCoords(1, 0, 1).Normalize()}, 1) concave = CapFromCenterAngle(PointFromLatLng(LatLngFromDegrees(80, 10)), s1.Angle(150.0)*s1.Degree) tiny = CapFromCenterAngle(Point{PointFromCoords(1, 2, 3).Normalize()}, s1.Angle(tinyRad)) ) func TestCapBasicEmptyFullValid(t *testing.T) { tests := []struct { got Cap empty, full, valid bool }{ {Cap{}, false, false, false}, {empty, true, false, true}, {empty.Complement(), false, true, true}, {full, false, true, true}, {full.Complement(), true, false, true},
func longitude(p Point) s1.Angle { return s1.Angle(math.Atan2(p.Y, p.X)) }
func latitude(p Point) s1.Angle { return s1.Angle(math.Atan2(p.Z, math.Sqrt(p.X*p.X+p.Y*p.Y))) }
// LatLngFromDegrees returns a LatLng for the coordinates given in degrees. func LatLngFromDegrees(lat, lng float64) LatLng { return LatLng{s1.Angle(lat) * s1.Degree, s1.Angle(lng) * s1.Degree} }
// 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) }
// Angle returns the angle between v and ov. func (v Vector) Angle(ov Vector) s1.Angle { return s1.Angle(math.Atan2(v.Cross(ov).Norm(), v.Dot(ov))) * s1.Radian }