/**
  * Return the complement of the interior of the interval. An interval and its
  * complement have the same boundary but do not share any interior values. The
  * complement operator is not a bijection, since the complement of a singleton
  * interval (containing a single value) is the same as the complement of an
  * empty interval.
  *#/
  * public S1Interval complement() {
  * if (lo() == hi()) {
  * return full(); // Singleton.
  * }
  * return new S1Interval(hi(), lo(), true); // Handles
  * // empty and
  * // full.
  * }
  *
  * /** Return true if the interval (which is closed) contains the point 'p'. *#/
  * public boolean contains(double p) {
  * // Works for empty, full, and singleton intervals.
  * // assert (Math.abs(p) <= S2.M_PI);
  * if (p == -S2.M_PI) {
  * p = S2.M_PI;
  * }
  * return fastContains(p);
  * }
  *
  * /**
  * Return true if the interval (which is closed) contains the point 'p'. Skips
  * the normalization of 'p' from -Pi to Pi.
  *
  *#/
  * public boolean fastContains(double p) {
  * if (isInverted()) {
  * return (p >= lo() || p <= hi()) && !isEmpty();
  * } else {
  * return p >= lo() && p <= hi();
  * }
  * }
  *
  * /** Return true if the interior of the interval contains the point 'p'. *#/
  * public boolean interiorContains(double p) {
  * // Works for empty, full, and singleton intervals.
  * // assert (Math.abs(p) <= S2.M_PI);
  * if (p == -S2.M_PI) {
  * p = S2.M_PI;
  * }
  *
  * if (isInverted()) {
  * return p > lo() || p < hi();
  * } else {
  * return (p > lo() && p < hi()) || isFull();
  * }
  * }
  *
  * /**
  * Return true if the interval contains the given interval 'y'. Works for
  * empty, full, and singleton intervals.
  *#/
  * public boolean contains(final S1Interval y) {
  * // It might be helpful to compare the structure of these tests to
  * // the simpler Contains(double) method above.
  *
  * if (isInverted()) {
  * if (y.isInverted()) {
  * return y.lo() >= lo() && y.hi() <= hi();
  * }
  * return (y.lo() >= lo() || y.hi() <= hi()) && !isEmpty();
  * } else {
  * if (y.isInverted()) {
  * return isFull() || y.isEmpty();
  * }
  * return y.lo() >= lo() && y.hi() <= hi();
  * }
  * }
  *
  * /**
  * Returns true if the interior of this interval contains the entire interval
  * 'y'. Note that x.InteriorContains(x) is true only when x is the empty or
  * full interval, and x.InteriorContains(S1Interval(p,p)) is equivalent to
  * x.InteriorContains(p).
  *#/
  * public boolean interiorContains(final S1Interval y) {
  * if (isInverted()) {
  * if (!y.isInverted()) {
  * return y.lo() > lo() || y.hi() < hi();
  * }
  * return (y.lo() > lo() && y.hi() < hi()) || y.isEmpty();
  * } else {
  * if (y.isInverted()) {
  * return isFull() || y.isEmpty();
  * }
  * return (y.lo() > lo() && y.hi() < hi()) || isFull();
  * }
  * }
  *
  * /**
  * Return true if the two intervals contain any points in common. Note that
  * the point +/-Pi has two representations, so the intervals [-Pi,-3] and
  * [2,Pi] intersect, for example.
  */
 public function intersects(S1Interval $y)
 {
     if ($this->isEmpty() || $y->isEmpty()) {
         return false;
     }
     if ($this->isInverted()) {
         // Every non-empty inverted interval contains Pi.
         return $y->isInverted() || $y->lo() <= $this->hi() || $y->hi() >= $this->lo();
     } else {
         if ($y->isInverted()) {
             return $y->lo() <= $this->hi() || $y->hi() >= $this->lo();
         }
         return $y->lo() <= $this->hi() && $y->hi() >= $this->lo();
     }
 }
 /**
  * Return the smallest rectangle containing the intersection of this rectangle
  * and the given rectangle. Note that the region of intersection may consist
  * of two disjoint rectangles, in which case a single rectangle spanning both
  * of them is returned.
  *#/
  * public S2LatLngRect intersection(S2LatLngRect other) {
  * R1Interval intersectLat = lat.intersection(other.lat);
  * S1Interval intersectLng = lng.intersection(other.lng);
  * if (intersectLat.isEmpty() || intersectLng.isEmpty()) {
  * // The lat/lng ranges must either be both empty or both non-empty.
  * return empty();
  * }
  * return new S2LatLngRect(intersectLat, intersectLng);
  * }
  *
  * /**
  * Return a rectangle that contains the convolution of this rectangle with a
  * cap of the given angle. This expands the rectangle by a fixed distance (as
  * opposed to growing the rectangle in latitude-longitude space). The returned
  * rectangle includes all points whose minimum distance to the original
  * rectangle is at most the given angle.
  *#/
  * public S2LatLngRect convolveWithCap(S1Angle angle) {
  * // The most straightforward approach is to build a cap centered on each
  * // vertex and take the union of all the bounding rectangles (including the
  * // original rectangle; this is necessary for very large rectangles).
  *
  * // Optimization: convert the angle to a height exactly once.
  * S2Cap cap = S2Cap.fromAxisAngle(new S2Point(1, 0, 0), angle);
  *
  * S2LatLngRect r = this;
  * for (int k = 0; k < 4; ++k) {
  * S2Cap vertexCap = S2Cap.fromAxisHeight(getVertex(k).toPoint(), cap
  * .height());
  * r = r.union(vertexCap.getRectBound());
  * }
  * return r;
  * }
  *
  * /** Return the surface area of this rectangle on the unit sphere. *#/
  * public double area() {
  * if (isEmpty()) {
  * return 0;
  * }
  *
  * // This is the size difference of the two spherical caps, multiplied by
  * // the longitude ratio.
  * return lng().getLength() * Math.abs(Math.sin(latHi().radians()) - Math.sin(latLo().radians()));
  * }
  *
  * /** Return true if two rectangles contains the same set of points. *#/
  * @Override
  * public boolean equals(Object that) {
  * if (!(that instanceof S2LatLngRect)) {
  * return false;
  * }
  * S2LatLngRect otherRect = (S2LatLngRect) that;
  * return lat().equals(otherRect.lat()) && lng().equals(otherRect.lng());
  * }
  *
  * /**
  * Return true if the latitude and longitude intervals of the two rectangles
  * are the same up to the given tolerance (see r1interval.h and s1interval.h
  * for details).
  *#/
  * public boolean approxEquals(S2LatLngRect other, double maxError) {
  * return (lat.approxEquals(other.lat, maxError) && lng.approxEquals(
  * other.lng, maxError));
  * }
  *
  * public boolean approxEquals(S2LatLngRect other) {
  * return approxEquals(other, 1e-15);
  * }
  *
  * @Override
  * public int hashCode() {
  * int value = 17;
  * value = 37 * value + lat.hashCode();
  * return (37 * value + lng.hashCode());
  * }
  *
  * // //////////////////////////////////////////////////////////////////////
  * // S2Region interface (see {@code S2Region} for details):
  *
  * @Override
  * public S2Region clone() {
  * return new S2LatLngRect(this.lo(), this.hi());
  * }
  */
 public function getCapBound()
 {
     // We consider two possible bounding caps, one whose axis passes
     // through the center of the lat-long rectangle and one whose axis
     // is the north or south pole. We return the smaller of the two caps.
     if ($this->isEmpty()) {
         echo __METHOD__ . " empty\n";
         return S2Cap::sempty();
     }
     $poleZ = null;
     $poleAngle = null;
     if ($this->lat->lo() + $this->lat->hi() < 0) {
         // South pole axis yields smaller cap.
         $poleZ = -1;
         $poleAngle = S2::M_PI_2 + $this->lat->hi();
     } else {
         $poleZ = 1;
         $poleAngle = S2::M_PI_2 - $this->lat->lo();
     }
     $poleCap = S2Cap::fromAxisAngle(new S2Point(0, 0, $poleZ), S1Angle::sradians($poleAngle));
     // For bounding rectangles that span 180 degrees or less in longitude, the
     // maximum cap size is achieved at one of the rectangle vertices. For
     // rectangles that are larger than 180 degrees, we punt and always return a
     // bounding cap centered at one of the two poles.
     $lngSpan = $this->lng->hi() - $this->lng->lo();
     if (S2::IEEEremainder($lngSpan, 2 * S2::M_PI) >= 0) {
         if ($lngSpan < 2 * S2::M_PI) {
             $midCap = S2Cap::fromAxisAngle($this->getCenter()->toPoint(), S1Angle::sradians(0));
             for ($k = 0; $k < 4; ++$k) {
                 $midCap = $midCap->addPoint($this->getVertex($k)->toPoint());
             }
             if ($midCap->height() < $poleCap->height()) {
                 return $midCap;
             }
         }
     }
     return $poleCap;
 }