Beispiel #1
0
// intersectsLatEdge reports if the edge AB intersects the given edge of constant
// latitude. Requires the points to have unit length.
func intersectsLatEdge(a, b Point, lat s1.Angle, lng s1.Interval) bool {
	// Unfortunately, lines of constant latitude are curves on
	// 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)
	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))
	x := y.Cross(z.Vector)

	// Compute the angle "theta" from the x-axis (in the x-y plane defined
	// above) where the great circle intersects the given line of latitude.
	sinLat := math.Sin(float64(lat))
	if math.Abs(sinLat) >= x.Z {
		// The great circle does not reach the given latitude.
		return false
	}

	cosTheta := sinLat / x.Z
	sinTheta := math.Sqrt(1 - cosTheta*cosTheta)
	theta := math.Atan2(sinTheta, cosTheta)

	// The candidate intersection points are located +/- theta in the x-y
	// plane. For an intersection to be valid, we need to check that the
	// intersection point is contained in the interior of the edge AB and
	// also that it is contained within the given longitude interval "lng".

	// Compute the range of theta values spanned by the edge AB.
	abTheta := s1.IntervalFromEndpoints(
		math.Atan2(a.Dot(y.Vector), a.Dot(x)),
		math.Atan2(b.Dot(y.Vector), b.Dot(x)))

	if abTheta.Contains(theta) {
		// Check if the intersection point is also in the given lng interval.
		isect := x.Mul(cosTheta).Add(y.Mul(sinTheta))
		if lng.Contains(math.Atan2(isect.Y, isect.X)) {
			return true
		}
	}

	if abTheta.Contains(-theta) {
		// Check if the other intersection point is also in the given lng interval.
		isect := x.Mul(cosTheta).Sub(y.Mul(sinTheta))
		if lng.Contains(math.Atan2(isect.Y, isect.X)) {
			return true
		}
	}
	return false
}
Beispiel #2
0
// 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")
	}
}