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) } } }
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") } }
// This is named GetDistance() in the C++ API. func (x Point) DistanceToEdgeWithNormal(a, b, a_cross_b Point) s1.Angle { // 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 CCW(a_cross_b, a, x) && CCW(x, b, a_cross_b) { // 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). // // TODO: sanity check a != b sin_dist := math.Abs(x.Dot(a_cross_b.Vector)) / a_cross_b.Norm() return s1.Angle(math.Asin(math.Min(1.0, sin_dist))) } // 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). xa := x.Sub(a.Vector).Norm2() xb := x.Sub(b.Vector).Norm2() linear_dist2 := math.Min(xa, xb) return s1.Angle(2 * math.Asin(math.Min(1.0, 0.5*math.Sqrt(linear_dist2)))) }
// 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 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 randomEdgeCrossingCap(maxLengthMeters float64, s2cap Cap) Edge { center := samplePoint(s2cap) edgeCap := CapFromCenterAngle(center, s1.Angle(maxLengthMeters/kEarthRadiusMeters/2)) p0 := samplePoint(edgeCap) p1 := samplePoint(edgeCap) return Edge{p0, p1} }
func Perturb(x Point, maxPerturb float64) Point { // Perturb "x" randomly within the radius of maxPerturb. if maxPerturb == 0 { return x } s2cap := CapFromCenterAngle(Point{x.Normalize()}, s1.Angle(maxPerturb)) return samplePoint(s2cap) }
// 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)))) * s1.Radian }
func EdgeInterpolate(t float64, a, b Point) Point { if t == 0 { return a } if t == 1 { return b } ab := a.Angle(b.Vector) return EdgeInterpolateAtDistance(s1.Angle(t)*ab, a, b, ab) }
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 (r Rect) CapBound() Cap { // We consider two possible bounding caps, one whose axis passes // through the center of the lat-lng rectangle and one whose axis // is the north or south pole. We return the smaller of the two caps. if r.IsEmpty() { return EmptyCap() } var poleZ, poleAngle float64 // var poleAngle s1.Angle if r.Lat.Lo+r.Lat.Hi < 0 { // South pole axis yields smaller cap. poleZ = -1 poleAngle = math.Pi/2 + r.Lat.Hi } else { poleZ = 1 poleAngle = math.Pi/2 - r.Lat.Lo } poleCap := CapFromCenterAngle(PointFromCoords(0, 0, poleZ), s1.Angle(poleAngle)) // For bounding rectangles that span 180 degrees or less in longitude, // the maximum cap size is achieved at one of the rectangle vertices. // For rectangles that are larger than 180 degrees, we punt and always // return a bounding cap centered at one of the two poles. lngSpan := r.Lng.Hi - r.Lng.Lo if math.Remainder(lngSpan, 2*math.Pi) >= 0 { if lngSpan < 2*math.Pi { midCap := CapFromCenterAngle(PointFromLatLng(r.Center()), s1.Angle(0)) for k := 0; k < 4; k++ { midCap.AddPoint(PointFromLatLng(r.Vertex(k))) } if midCap.height < poleCap.height { return midCap } } } return poleCap }
// Size returns the size of the Rect. func (r Rect) Size() LatLng { return LatLng{s1.Angle(r.Lat.Length()) * s1.Radian, s1.Angle(r.Lng.Length()) * s1.Radian} }
// Center returns the center of the rectangle. func (r Rect) Center() LatLng { return LatLng{s1.Angle(r.Lat.Center()) * s1.Radian, s1.Angle(r.Lng.Center()) * s1.Radian} }
// Hi returns the other corner of the rectangle. func (r Rect) Hi() LatLng { return LatLng{s1.Angle(r.Lat.Hi) * s1.Radian, s1.Angle(r.Lng.Hi) * s1.Radian} }
// Lo returns one corner of the rectangle. func (r Rect) Lo() LatLng { return LatLng{s1.Angle(r.Lat.Lo) * s1.Radian, s1.Angle(r.Lng.Lo) * s1.Radian} }
func GenerateRandomEarthEdges(edgeLengthMetersMax, capSpanMeters float64, numEdges int, edges *[]Edge) { s2cap := CapFromCenterAngle(randomPoint(), s1.Angle(capSpanMeters/kEarthRadiusMeters)) for i := 0; i < numEdges; i++ { *edges = append(*edges, randomEdgeCrossingCap(edgeLengthMetersMax, s2cap)) } }
// 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} }
func LatLngFromRadians(lat_radians, lng_radians float64) LatLng { return LatLng{s1.Angle(lat_radians), s1.Angle(lng_radians)} }
func latitude(p Point) s1.Angle { return s1.Angle(math.Atan2(p.Z, math.Sqrt(p.X*p.X+p.Y*p.Y))) * s1.Radian }
func longitude(p Point) s1.Angle { return s1.Angle(math.Atan2(p.Y, p.X)) * s1.Radian }
func runTestCase(t *testing.T, test TestCase) bool { for iter := 0; iter < 250; iter++ { options := PolygonBuilderOptions{ edge_splice_fraction: .866, validate: false, vertex_merge_radius: s1.Angle(0), } options.undirected_edges = evalTristate(test.undirectedEdges) options.xor_edges = evalTristate(test.xorEdges) minMerge := (s1.Angle(test.minMerge) * s1.Degree).Radians() maxMerge := (s1.Angle(test.maxMerge) * s1.Degree).Radians() minSin := math.Sin((s1.Angle(test.minVertexAngle) * s1.Degree).Radians()) // Half of the time we allow edges to be split into smaller // pieces (up to 5 levels, i.e. up to 32 pieces). maxSplits := max(0, rand.Intn(10)-4) if !test.canSplit { maxSplits = 0 } // We choose randomly among two different values for the edge // fraction, just to exercise that code. edgeFraction := options.edge_splice_fraction var vertexMerge, maxPerturb float64 if minSin < edgeFraction && oneIn(2) { edgeFraction = minSin } if maxSplits == 0 && oneIn(2) { // Turn off edge splicing completely. edgeFraction = 0 vertexMerge = minMerge + smallFraction()*(maxMerge-minMerge) maxPerturb = 0.5 * math.Min(vertexMerge-minMerge, maxMerge-vertexMerge) } else { // Splice edges. These bounds also assume that edges // may be split. // // If edges are actually split, need to bump up the // minimum merge radius to ensure that split edges // in opposite directions are unified. Otherwise // there will be tiny degenerate loops created. if maxSplits > 0 { minMerge += 1e-15 } minMerge /= edgeFraction maxMerge *= minSin if maxMerge < minMerge { t.Errorf("%v < %v", maxMerge, minMerge) } vertexMerge = minMerge + smallFraction()*(maxMerge-minMerge) maxPerturb = 0.5 * math.Min(edgeFraction*(vertexMerge-minMerge), maxMerge-vertexMerge) } // We can perturb by any amount up to the maximum, but choosing // a lower maximum decreases the error bounds when checking the // output. maxPerturb *= smallFraction() // This is the minimum length of a split edge to prevent // unexpected merging and/or splicing. minEdge := minMerge + (vertexMerge+2*maxPerturb)/minSin options.vertex_merge_radius = s1.Angle(vertexMerge) options.edge_splice_fraction = edgeFraction options.validate = true builder := NewPolygonBuilder(options) // On each iteration we randomly rotate the test case around // the sphere. This causes the PolygonBuilder to choose // different first edges when trying to build loops. x, y, z := randomFrame() m := r3.MatrixFromCols(x.Vector, y.Vector, z.Vector) for _, chain := range test.chainsIn { addChain(chain, m, maxSplits, maxPerturb, minEdge, &builder) } var loops []*Loop var unusedEdges []Edge if test.xorEdges < 0 { builder.AssembleLoops(&loops, &unusedEdges) } else { var polygon Polygon builder.AssemblePolygon(&polygon, &unusedEdges) polygon.Release(&loops) } expected := []*Loop{} for _, str := range test.loopsOut { if str != "" { vertices := getVertices(str, m) expected = append(expected, NewLoopFromPath(vertices)) } } // We assume that the vertex locations in the expected output // polygon are separated from the corresponding vertex // locations in the input edges by at most half of the // minimum merge radius. Essentially this means that the // expected output vertices should be near the centroid of the // various input vertices. // // If any edges were split, we need to allow a bit more error // due to inaccuracies in the interpolated positions. // Similarly, if any vertices were perturbed, we need to bump // up the error to allow for numerical errors in the actual // perturbation. maxError := 0.5*minMerge + maxPerturb if maxSplits > 0 || maxPerturb > 0 { maxError += 1e-15 } ok0 := findMissingLoops(loops, expected, m, maxSplits, maxError, "Actual") ok1 := findMissingLoops(expected, loops, m, maxSplits, maxError, "Expected") ok2 := unexpectedUnusedEdgeCount(len(unusedEdges), test.numUnusedEdges, maxSplits) if ok0 || ok1 || ok2 { // We found a problem. dumpUnusedEdges(unusedEdges, m, test.numUnusedEdges) fmt.Printf(`During iteration %d: undirected: %v xor: %v maxSplits: %d maxPerturb: %.6g vertexMergeRadius: %.6g edgeSpliceFraction: %.6g minEdge: %.6g maxError: %.6g `, iter, options.undirected_edges, options.xor_edges, maxSplits, s1.Angle(maxPerturb).Degrees(), options.vertex_merge_radius.Degrees(), options.edge_splice_fraction, s1.Angle(minEdge).Degrees(), s1.Angle(maxError).Degrees()) return false } } return true }
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},
lastParent = parent } count := 0 for j := 0; j < child.NumVertices(); j++ { if _, ok := vertices[*child.vertex(j)]; ok { count++ } } if count > 1 { return false } } return true } var intersectionTolerance = s1.Angle(1.5e-15) func (p *Polygon) InitToIntersection(a, b *Polygon) { p.InitToIntersectionSloppy(a, b, intersectionTolerance) } func (p *Polygon) InitToIntersectionSloppy(a, b *Polygon, vertexMergeRadius s1.Angle) { if !a.bound.Intersects(b.bound) { return } // We want the boundary of A clipped to the interior of B, // plus the boundary of B clipped to the interior of A, // plus one copy of any directed edges that are in both boundaries. options := DIRECTED_XOR() options.vertex_merge_radius = vertexMergeRadius
// 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 }