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 }
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 }
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}, } } }
"fmt" "math" "github.com/davidreynolds/gos2/r1" "github.com/davidreynolds/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()}, } }