/** * 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; }
public function getRectBound() { if ($this->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 = $this->uv[0][0] + $this->uv[0][1]; $v = $this->uv[1][0] + $this->uv[1][1]; $i = S2Projections::getUAxis($this->face)->z == 0 ? $u < 0 ? 1 : 0 : ($u > 0 ? 1 : 0); $j = S2Projections::getVAxis($this->face)->z == 0 ? $v < 0 ? 1 : 0 : ($v > 0 ? 1 : 0); $lat = R1Interval::fromPointPair($this->getLatitude($i, $j), $this->getLatitude(1 - $i, 1 - $j)); $lat = $lat->expanded(self::MAX_ERROR)->intersection(S2LatLngRect::fullLat()); if ($lat->lo() == -S2::M_PI_2 || $lat->hi() == S2::M_PI_2) { return new S2LatLngRect($lat, S1Interval::full()); } $lng = S1Interval::fromPointPair($this->getLongitude($i, 1 - $j), $this->getLongitude(1 - $i, $j)); return new S2LatLngRect($lat, $lng->expanded(self::MAX_ERROR)); } // The face centers are the +X, +Y, +Z, -X, -Y, -Z axes in that order. // assert (S2Projections.getNorm(face).get(face % 3) == ((face < 3) ? 1 : -1)); switch ($this->face) { case 0: return new S2LatLngRect(new R1Interval(-S2::M_PI_4, S2::M_PI_4), new S1Interval(-S2::M_PI_4, S2::M_PI_4)); case 1: return new S2LatLngRect(new R1Interval(-S2::M_PI_4, S2::M_PI_4), new S1Interval(S2::M_PI_4, 3 * S2::M_PI_4)); case 2: return new S2LatLngRect(new R1Interval(POLE_MIN_LAT, S2::M_PI_2), new S1Interval(-S2::M_PI, S2::M_PI)); case 3: return new S2LatLngRect(new R1Interval(-S2::M_PI_4, S2::M_PI_4), new S1Interval(3 * S2::M_PI_4, -3 * S2::M_PI_4)); case 4: return new S2LatLngRect(new R1Interval(-S2::M_PI_4, S2::M_PI_4), new S1Interval(-3 * S2::M_PI_4, -S2::M_PI_4)); default: return new S2LatLngRect(new R1Interval(-S2::M_PI_2, -POLE_MIN_LAT), new S1Interval(-S2::M_PI, S2::M_PI)); } }