Example #1
0
func (l *Loop) InitBound() {
	// The bounding rectangle of a loop is not necessarily the same as the
	// bounding rectangle of its vertices. First, the loop may wrap entirely
	// around the sphere (e.g. a loop that defines two revolutions of a
	// candy-cane stripe). Second, the loop may include one or both poles.
	// Note that a small clockwise loop near the equator contains both
	// poles.

	bounder := NewRectBounder()
	for i := 0; i <= len(l.vertices); i++ {
		bounder.AddPoint(l.vertex(i))
	}

	b := bounder.Bound()
	// Note that we need to initialize l.bound with a temporary value since
	// Contains() does a bounding rectangle check before doing anything
	// else.
	l.bound = FullRect()
	if l.Contains(PointFromCoords(0, 0, 1)) {
		b = Rect{
			Lat: r1.Interval{b.Lat.Lo, math.Pi / 2},
			Lng: s1.FullInterval(),
		}
	}
	// If a loop contains the south pole, then either it wraps entirely
	// around the sphere (full longitude range), or it also contains the
	// north pole in which case b.Lng.IsFull() due to the test above.
	// Either way, we only need to do the south pole containment test if
	// b.Lng.IsFull()
	if b.Lng.IsFull() && l.Contains(PointFromCoords(0, 0, -1)) {
		b.Lat.Lo = -math.Pi / 2
	}
	l.bound = b
}
Example #2
0
func (r *RectBounder) AddPoint(b *Point) {
	ll := LatLngFromPoint(*b)
	if r.bound.IsEmpty() {
		r.bound = r.bound.AddPoint(ll)
	} else {
		// We can't just call bound.AddPoint(ll) here, since we need to
		// ensure that all the longitudes between "a" and "b" are
		// included.
		r.bound = r.bound.Union(RectFromPointPair(r.latlng, ll))

		// Check whether the min/max latitude occurs in the edge
		// interior. We find the normal to the plane containing AB,
		// and then a vector "dir" in this plane that also passes
		// through the equator. We use RobustCrossProd to ensure that
		// the edge normal is accurate even when the two points are
		// very close together.
		a_cross_b := r.a.PointCross(*b)
		dir := a_cross_b.Cross(PointFromCoords(0, 0, 1).Vector)
		da := dir.Dot(r.a.Vector)
		db := dir.Dot(b.Vector)
		if da*db < 0 {
			// min/max latitude occurs in the edge interior.
			abslat := math.Acos(math.Abs(a_cross_b.Z / a_cross_b.Norm()))
			if da < 0 {
				// It's possible that abslat < r.Lat.Lo due to
				// numerical errors.
				r.bound.Lat.Hi = math.Max(abslat, r.bound.Lat.Hi)
			} else {
				r.bound.Lat.Lo = math.Min(-abslat, r.bound.Lat.Lo)
			}

			// If the edge comes very close to the north or south
			// pole then we may not be certain which side of the
			// pole it is on. We handle this by expanding the
			// longitude bounds if the maximum latitude is
			// approximately Pi/2.
			if abslat >= math.Pi/2-1e-15 {
				r.bound.Lng = s1.FullInterval()
			}
		}
	}
	r.a = b
	r.latlng = ll
}
Example #3
0
	"fmt"
	"math"

	"github.com/hailocab/gos2/r1"
	"github.com/hailocab/gos2/s1"
)

// Rect represents a closed latitude-longitude rectangle.
type Rect struct {
	Lat r1.Interval
	Lng s1.Interval
}

var (
	validRectLatRange = r1.Interval{-math.Pi / 2, math.Pi / 2}
	validRectLngRange = s1.FullInterval()
)

// FullRect returns the full rectangle.
func FullRect() Rect { return Rect{validRectLatRange, validRectLngRange} }

func EmptyRect() Rect { return Rect{r1.EmptyInterval(), s1.EmptyInterval()} }

// RectFromLatLng constructs a rectangle containing a single point p.
func RectFromLatLng(p LatLng) Rect {
	return Rect{
		Lat: r1.Interval{p.Lat.Radians(), p.Lat.Radians()},
		Lng: s1.Interval{p.Lng.Radians(), p.Lng.Radians()},
	}
}
Example #4
0
func (c Cell) RectBound() Rect {
	if c.level > 0 {
		// Except for cells at level 0, the latitude and longitude
		// extremes are attained at the vertices. Furthermore, the
		// latitude range is determined by one pair of diagonally
		// opposite vertices and the longitude range is determined by
		// the other pair.
		//
		// We first determine which corner (i,j) of the cell has the
		// largest absolute latitude. To maximize latitude, we want to
		// find the point in the cell that has the largest absolute
		// z-coordinate and the smallest absolute x- and y-coordinates.
		// To do this we look at each coordinate (u and v), and
		// determine whether we want to minimize or maximize that
		// coordinate based on the axis direction and the cell's (u,v)
		// quadrant.
		u := c.uv.X.Lo + c.uv.X.Hi
		v := c.uv.Y.Lo + c.uv.Y.Hi
		i, j := ijFromFaceZ(c.face, u, v)

		// We grow the bounds slightly to make sure that the bounding
		// rectangle also contains the normalized versions of the
		// vertices. Note that the maximum result magnitude is Pi, with
		// a floating-point exponent of 1. Therefore adding or
		// subtracting 2**-51 will always change the result.
		lat := r1.IntervalFromPointPair(c.Latitude(i, j), c.Latitude(1-i, 1-j))
		lat = lat.Expanded(maxError).Intersection(validRectLatRange)
		if lat.Lo == -M_PI_2 || lat.Hi == M_PI_2 {
			return Rect{lat, s1.FullInterval()}
		}

		lng := s1.IntervalFromPointPair(c.Longitude(i, 1-j), c.Longitude(1-i, j))
		return Rect{lat, lng.Expanded(maxError)}
	}

	// The 4 cells around the equator extend to +/-45 degrees latitude at
	// the midpoints of their top and bottom edges. The two cells covering
	// the poles extend down to +/-35.26 degrees at their vertices.
	//
	// The face centers are the +X, +Y, +Z, -X, -Y, -Z axes in that order.
	switch c.face {
	case 0:
		return Rect{
			r1.Interval{-M_PI_4, M_PI_4},
			s1.Interval{-M_PI_4, M_PI_4},
		}
	case 1:
		return Rect{
			r1.Interval{-M_PI_4, M_PI_4},
			s1.Interval{M_PI_4, 3 * M_PI_4},
		}
	case 2:
		return Rect{
			r1.Interval{poleMinLat, M_PI_2},
			s1.Interval{-math.Pi, math.Pi},
		}
	case 3:
		return Rect{
			r1.Interval{-M_PI_4, M_PI_4},
			s1.Interval{3 * M_PI_4, -3 * M_PI_4},
		}
	case 4:
		return Rect{
			r1.Interval{-M_PI_4, M_PI_4},
			s1.Interval{-3 * M_PI_4, -M_PI_4},
		}
	default:
		return Rect{
			r1.Interval{-M_PI_2, -poleMinLat},
			s1.Interval{-math.Pi, math.Pi},
		}
	}
}