/** * 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; }
/** * Return the average area for cells at the given level. *#/ * public static double averageArea(int level) { * return S2Projections.AVG_AREA.getValue(level); * } * * /** * Return the average area of cells at this level. This is accurate to within * a factor of 1.7 (for S2_QUADRATIC_PROJECTION) and is extremely cheap to * compute. *#/ * public double averageArea() { * return averageArea(level); * } * * /** * Return the approximate area of this cell. This method is accurate to within * 3% percent for all cell sizes and accurate to within 0.1% for cells at * level 5 or higher (i.e. 300km square or smaller). It is moderately cheap to * compute. *#/ * public double approxArea() { * * // All cells at the first two levels have the same area. * if (level < 2) { * return averageArea(level); * } * * // First, compute the approximate area of the cell when projected * // perpendicular to its normal. The cross product of its diagonals gives * // the normal, and the length of the normal is twice the projected area. * double flatArea = 0.5 * S2Point.crossProd( * S2Point.sub(getVertex(2), getVertex(0)), S2Point.sub(getVertex(3), getVertex(1))).norm(); * * // Now, compensate for the curvature of the cell surface by pretending * // that the cell is shaped like a spherical cap. The ratio of the * // area of a spherical cap to the area of its projected disc turns out * // to be 2 / (1 + sqrt(1 - r*r)) where "r" is the radius of the disc. * // For example, when r=0 the ratio is 1, and when r=1 the ratio is 2. * // Here we set Pi*r*r == flat_area to find the equivalent disc. * return flatArea * 2 / (1 + Math.sqrt(1 - Math.min(S2.M_1_PI * flatArea, 1.0))); * } * * /** * Return the area of this cell as accurately as possible. This method is more * expensive but it is accurate to 6 digits of precision even for leaf cells * (whose area is approximately 1e-18). *#/ * public double exactArea() { * S2Point v0 = getVertex(0); * S2Point v1 = getVertex(1); * S2Point v2 = getVertex(2); * S2Point v3 = getVertex(3); * return S2.area(v0, v1, v2) + S2.area(v0, v2, v3); * } * * // ////////////////////////////////////////////////////////////////////// * // S2Region interface (see {@code S2Region} for details): * * @Override * public S2Region clone() { * S2Cell clone = new S2Cell(); * clone.face = this.face; * clone.level = this.level; * clone.orientation = this.orientation; * clone.uv = this.uv.clone(); * * return clone; * } */ public function getCapBound() { // Use the cell center in (u,v)-space as the cap axis. This vector is // very close to GetCenter() and faster to compute. Neither one of these // vectors yields the bounding cap with minimal surface area, but they // are both pretty close. // // It's possible to show that the two vertices that are furthest from // the (u,v)-origin never determine the maximum cap size (this is a // possible future optimization). $u = 0.5 * ($this->uv[0][0] + $this->uv[0][1]); $v = 0.5 * ($this->uv[1][0] + $this->uv[1][1]); $cap = S2Cap::fromAxisHeight(S2Point::normalize(S2Projections::faceUvToXyz($this->face, $u, $v)), 0); for ($k = 0; $k < 4; ++$k) { $cap = $cap->addPoint($this->getVertex($k)); } return $cap; }