...

Source file src/github.com/golang/geo/s2/cap.go

Documentation: github.com/golang/geo/s2

     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  }
   520  

View as plain text