gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

rect.go (24902B)


      1 // Copyright 2014 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package s2
     16 
     17 import (
     18 	"fmt"
     19 	"io"
     20 	"math"
     21 
     22 	"github.com/golang/geo/r1"
     23 	"github.com/golang/geo/r3"
     24 	"github.com/golang/geo/s1"
     25 )
     26 
     27 // Rect represents a closed latitude-longitude rectangle.
     28 type Rect struct {
     29 	Lat r1.Interval
     30 	Lng s1.Interval
     31 }
     32 
     33 var (
     34 	validRectLatRange = r1.Interval{-math.Pi / 2, math.Pi / 2}
     35 	validRectLngRange = s1.FullInterval()
     36 )
     37 
     38 // EmptyRect returns the empty rectangle.
     39 func EmptyRect() Rect { return Rect{r1.EmptyInterval(), s1.EmptyInterval()} }
     40 
     41 // FullRect returns the full rectangle.
     42 func FullRect() Rect { return Rect{validRectLatRange, validRectLngRange} }
     43 
     44 // RectFromLatLng constructs a rectangle containing a single point p.
     45 func RectFromLatLng(p LatLng) Rect {
     46 	return Rect{
     47 		Lat: r1.Interval{p.Lat.Radians(), p.Lat.Radians()},
     48 		Lng: s1.Interval{p.Lng.Radians(), p.Lng.Radians()},
     49 	}
     50 }
     51 
     52 // RectFromCenterSize constructs a rectangle with the given size and center.
     53 // center needs to be normalized, but size does not. The latitude
     54 // interval of the result is clamped to [-90,90] degrees, and the longitude
     55 // interval of the result is FullRect() if and only if the longitude size is
     56 // 360 degrees or more.
     57 //
     58 // Examples of clamping (in degrees):
     59 //   center=(80,170),  size=(40,60)   -> lat=[60,90],   lng=[140,-160]
     60 //   center=(10,40),   size=(210,400) -> lat=[-90,90],  lng=[-180,180]
     61 //   center=(-90,180), size=(20,50)   -> lat=[-90,-80], lng=[155,-155]
     62 func RectFromCenterSize(center, size LatLng) Rect {
     63 	half := LatLng{size.Lat / 2, size.Lng / 2}
     64 	return RectFromLatLng(center).expanded(half)
     65 }
     66 
     67 // IsValid returns true iff the rectangle is valid.
     68 // This requires Lat ⊆ [-π/2,π/2] and Lng ⊆ [-π,π], and Lat = ∅ iff Lng = ∅
     69 func (r Rect) IsValid() bool {
     70 	return math.Abs(r.Lat.Lo) <= math.Pi/2 &&
     71 		math.Abs(r.Lat.Hi) <= math.Pi/2 &&
     72 		r.Lng.IsValid() &&
     73 		r.Lat.IsEmpty() == r.Lng.IsEmpty()
     74 }
     75 
     76 // IsEmpty reports whether the rectangle is empty.
     77 func (r Rect) IsEmpty() bool { return r.Lat.IsEmpty() }
     78 
     79 // IsFull reports whether the rectangle is full.
     80 func (r Rect) IsFull() bool { return r.Lat.Equal(validRectLatRange) && r.Lng.IsFull() }
     81 
     82 // IsPoint reports whether the rectangle is a single point.
     83 func (r Rect) IsPoint() bool { return r.Lat.Lo == r.Lat.Hi && r.Lng.Lo == r.Lng.Hi }
     84 
     85 // Vertex returns the i-th vertex of the rectangle (i = 0,1,2,3) in CCW order
     86 // (lower left, lower right, upper right, upper left).
     87 func (r Rect) Vertex(i int) LatLng {
     88 	var lat, lng float64
     89 
     90 	switch i {
     91 	case 0:
     92 		lat = r.Lat.Lo
     93 		lng = r.Lng.Lo
     94 	case 1:
     95 		lat = r.Lat.Lo
     96 		lng = r.Lng.Hi
     97 	case 2:
     98 		lat = r.Lat.Hi
     99 		lng = r.Lng.Hi
    100 	case 3:
    101 		lat = r.Lat.Hi
    102 		lng = r.Lng.Lo
    103 	}
    104 	return LatLng{s1.Angle(lat) * s1.Radian, s1.Angle(lng) * s1.Radian}
    105 }
    106 
    107 // Lo returns one corner of the rectangle.
    108 func (r Rect) Lo() LatLng {
    109 	return LatLng{s1.Angle(r.Lat.Lo) * s1.Radian, s1.Angle(r.Lng.Lo) * s1.Radian}
    110 }
    111 
    112 // Hi returns the other corner of the rectangle.
    113 func (r Rect) Hi() LatLng {
    114 	return LatLng{s1.Angle(r.Lat.Hi) * s1.Radian, s1.Angle(r.Lng.Hi) * s1.Radian}
    115 }
    116 
    117 // Center returns the center of the rectangle.
    118 func (r Rect) Center() LatLng {
    119 	return LatLng{s1.Angle(r.Lat.Center()) * s1.Radian, s1.Angle(r.Lng.Center()) * s1.Radian}
    120 }
    121 
    122 // Size returns the size of the Rect.
    123 func (r Rect) Size() LatLng {
    124 	return LatLng{s1.Angle(r.Lat.Length()) * s1.Radian, s1.Angle(r.Lng.Length()) * s1.Radian}
    125 }
    126 
    127 // Area returns the surface area of the Rect.
    128 func (r Rect) Area() float64 {
    129 	if r.IsEmpty() {
    130 		return 0
    131 	}
    132 	capDiff := math.Abs(math.Sin(r.Lat.Hi) - math.Sin(r.Lat.Lo))
    133 	return r.Lng.Length() * capDiff
    134 }
    135 
    136 // AddPoint increases the size of the rectangle to include the given point.
    137 func (r Rect) AddPoint(ll LatLng) Rect {
    138 	if !ll.IsValid() {
    139 		return r
    140 	}
    141 	return Rect{
    142 		Lat: r.Lat.AddPoint(ll.Lat.Radians()),
    143 		Lng: r.Lng.AddPoint(ll.Lng.Radians()),
    144 	}
    145 }
    146 
    147 // expanded returns a rectangle that has been expanded by margin.Lat on each side
    148 // in the latitude direction, and by margin.Lng on each side in the longitude
    149 // direction. If either margin is negative, then it shrinks the rectangle on
    150 // the corresponding sides instead. The resulting rectangle may be empty.
    151 //
    152 // The latitude-longitude space has the topology of a cylinder. Longitudes
    153 // "wrap around" at +/-180 degrees, while latitudes are clamped to range [-90, 90].
    154 // This means that any expansion (positive or negative) of the full longitude range
    155 // remains full (since the "rectangle" is actually a continuous band around the
    156 // cylinder), while expansion of the full latitude range remains full only if the
    157 // margin is positive.
    158 //
    159 // If either the latitude or longitude interval becomes empty after
    160 // expansion by a negative margin, the result is empty.
    161 //
    162 // Note that if an expanded rectangle contains a pole, it may not contain
    163 // all possible lat/lng representations of that pole, e.g., both points [π/2,0]
    164 // and [π/2,1] represent the same pole, but they might not be contained by the
    165 // same Rect.
    166 //
    167 // If you are trying to grow a rectangle by a certain distance on the
    168 // sphere (e.g. 5km), refer to the ExpandedByDistance() C++ method implementation
    169 // instead.
    170 func (r Rect) expanded(margin LatLng) Rect {
    171 	lat := r.Lat.Expanded(margin.Lat.Radians())
    172 	lng := r.Lng.Expanded(margin.Lng.Radians())
    173 
    174 	if lat.IsEmpty() || lng.IsEmpty() {
    175 		return EmptyRect()
    176 	}
    177 
    178 	return Rect{
    179 		Lat: lat.Intersection(validRectLatRange),
    180 		Lng: lng,
    181 	}
    182 }
    183 
    184 func (r Rect) String() string { return fmt.Sprintf("[Lo%v, Hi%v]", r.Lo(), r.Hi()) }
    185 
    186 // PolarClosure returns the rectangle unmodified if it does not include either pole.
    187 // If it includes either pole, PolarClosure returns an expansion of the rectangle along
    188 // the longitudinal range to include all possible representations of the contained poles.
    189 func (r Rect) PolarClosure() Rect {
    190 	if r.Lat.Lo == -math.Pi/2 || r.Lat.Hi == math.Pi/2 {
    191 		return Rect{r.Lat, s1.FullInterval()}
    192 	}
    193 	return r
    194 }
    195 
    196 // Union returns the smallest Rect containing the union of this rectangle and the given rectangle.
    197 func (r Rect) Union(other Rect) Rect {
    198 	return Rect{
    199 		Lat: r.Lat.Union(other.Lat),
    200 		Lng: r.Lng.Union(other.Lng),
    201 	}
    202 }
    203 
    204 // Intersection returns the smallest rectangle containing the intersection of
    205 // this rectangle and the given rectangle. Note that the region of intersection
    206 // may consist of two disjoint rectangles, in which case a single rectangle
    207 // spanning both of them is returned.
    208 func (r Rect) Intersection(other Rect) Rect {
    209 	lat := r.Lat.Intersection(other.Lat)
    210 	lng := r.Lng.Intersection(other.Lng)
    211 
    212 	if lat.IsEmpty() || lng.IsEmpty() {
    213 		return EmptyRect()
    214 	}
    215 	return Rect{lat, lng}
    216 }
    217 
    218 // Intersects reports whether this rectangle and the other have any points in common.
    219 func (r Rect) Intersects(other Rect) bool {
    220 	return r.Lat.Intersects(other.Lat) && r.Lng.Intersects(other.Lng)
    221 }
    222 
    223 // CapBound returns a cap that contains Rect.
    224 func (r Rect) CapBound() Cap {
    225 	// We consider two possible bounding caps, one whose axis passes
    226 	// through the center of the lat-long rectangle and one whose axis
    227 	// is the north or south pole.  We return the smaller of the two caps.
    228 
    229 	if r.IsEmpty() {
    230 		return EmptyCap()
    231 	}
    232 
    233 	var poleZ, poleAngle float64
    234 	if r.Lat.Hi+r.Lat.Lo < 0 {
    235 		// South pole axis yields smaller cap.
    236 		poleZ = -1
    237 		poleAngle = math.Pi/2 + r.Lat.Hi
    238 	} else {
    239 		poleZ = 1
    240 		poleAngle = math.Pi/2 - r.Lat.Lo
    241 	}
    242 	poleCap := CapFromCenterAngle(Point{r3.Vector{0, 0, poleZ}}, s1.Angle(poleAngle)*s1.Radian)
    243 
    244 	// For bounding rectangles that span 180 degrees or less in longitude, the
    245 	// maximum cap size is achieved at one of the rectangle vertices.  For
    246 	// rectangles that are larger than 180 degrees, we punt and always return a
    247 	// bounding cap centered at one of the two poles.
    248 	if math.Remainder(r.Lng.Hi-r.Lng.Lo, 2*math.Pi) >= 0 && r.Lng.Hi-r.Lng.Lo < 2*math.Pi {
    249 		midCap := CapFromPoint(PointFromLatLng(r.Center())).AddPoint(PointFromLatLng(r.Lo())).AddPoint(PointFromLatLng(r.Hi()))
    250 		if midCap.Height() < poleCap.Height() {
    251 			return midCap
    252 		}
    253 	}
    254 	return poleCap
    255 }
    256 
    257 // RectBound returns itself.
    258 func (r Rect) RectBound() Rect {
    259 	return r
    260 }
    261 
    262 // Contains reports whether this Rect contains the other Rect.
    263 func (r Rect) Contains(other Rect) bool {
    264 	return r.Lat.ContainsInterval(other.Lat) && r.Lng.ContainsInterval(other.Lng)
    265 }
    266 
    267 // ContainsCell reports whether the given Cell is contained by this Rect.
    268 func (r Rect) ContainsCell(c Cell) bool {
    269 	// A latitude-longitude rectangle contains a cell if and only if it contains
    270 	// the cell's bounding rectangle. This test is exact from a mathematical
    271 	// point of view, assuming that the bounds returned by Cell.RectBound()
    272 	// are tight. However, note that there can be a loss of precision when
    273 	// converting between representations -- for example, if an s2.Cell is
    274 	// converted to a polygon, the polygon's bounding rectangle may not contain
    275 	// the cell's bounding rectangle. This has some slightly unexpected side
    276 	// effects; for instance, if one creates an s2.Polygon from an s2.Cell, the
    277 	// polygon will contain the cell, but the polygon's bounding box will not.
    278 	return r.Contains(c.RectBound())
    279 }
    280 
    281 // ContainsLatLng reports whether the given LatLng is within the Rect.
    282 func (r Rect) ContainsLatLng(ll LatLng) bool {
    283 	if !ll.IsValid() {
    284 		return false
    285 	}
    286 	return r.Lat.Contains(ll.Lat.Radians()) && r.Lng.Contains(ll.Lng.Radians())
    287 }
    288 
    289 // ContainsPoint reports whether the given Point is within the Rect.
    290 func (r Rect) ContainsPoint(p Point) bool {
    291 	return r.ContainsLatLng(LatLngFromPoint(p))
    292 }
    293 
    294 // CellUnionBound computes a covering of the Rect.
    295 func (r Rect) CellUnionBound() []CellID {
    296 	return r.CapBound().CellUnionBound()
    297 }
    298 
    299 // intersectsLatEdge reports whether the edge AB intersects the given edge of constant
    300 // latitude. Requires the points to have unit length.
    301 func intersectsLatEdge(a, b Point, lat s1.Angle, lng s1.Interval) bool {
    302 	// Unfortunately, lines of constant latitude are curves on
    303 	// the sphere. They can intersect a straight edge in 0, 1, or 2 points.
    304 
    305 	// First, compute the normal to the plane AB that points vaguely north.
    306 	z := Point{a.PointCross(b).Normalize()}
    307 	if z.Z < 0 {
    308 		z = Point{z.Mul(-1)}
    309 	}
    310 
    311 	// Extend this to an orthonormal frame (x,y,z) where x is the direction
    312 	// where the great circle through AB achieves its maximium latitude.
    313 	y := Point{z.PointCross(PointFromCoords(0, 0, 1)).Normalize()}
    314 	x := y.Cross(z.Vector)
    315 
    316 	// Compute the angle "theta" from the x-axis (in the x-y plane defined
    317 	// above) where the great circle intersects the given line of latitude.
    318 	sinLat := math.Sin(float64(lat))
    319 	if math.Abs(sinLat) >= x.Z {
    320 		// The great circle does not reach the given latitude.
    321 		return false
    322 	}
    323 
    324 	cosTheta := sinLat / x.Z
    325 	sinTheta := math.Sqrt(1 - cosTheta*cosTheta)
    326 	theta := math.Atan2(sinTheta, cosTheta)
    327 
    328 	// The candidate intersection points are located +/- theta in the x-y
    329 	// plane. For an intersection to be valid, we need to check that the
    330 	// intersection point is contained in the interior of the edge AB and
    331 	// also that it is contained within the given longitude interval "lng".
    332 
    333 	// Compute the range of theta values spanned by the edge AB.
    334 	abTheta := s1.IntervalFromPointPair(
    335 		math.Atan2(a.Dot(y.Vector), a.Dot(x)),
    336 		math.Atan2(b.Dot(y.Vector), b.Dot(x)))
    337 
    338 	if abTheta.Contains(theta) {
    339 		// Check if the intersection point is also in the given lng interval.
    340 		isect := x.Mul(cosTheta).Add(y.Mul(sinTheta))
    341 		if lng.Contains(math.Atan2(isect.Y, isect.X)) {
    342 			return true
    343 		}
    344 	}
    345 
    346 	if abTheta.Contains(-theta) {
    347 		// Check if the other intersection point is also in the given lng interval.
    348 		isect := x.Mul(cosTheta).Sub(y.Mul(sinTheta))
    349 		if lng.Contains(math.Atan2(isect.Y, isect.X)) {
    350 			return true
    351 		}
    352 	}
    353 	return false
    354 }
    355 
    356 // intersectsLngEdge reports whether the edge AB intersects the given edge of constant
    357 // longitude. Requires the points to have unit length.
    358 func intersectsLngEdge(a, b Point, lat r1.Interval, lng s1.Angle) bool {
    359 	// The nice thing about edges of constant longitude is that
    360 	// they are straight lines on the sphere (geodesics).
    361 	return CrossingSign(a, b, PointFromLatLng(LatLng{s1.Angle(lat.Lo), lng}),
    362 		PointFromLatLng(LatLng{s1.Angle(lat.Hi), lng})) == Cross
    363 }
    364 
    365 // IntersectsCell reports whether this rectangle intersects the given cell. This is an
    366 // exact test and may be fairly expensive.
    367 func (r Rect) IntersectsCell(c Cell) bool {
    368 	// First we eliminate the cases where one region completely contains the
    369 	// other. Once these are disposed of, then the regions will intersect
    370 	// if and only if their boundaries intersect.
    371 	if r.IsEmpty() {
    372 		return false
    373 	}
    374 	if r.ContainsPoint(Point{c.id.rawPoint()}) {
    375 		return true
    376 	}
    377 	if c.ContainsPoint(PointFromLatLng(r.Center())) {
    378 		return true
    379 	}
    380 
    381 	// Quick rejection test (not required for correctness).
    382 	if !r.Intersects(c.RectBound()) {
    383 		return false
    384 	}
    385 
    386 	// Precompute the cell vertices as points and latitude-longitudes. We also
    387 	// check whether the Cell contains any corner of the rectangle, or
    388 	// vice-versa, since the edge-crossing tests only check the edge interiors.
    389 	vertices := [4]Point{}
    390 	latlngs := [4]LatLng{}
    391 
    392 	for i := range vertices {
    393 		vertices[i] = c.Vertex(i)
    394 		latlngs[i] = LatLngFromPoint(vertices[i])
    395 		if r.ContainsLatLng(latlngs[i]) {
    396 			return true
    397 		}
    398 		if c.ContainsPoint(PointFromLatLng(r.Vertex(i))) {
    399 			return true
    400 		}
    401 	}
    402 
    403 	// Now check whether the boundaries intersect. Unfortunately, a
    404 	// latitude-longitude rectangle does not have straight edges: two edges
    405 	// are curved, and at least one of them is concave.
    406 	for i := range vertices {
    407 		edgeLng := s1.IntervalFromEndpoints(latlngs[i].Lng.Radians(), latlngs[(i+1)&3].Lng.Radians())
    408 		if !r.Lng.Intersects(edgeLng) {
    409 			continue
    410 		}
    411 
    412 		a := vertices[i]
    413 		b := vertices[(i+1)&3]
    414 		if edgeLng.Contains(r.Lng.Lo) && intersectsLngEdge(a, b, r.Lat, s1.Angle(r.Lng.Lo)) {
    415 			return true
    416 		}
    417 		if edgeLng.Contains(r.Lng.Hi) && intersectsLngEdge(a, b, r.Lat, s1.Angle(r.Lng.Hi)) {
    418 			return true
    419 		}
    420 		if intersectsLatEdge(a, b, s1.Angle(r.Lat.Lo), r.Lng) {
    421 			return true
    422 		}
    423 		if intersectsLatEdge(a, b, s1.Angle(r.Lat.Hi), r.Lng) {
    424 			return true
    425 		}
    426 	}
    427 	return false
    428 }
    429 
    430 // Encode encodes the Rect.
    431 func (r Rect) Encode(w io.Writer) error {
    432 	e := &encoder{w: w}
    433 	r.encode(e)
    434 	return e.err
    435 }
    436 
    437 func (r Rect) encode(e *encoder) {
    438 	e.writeInt8(encodingVersion)
    439 	e.writeFloat64(r.Lat.Lo)
    440 	e.writeFloat64(r.Lat.Hi)
    441 	e.writeFloat64(r.Lng.Lo)
    442 	e.writeFloat64(r.Lng.Hi)
    443 }
    444 
    445 // Decode decodes a rectangle.
    446 func (r *Rect) Decode(rd io.Reader) error {
    447 	d := &decoder{r: asByteReader(rd)}
    448 	r.decode(d)
    449 	return d.err
    450 }
    451 
    452 func (r *Rect) decode(d *decoder) {
    453 	if version := d.readUint8(); int8(version) != encodingVersion && d.err == nil {
    454 		d.err = fmt.Errorf("can't decode version %d; my version: %d", version, encodingVersion)
    455 		return
    456 	}
    457 	r.Lat.Lo = d.readFloat64()
    458 	r.Lat.Hi = d.readFloat64()
    459 	r.Lng.Lo = d.readFloat64()
    460 	r.Lng.Hi = d.readFloat64()
    461 	return
    462 }
    463 
    464 // DistanceToLatLng returns the minimum distance (measured along the surface of the sphere)
    465 // from a given point to the rectangle (both its boundary and its interior).
    466 // If r is empty, the result is meaningless.
    467 // The latlng must be valid.
    468 func (r Rect) DistanceToLatLng(ll LatLng) s1.Angle {
    469 	if r.Lng.Contains(float64(ll.Lng)) {
    470 		return maxAngle(0, ll.Lat-s1.Angle(r.Lat.Hi), s1.Angle(r.Lat.Lo)-ll.Lat)
    471 	}
    472 
    473 	i := s1.IntervalFromEndpoints(r.Lng.Hi, r.Lng.ComplementCenter())
    474 	rectLng := r.Lng.Lo
    475 	if i.Contains(float64(ll.Lng)) {
    476 		rectLng = r.Lng.Hi
    477 	}
    478 
    479 	lo := LatLng{s1.Angle(r.Lat.Lo) * s1.Radian, s1.Angle(rectLng) * s1.Radian}
    480 	hi := LatLng{s1.Angle(r.Lat.Hi) * s1.Radian, s1.Angle(rectLng) * s1.Radian}
    481 	return DistanceFromSegment(PointFromLatLng(ll), PointFromLatLng(lo), PointFromLatLng(hi))
    482 }
    483 
    484 // DirectedHausdorffDistance returns the directed Hausdorff distance (measured along the
    485 // surface of the sphere) to the given Rect. The directed Hausdorff
    486 // distance from rectangle A to rectangle B is given by
    487 //     h(A, B) = max_{p in A} min_{q in B} d(p, q).
    488 func (r Rect) DirectedHausdorffDistance(other Rect) s1.Angle {
    489 	if r.IsEmpty() {
    490 		return 0 * s1.Radian
    491 	}
    492 	if other.IsEmpty() {
    493 		return math.Pi * s1.Radian
    494 	}
    495 
    496 	lng := r.Lng.DirectedHausdorffDistance(other.Lng)
    497 	return directedHausdorffDistance(lng, r.Lat, other.Lat)
    498 }
    499 
    500 // HausdorffDistance returns the undirected Hausdorff distance (measured along the
    501 // surface of the sphere) to the given Rect.
    502 // The Hausdorff distance between rectangle A and rectangle B is given by
    503 //     H(A, B) = max{h(A, B), h(B, A)}.
    504 func (r Rect) HausdorffDistance(other Rect) s1.Angle {
    505 	return maxAngle(r.DirectedHausdorffDistance(other),
    506 		other.DirectedHausdorffDistance(r))
    507 }
    508 
    509 // ApproxEqual reports whether the latitude and longitude intervals of the two rectangles
    510 // are the same up to a small tolerance.
    511 func (r Rect) ApproxEqual(other Rect) bool {
    512 	return r.Lat.ApproxEqual(other.Lat) && r.Lng.ApproxEqual(other.Lng)
    513 }
    514 
    515 // directedHausdorffDistance returns the directed Hausdorff distance
    516 // from one longitudinal edge spanning latitude range 'a' to the other
    517 // longitudinal edge spanning latitude range 'b', with their longitudinal
    518 // difference given by 'lngDiff'.
    519 func directedHausdorffDistance(lngDiff s1.Angle, a, b r1.Interval) s1.Angle {
    520 	// By symmetry, we can assume a's longitude is 0 and b's longitude is
    521 	// lngDiff. Call b's two endpoints bLo and bHi. Let H be the hemisphere
    522 	// containing a and delimited by the longitude line of b. The Voronoi diagram
    523 	// of b on H has three edges (portions of great circles) all orthogonal to b
    524 	// and meeting at bLo cross bHi.
    525 	// E1: (bLo, bLo cross bHi)
    526 	// E2: (bHi, bLo cross bHi)
    527 	// E3: (-bMid, bLo cross bHi), where bMid is the midpoint of b
    528 	//
    529 	// They subdivide H into three Voronoi regions. Depending on how longitude 0
    530 	// (which contains edge a) intersects these regions, we distinguish two cases:
    531 	// Case 1: it intersects three regions. This occurs when lngDiff <= π/2.
    532 	// Case 2: it intersects only two regions. This occurs when lngDiff > π/2.
    533 	//
    534 	// In the first case, the directed Hausdorff distance to edge b can only be
    535 	// realized by the following points on a:
    536 	// A1: two endpoints of a.
    537 	// A2: intersection of a with the equator, if b also intersects the equator.
    538 	//
    539 	// In the second case, the directed Hausdorff distance to edge b can only be
    540 	// realized by the following points on a:
    541 	// B1: two endpoints of a.
    542 	// B2: intersection of a with E3
    543 	// B3: farthest point from bLo to the interior of D, and farthest point from
    544 	//     bHi to the interior of U, if any, where D (resp. U) is the portion
    545 	//     of edge a below (resp. above) the intersection point from B2.
    546 
    547 	if lngDiff < 0 {
    548 		panic("impossible: negative lngDiff")
    549 	}
    550 	if lngDiff > math.Pi {
    551 		panic("impossible: lngDiff > Pi")
    552 	}
    553 
    554 	if lngDiff == 0 {
    555 		return s1.Angle(a.DirectedHausdorffDistance(b))
    556 	}
    557 
    558 	// Assumed longitude of b.
    559 	bLng := lngDiff
    560 	// Two endpoints of b.
    561 	bLo := PointFromLatLng(LatLng{s1.Angle(b.Lo), bLng})
    562 	bHi := PointFromLatLng(LatLng{s1.Angle(b.Hi), bLng})
    563 
    564 	// Cases A1 and B1.
    565 	aLo := PointFromLatLng(LatLng{s1.Angle(a.Lo), 0})
    566 	aHi := PointFromLatLng(LatLng{s1.Angle(a.Hi), 0})
    567 	maxDistance := maxAngle(
    568 		DistanceFromSegment(aLo, bLo, bHi),
    569 		DistanceFromSegment(aHi, bLo, bHi))
    570 
    571 	if lngDiff <= math.Pi/2 {
    572 		// Case A2.
    573 		if a.Contains(0) && b.Contains(0) {
    574 			maxDistance = maxAngle(maxDistance, lngDiff)
    575 		}
    576 		return maxDistance
    577 	}
    578 
    579 	// Case B2.
    580 	p := bisectorIntersection(b, bLng)
    581 	pLat := LatLngFromPoint(p).Lat
    582 	if a.Contains(float64(pLat)) {
    583 		maxDistance = maxAngle(maxDistance, p.Angle(bLo.Vector))
    584 	}
    585 
    586 	// Case B3.
    587 	if pLat > s1.Angle(a.Lo) {
    588 		intDist, ok := interiorMaxDistance(r1.Interval{a.Lo, math.Min(float64(pLat), a.Hi)}, bLo)
    589 		if ok {
    590 			maxDistance = maxAngle(maxDistance, intDist)
    591 		}
    592 	}
    593 	if pLat < s1.Angle(a.Hi) {
    594 		intDist, ok := interiorMaxDistance(r1.Interval{math.Max(float64(pLat), a.Lo), a.Hi}, bHi)
    595 		if ok {
    596 			maxDistance = maxAngle(maxDistance, intDist)
    597 		}
    598 	}
    599 
    600 	return maxDistance
    601 }
    602 
    603 // interiorMaxDistance returns the max distance from a point b to the segment spanning latitude range
    604 // aLat on longitude 0 if the max occurs in the interior of aLat. Otherwise, returns (0, false).
    605 func interiorMaxDistance(aLat r1.Interval, b Point) (a s1.Angle, ok bool) {
    606 	// Longitude 0 is in the y=0 plane. b.X >= 0 implies that the maximum
    607 	// does not occur in the interior of aLat.
    608 	if aLat.IsEmpty() || b.X >= 0 {
    609 		return 0, false
    610 	}
    611 
    612 	// Project b to the y=0 plane. The antipodal of the normalized projection is
    613 	// the point at which the maxium distance from b occurs, if it is contained
    614 	// in aLat.
    615 	intersectionPoint := PointFromCoords(-b.X, 0, -b.Z)
    616 	if !aLat.InteriorContains(float64(LatLngFromPoint(intersectionPoint).Lat)) {
    617 		return 0, false
    618 	}
    619 	return b.Angle(intersectionPoint.Vector), true
    620 }
    621 
    622 // bisectorIntersection return the intersection of longitude 0 with the bisector of an edge
    623 // on longitude 'lng' and spanning latitude range 'lat'.
    624 func bisectorIntersection(lat r1.Interval, lng s1.Angle) Point {
    625 	lng = s1.Angle(math.Abs(float64(lng)))
    626 	latCenter := s1.Angle(lat.Center())
    627 
    628 	// A vector orthogonal to the bisector of the given longitudinal edge.
    629 	orthoBisector := LatLng{latCenter - math.Pi/2, lng}
    630 	if latCenter < 0 {
    631 		orthoBisector = LatLng{-latCenter - math.Pi/2, lng - math.Pi}
    632 	}
    633 
    634 	// A vector orthogonal to longitude 0.
    635 	orthoLng := Point{r3.Vector{0, -1, 0}}
    636 
    637 	return orthoLng.PointCross(PointFromLatLng(orthoBisector))
    638 }
    639 
    640 // Centroid returns the true centroid of the given Rect multiplied by its
    641 // surface area. The result is not unit length, so you may want to normalize it.
    642 // Note that in general the centroid is *not* at the center of the rectangle, and
    643 // in fact it may not even be contained by the rectangle. (It is the "center of
    644 // mass" of the rectangle viewed as subset of the unit sphere, i.e. it is the
    645 // point in space about which this curved shape would rotate.)
    646 //
    647 // The reason for multiplying the result by the rectangle area is to make it
    648 // easier to compute the centroid of more complicated shapes. The centroid
    649 // of a union of disjoint regions can be computed simply by adding their
    650 // Centroid results.
    651 func (r Rect) Centroid() Point {
    652 	// When a sphere is divided into slices of constant thickness by a set
    653 	// of parallel planes, all slices have the same surface area. This
    654 	// implies that the z-component of the centroid is simply the midpoint
    655 	// of the z-interval spanned by the Rect.
    656 	//
    657 	// Similarly, it is easy to see that the (x,y) of the centroid lies in
    658 	// the plane through the midpoint of the rectangle's longitude interval.
    659 	// We only need to determine the distance "d" of this point from the
    660 	// z-axis.
    661 	//
    662 	// Let's restrict our attention to a particular z-value. In this
    663 	// z-plane, the Rect is a circular arc. The centroid of this arc
    664 	// lies on a radial line through the midpoint of the arc, and at a
    665 	// distance from the z-axis of
    666 	//
    667 	//     r * (sin(alpha) / alpha)
    668 	//
    669 	// where r = sqrt(1-z^2) is the radius of the arc, and "alpha" is half
    670 	// of the arc length (i.e., the arc covers longitudes [-alpha, alpha]).
    671 	//
    672 	// To find the centroid distance from the z-axis for the entire
    673 	// rectangle, we just need to integrate over the z-interval. This gives
    674 	//
    675 	//    d = Integrate[sqrt(1-z^2)*sin(alpha)/alpha, z1..z2] / (z2 - z1)
    676 	//
    677 	// where [z1, z2] is the range of z-values covered by the rectangle.
    678 	// This simplifies to
    679 	//
    680 	//    d = sin(alpha)/(2*alpha*(z2-z1))*(z2*r2 - z1*r1 + theta2 - theta1)
    681 	//
    682 	// where [theta1, theta2] is the latitude interval, z1=sin(theta1),
    683 	// z2=sin(theta2), r1=cos(theta1), and r2=cos(theta2).
    684 	//
    685 	// Finally, we want to return not the centroid itself, but the centroid
    686 	// scaled by the area of the rectangle. The area of the rectangle is
    687 	//
    688 	//    A = 2 * alpha * (z2 - z1)
    689 	//
    690 	// which fortunately appears in the denominator of "d".
    691 
    692 	if r.IsEmpty() {
    693 		return Point{}
    694 	}
    695 
    696 	z1 := math.Sin(r.Lat.Lo)
    697 	z2 := math.Sin(r.Lat.Hi)
    698 	r1 := math.Cos(r.Lat.Lo)
    699 	r2 := math.Cos(r.Lat.Hi)
    700 
    701 	alpha := 0.5 * r.Lng.Length()
    702 	r0 := math.Sin(alpha) * (r2*z2 - r1*z1 + r.Lat.Length())
    703 	lng := r.Lng.Center()
    704 	z := alpha * (z2 + z1) * (z2 - z1) // scaled by the area
    705 
    706 	return Point{r3.Vector{r0 * math.Cos(lng), r0 * math.Sin(lng), z}}
    707 }
    708 
    709 // BUG: The major differences from the C++ version are:
    710 //  - Get*Distance, Vertex, InteriorContains(LatLng|Rect|Point)