gtsocial-umbx

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

cap.go (17742B)


      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/s1"
     24 )
     25 
     26 var (
     27 	// centerPoint is the default center for Caps
     28 	centerPoint = PointFromCoords(1.0, 0, 0)
     29 )
     30 
     31 // Cap represents a disc-shaped region defined by a center and radius.
     32 // Technically this shape is called a "spherical cap" (rather than disc)
     33 // because it is not planar; the cap represents a portion of the sphere that
     34 // has been cut off by a plane. The boundary of the cap is the circle defined
     35 // by the intersection of the sphere and the plane. For containment purposes,
     36 // the cap is a closed set, i.e. it contains its boundary.
     37 //
     38 // For the most part, you can use a spherical cap wherever you would use a
     39 // disc in planar geometry. The radius of the cap is measured along the
     40 // surface of the sphere (rather than the straight-line distance through the
     41 // interior). Thus a cap of radius π/2 is a hemisphere, and a cap of radius
     42 // π covers the entire sphere.
     43 //
     44 // The center is a point on the surface of the unit sphere. (Hence the need for
     45 // it to be of unit length.)
     46 //
     47 // A cap can also be defined by its center point and height. The height is the
     48 // distance from the center point to the cutoff plane. There is also support for
     49 // "empty" and "full" caps, which contain no points and all points respectively.
     50 //
     51 // Here are some useful relationships between the cap height (h), the cap
     52 // radius (r), the maximum chord length from the cap's center (d), and the
     53 // radius of cap's base (a).
     54 //
     55 //     h = 1 - cos(r)
     56 //       = 2 * sin^2(r/2)
     57 //   d^2 = 2 * h
     58 //       = a^2 + h^2
     59 //
     60 // The zero value of Cap is an invalid cap. Use EmptyCap to get a valid empty cap.
     61 type Cap struct {
     62 	center Point
     63 	radius s1.ChordAngle
     64 }
     65 
     66 // CapFromPoint constructs a cap containing a single point.
     67 func CapFromPoint(p Point) Cap {
     68 	return CapFromCenterChordAngle(p, 0)
     69 }
     70 
     71 // CapFromCenterAngle constructs a cap with the given center and angle.
     72 func CapFromCenterAngle(center Point, angle s1.Angle) Cap {
     73 	return CapFromCenterChordAngle(center, s1.ChordAngleFromAngle(angle))
     74 }
     75 
     76 // CapFromCenterChordAngle constructs a cap where the angle is expressed as an
     77 // s1.ChordAngle. This constructor is more efficient than using an s1.Angle.
     78 func CapFromCenterChordAngle(center Point, radius s1.ChordAngle) Cap {
     79 	return Cap{
     80 		center: center,
     81 		radius: radius,
     82 	}
     83 }
     84 
     85 // CapFromCenterHeight constructs a cap with the given center and height. A
     86 // negative height yields an empty cap; a height of 2 or more yields a full cap.
     87 // The center should be unit length.
     88 func CapFromCenterHeight(center Point, height float64) Cap {
     89 	return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(2*height))
     90 }
     91 
     92 // CapFromCenterArea constructs a cap with the given center and surface area.
     93 // Note that the area can also be interpreted as the solid angle subtended by the
     94 // cap (because the sphere has unit radius). A negative area yields an empty cap;
     95 // an area of 4*π or more yields a full cap.
     96 func CapFromCenterArea(center Point, area float64) Cap {
     97 	return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(area/math.Pi))
     98 }
     99 
    100 // EmptyCap returns a cap that contains no points.
    101 func EmptyCap() Cap {
    102 	return CapFromCenterChordAngle(centerPoint, s1.NegativeChordAngle)
    103 }
    104 
    105 // FullCap returns a cap that contains all points.
    106 func FullCap() Cap {
    107 	return CapFromCenterChordAngle(centerPoint, s1.StraightChordAngle)
    108 }
    109 
    110 // IsValid reports whether the Cap is considered valid.
    111 func (c Cap) IsValid() bool {
    112 	return c.center.Vector.IsUnit() && c.radius <= s1.StraightChordAngle
    113 }
    114 
    115 // IsEmpty reports whether the cap is empty, i.e. it contains no points.
    116 func (c Cap) IsEmpty() bool {
    117 	return c.radius < 0
    118 }
    119 
    120 // IsFull reports whether the cap is full, i.e. it contains all points.
    121 func (c Cap) IsFull() bool {
    122 	return c.radius == s1.StraightChordAngle
    123 }
    124 
    125 // Center returns the cap's center point.
    126 func (c Cap) Center() Point {
    127 	return c.center
    128 }
    129 
    130 // Height returns the height of the cap. This is the distance from the center
    131 // point to the cutoff plane.
    132 func (c Cap) Height() float64 {
    133 	return float64(0.5 * c.radius)
    134 }
    135 
    136 // Radius returns the cap radius as an s1.Angle. (Note that the cap angle
    137 // is stored internally as a ChordAngle, so this method requires a trigonometric
    138 // operation and may yield a slightly different result than the value passed
    139 // to CapFromCenterAngle).
    140 func (c Cap) Radius() s1.Angle {
    141 	return c.radius.Angle()
    142 }
    143 
    144 // Area returns the surface area of the Cap on the unit sphere.
    145 func (c Cap) Area() float64 {
    146 	return 2.0 * math.Pi * math.Max(0, c.Height())
    147 }
    148 
    149 // Contains reports whether this cap contains the other.
    150 func (c Cap) Contains(other Cap) bool {
    151 	// In a set containment sense, every cap contains the empty cap.
    152 	if c.IsFull() || other.IsEmpty() {
    153 		return true
    154 	}
    155 	return c.radius >= ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
    156 }
    157 
    158 // Intersects reports whether this cap intersects the other cap.
    159 // i.e. whether they have any points in common.
    160 func (c Cap) Intersects(other Cap) bool {
    161 	if c.IsEmpty() || other.IsEmpty() {
    162 		return false
    163 	}
    164 
    165 	return c.radius.Add(other.radius) >= ChordAngleBetweenPoints(c.center, other.center)
    166 }
    167 
    168 // InteriorIntersects reports whether this caps interior intersects the other cap.
    169 func (c Cap) InteriorIntersects(other Cap) bool {
    170 	// Make sure this cap has an interior and the other cap is non-empty.
    171 	if c.radius <= 0 || other.IsEmpty() {
    172 		return false
    173 	}
    174 
    175 	return c.radius.Add(other.radius) > ChordAngleBetweenPoints(c.center, other.center)
    176 }
    177 
    178 // ContainsPoint reports whether this cap contains the point.
    179 func (c Cap) ContainsPoint(p Point) bool {
    180 	return ChordAngleBetweenPoints(c.center, p) <= c.radius
    181 }
    182 
    183 // InteriorContainsPoint reports whether the point is within the interior of this cap.
    184 func (c Cap) InteriorContainsPoint(p Point) bool {
    185 	return c.IsFull() || ChordAngleBetweenPoints(c.center, p) < c.radius
    186 }
    187 
    188 // Complement returns the complement of the interior of the cap. A cap and its
    189 // complement have the same boundary but do not share any interior points.
    190 // The complement operator is not a bijection because the complement of a
    191 // singleton cap (containing a single point) is the same as the complement
    192 // of an empty cap.
    193 func (c Cap) Complement() Cap {
    194 	if c.IsFull() {
    195 		return EmptyCap()
    196 	}
    197 	if c.IsEmpty() {
    198 		return FullCap()
    199 	}
    200 
    201 	return CapFromCenterChordAngle(Point{c.center.Mul(-1)}, s1.StraightChordAngle.Sub(c.radius))
    202 }
    203 
    204 // CapBound returns a bounding spherical cap. This is not guaranteed to be exact.
    205 func (c Cap) CapBound() Cap {
    206 	return c
    207 }
    208 
    209 // RectBound returns a bounding latitude-longitude rectangle.
    210 // The bounds are not guaranteed to be tight.
    211 func (c Cap) RectBound() Rect {
    212 	if c.IsEmpty() {
    213 		return EmptyRect()
    214 	}
    215 
    216 	capAngle := c.Radius().Radians()
    217 	allLongitudes := false
    218 	lat := r1.Interval{
    219 		Lo: latitude(c.center).Radians() - capAngle,
    220 		Hi: latitude(c.center).Radians() + capAngle,
    221 	}
    222 	lng := s1.FullInterval()
    223 
    224 	// Check whether cap includes the south pole.
    225 	if lat.Lo <= -math.Pi/2 {
    226 		lat.Lo = -math.Pi / 2
    227 		allLongitudes = true
    228 	}
    229 
    230 	// Check whether cap includes the north pole.
    231 	if lat.Hi >= math.Pi/2 {
    232 		lat.Hi = math.Pi / 2
    233 		allLongitudes = true
    234 	}
    235 
    236 	if !allLongitudes {
    237 		// Compute the range of longitudes covered by the cap. We use the law
    238 		// of sines for spherical triangles. Consider the triangle ABC where
    239 		// A is the north pole, B is the center of the cap, and C is the point
    240 		// of tangency between the cap boundary and a line of longitude. Then
    241 		// C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
    242 		// we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
    243 		// Here "a" is the cap angle, and "c" is the colatitude (90 degrees
    244 		// minus the latitude). This formula also works for negative latitudes.
    245 		//
    246 		// The formula for sin(a) follows from the relationship h = 1 - cos(a).
    247 		sinA := c.radius.Sin()
    248 		sinC := math.Cos(latitude(c.center).Radians())
    249 		if sinA <= sinC {
    250 			angleA := math.Asin(sinA / sinC)
    251 			lng.Lo = math.Remainder(longitude(c.center).Radians()-angleA, math.Pi*2)
    252 			lng.Hi = math.Remainder(longitude(c.center).Radians()+angleA, math.Pi*2)
    253 		}
    254 	}
    255 	return Rect{lat, lng}
    256 }
    257 
    258 // Equal reports whether this cap is equal to the other cap.
    259 func (c Cap) Equal(other Cap) bool {
    260 	return (c.radius == other.radius && c.center == other.center) ||
    261 		(c.IsEmpty() && other.IsEmpty()) ||
    262 		(c.IsFull() && other.IsFull())
    263 }
    264 
    265 // ApproxEqual reports whether this cap is equal to the other cap within the given tolerance.
    266 func (c Cap) ApproxEqual(other Cap) bool {
    267 	const epsilon = 1e-14
    268 	r2 := float64(c.radius)
    269 	otherR2 := float64(other.radius)
    270 	return c.center.ApproxEqual(other.center) &&
    271 		math.Abs(r2-otherR2) <= epsilon ||
    272 		c.IsEmpty() && otherR2 <= epsilon ||
    273 		other.IsEmpty() && r2 <= epsilon ||
    274 		c.IsFull() && otherR2 >= 2-epsilon ||
    275 		other.IsFull() && r2 >= 2-epsilon
    276 }
    277 
    278 // AddPoint increases the cap if necessary to include the given point. If this cap is empty,
    279 // then the center is set to the point with a zero height. p must be unit-length.
    280 func (c Cap) AddPoint(p Point) Cap {
    281 	if c.IsEmpty() {
    282 		c.center = p
    283 		c.radius = 0
    284 		return c
    285 	}
    286 
    287 	// After calling cap.AddPoint(p), cap.Contains(p) must be true. However
    288 	// we don't need to do anything special to achieve this because Contains()
    289 	// does exactly the same distance calculation that we do here.
    290 	if newRad := ChordAngleBetweenPoints(c.center, p); newRad > c.radius {
    291 		c.radius = newRad
    292 	}
    293 	return c
    294 }
    295 
    296 // AddCap increases the cap height if necessary to include the other cap. If this cap is empty,
    297 // it is set to the other cap.
    298 func (c Cap) AddCap(other Cap) Cap {
    299 	if c.IsEmpty() {
    300 		return other
    301 	}
    302 	if other.IsEmpty() {
    303 		return c
    304 	}
    305 
    306 	// We round up the distance to ensure that the cap is actually contained.
    307 	// TODO(roberts): Do some error analysis in order to guarantee this.
    308 	dist := ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
    309 	if newRad := dist.Expanded(dblEpsilon * float64(dist)); newRad > c.radius {
    310 		c.radius = newRad
    311 	}
    312 	return c
    313 }
    314 
    315 // Expanded returns a new cap expanded by the given angle. If the cap is empty,
    316 // it returns an empty cap.
    317 func (c Cap) Expanded(distance s1.Angle) Cap {
    318 	if c.IsEmpty() {
    319 		return EmptyCap()
    320 	}
    321 	return CapFromCenterChordAngle(c.center, c.radius.Add(s1.ChordAngleFromAngle(distance)))
    322 }
    323 
    324 func (c Cap) String() string {
    325 	return fmt.Sprintf("[Center=%v, Radius=%f]", c.center.Vector, c.Radius().Degrees())
    326 }
    327 
    328 // radiusToHeight converts an s1.Angle into the height of the cap.
    329 func radiusToHeight(r s1.Angle) float64 {
    330 	if r.Radians() < 0 {
    331 		return float64(s1.NegativeChordAngle)
    332 	}
    333 	if r.Radians() >= math.Pi {
    334 		return float64(s1.RightChordAngle)
    335 	}
    336 	return float64(0.5 * s1.ChordAngleFromAngle(r))
    337 
    338 }
    339 
    340 // ContainsCell reports whether the cap contains the given cell.
    341 func (c Cap) ContainsCell(cell Cell) bool {
    342 	// If the cap does not contain all cell vertices, return false.
    343 	var vertices [4]Point
    344 	for k := 0; k < 4; k++ {
    345 		vertices[k] = cell.Vertex(k)
    346 		if !c.ContainsPoint(vertices[k]) {
    347 			return false
    348 		}
    349 	}
    350 	// Otherwise, return true if the complement of the cap does not intersect the cell.
    351 	return !c.Complement().intersects(cell, vertices)
    352 }
    353 
    354 // IntersectsCell reports whether the cap intersects the cell.
    355 func (c Cap) IntersectsCell(cell Cell) bool {
    356 	// If the cap contains any cell vertex, return true.
    357 	var vertices [4]Point
    358 	for k := 0; k < 4; k++ {
    359 		vertices[k] = cell.Vertex(k)
    360 		if c.ContainsPoint(vertices[k]) {
    361 			return true
    362 		}
    363 	}
    364 	return c.intersects(cell, vertices)
    365 }
    366 
    367 // intersects reports whether the cap intersects any point of the cell excluding
    368 // its vertices (which are assumed to already have been checked).
    369 func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
    370 	// If the cap is a hemisphere or larger, the cell and the complement of the cap
    371 	// are both convex. Therefore since no vertex of the cell is contained, no other
    372 	// interior point of the cell is contained either.
    373 	if c.radius >= s1.RightChordAngle {
    374 		return false
    375 	}
    376 
    377 	// We need to check for empty caps due to the center check just below.
    378 	if c.IsEmpty() {
    379 		return false
    380 	}
    381 
    382 	// Optimization: return true if the cell contains the cap center. This allows half
    383 	// of the edge checks below to be skipped.
    384 	if cell.ContainsPoint(c.center) {
    385 		return true
    386 	}
    387 
    388 	// At this point we know that the cell does not contain the cap center, and the cap
    389 	// does not contain any cell vertex. The only way that they can intersect is if the
    390 	// cap intersects the interior of some edge.
    391 	sin2Angle := c.radius.Sin2()
    392 	for k := 0; k < 4; k++ {
    393 		edge := cell.Edge(k).Vector
    394 		dot := c.center.Vector.Dot(edge)
    395 		if dot > 0 {
    396 			// The center is in the interior half-space defined by the edge. We do not need
    397 			// to consider these edges, since if the cap intersects this edge then it also
    398 			// intersects the edge on the opposite side of the cell, because the center is
    399 			// not contained with the cell.
    400 			continue
    401 		}
    402 
    403 		// The Norm2() factor is necessary because "edge" is not normalized.
    404 		if dot*dot > sin2Angle*edge.Norm2() {
    405 			return false
    406 		}
    407 
    408 		// Otherwise, the great circle containing this edge intersects the interior of the cap. We just
    409 		// need to check whether the point of closest approach occurs between the two edge endpoints.
    410 		dir := edge.Cross(c.center.Vector)
    411 		if dir.Dot(vertices[k].Vector) < 0 && dir.Dot(vertices[(k+1)&3].Vector) > 0 {
    412 			return true
    413 		}
    414 	}
    415 	return false
    416 }
    417 
    418 // CellUnionBound computes a covering of the Cap. In general the covering
    419 // consists of at most 4 cells except for very large caps, which may need
    420 // up to 6 cells. The output is not sorted.
    421 func (c Cap) CellUnionBound() []CellID {
    422 	// TODO(roberts): The covering could be made quite a bit tighter by mapping
    423 	// the cap to a rectangle in (i,j)-space and finding a covering for that.
    424 
    425 	// Find the maximum level such that the cap contains at most one cell vertex
    426 	// and such that CellID.AppendVertexNeighbors() can be called.
    427 	level := MinWidthMetric.MaxLevel(c.Radius().Radians()) - 1
    428 
    429 	// If level < 0, more than three face cells are required.
    430 	if level < 0 {
    431 		cellIDs := make([]CellID, 6)
    432 		for face := 0; face < 6; face++ {
    433 			cellIDs[face] = CellIDFromFace(face)
    434 		}
    435 		return cellIDs
    436 	}
    437 	// The covering consists of the 4 cells at the given level that share the
    438 	// cell vertex that is closest to the cap center.
    439 	return cellIDFromPoint(c.center).VertexNeighbors(level)
    440 }
    441 
    442 // Centroid returns the true centroid of the cap multiplied by its surface area
    443 // The result lies on the ray from the origin through the cap's center, but it
    444 // is not unit length. Note that if you just want the "surface centroid", i.e.
    445 // the normalized result, then it is simpler to call Center.
    446 //
    447 // The reason for multiplying the result by the cap area is to make it
    448 // easier to compute the centroid of more complicated shapes. The centroid
    449 // of a union of disjoint regions can be computed simply by adding their
    450 // Centroid() results. Caveat: for caps that contain a single point
    451 // (i.e., zero radius), this method always returns the origin (0, 0, 0).
    452 // This is because shapes with no area don't affect the centroid of a
    453 // union whose total area is positive.
    454 func (c Cap) Centroid() Point {
    455 	// From symmetry, the centroid of the cap must be somewhere on the line
    456 	// from the origin to the center of the cap on the surface of the sphere.
    457 	// When a sphere is divided into slices of constant thickness by a set of
    458 	// parallel planes, all slices have the same surface area. This implies
    459 	// that the radial component of the centroid is simply the midpoint of the
    460 	// range of radial distances spanned by the cap. That is easily computed
    461 	// from the cap height.
    462 	if c.IsEmpty() {
    463 		return Point{}
    464 	}
    465 	r := 1 - 0.5*c.Height()
    466 	return Point{c.center.Mul(r * c.Area())}
    467 }
    468 
    469 // Union returns the smallest cap which encloses this cap and other.
    470 func (c Cap) Union(other Cap) Cap {
    471 	// If the other cap is larger, swap c and other for the rest of the computations.
    472 	if c.radius < other.radius {
    473 		c, other = other, c
    474 	}
    475 
    476 	if c.IsFull() || other.IsEmpty() {
    477 		return c
    478 	}
    479 
    480 	// TODO: This calculation would be more efficient using s1.ChordAngles.
    481 	cRadius := c.Radius()
    482 	otherRadius := other.Radius()
    483 	distance := c.center.Distance(other.center)
    484 	if cRadius >= distance+otherRadius {
    485 		return c
    486 	}
    487 
    488 	resRadius := 0.5 * (distance + cRadius + otherRadius)
    489 	resCenter := InterpolateAtDistance(0.5*(distance-cRadius+otherRadius), c.center, other.center)
    490 	return CapFromCenterAngle(resCenter, resRadius)
    491 }
    492 
    493 // Encode encodes the Cap.
    494 func (c Cap) Encode(w io.Writer) error {
    495 	e := &encoder{w: w}
    496 	c.encode(e)
    497 	return e.err
    498 }
    499 
    500 func (c Cap) encode(e *encoder) {
    501 	e.writeFloat64(c.center.X)
    502 	e.writeFloat64(c.center.Y)
    503 	e.writeFloat64(c.center.Z)
    504 	e.writeFloat64(float64(c.radius))
    505 }
    506 
    507 // Decode decodes the Cap.
    508 func (c *Cap) Decode(r io.Reader) error {
    509 	d := &decoder{r: asByteReader(r)}
    510 	c.decode(d)
    511 	return d.err
    512 }
    513 
    514 func (c *Cap) decode(d *decoder) {
    515 	c.center.X = d.readFloat64()
    516 	c.center.Y = d.readFloat64()
    517 	c.center.Z = d.readFloat64()
    518 	c.radius = s1.ChordAngle(d.readFloat64())
    519 }