Example #1
0
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")
	}
}
Example #2
0
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)
		}
	}
}
Example #3
0
// 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))))
}
Example #4
0
// 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)))
}
Example #5
0
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(),
		),
	}
}
Example #6
0
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}
}
Example #7
0
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)
}
Example #8
0
// 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
}
Example #9
0
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)
}
Example #10
0
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)
		}
	}
}
Example #11
0
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
}
Example #12
0
func LatLngFromRadians(lat_radians, lng_radians float64) LatLng {
	return LatLng{s1.Angle(lat_radians), s1.Angle(lng_radians)}
}
Example #13
0
// 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}
}
Example #14
0
			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
Example #15
0
// 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}
}
Example #16
0
// 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}
}
Example #17
0
// 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}
}
Example #18
0
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))
	}
}
Example #19
0
// 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
}
Example #20
0
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
}
Example #21
0
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
}
Example #22
0
// 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}
}
Example #23
0
func longitude(p Point) s1.Angle {
	return s1.Angle(math.Atan2(p.Y, p.X)) * s1.Radian
}
Example #24
0
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},