// 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 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) }
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 (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 }
// 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 }
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
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)) } }
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 }
// 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} }