vertices) {
50 | // All vertices must be unit length.
51 | int n = vertices.size();
52 | for (int i = 0; i < n; ++i) {
53 | if (!S2.isUnitLength(vertices.get(i))) {
54 | log.info("Vertex " + i + " is not unit length");
55 | return false;
56 | }
57 | }
58 |
59 | // Adjacent vertices must not be identical or antipodal.
60 | for (int i = 1; i < n; ++i) {
61 | if (vertices.get(i - 1).equals(vertices.get(i))
62 | || vertices.get(i - 1).equals(S2Point.neg(vertices.get(i)))) {
63 | log.info("Vertices " + (i - 1) + " and " + i + " are identical or antipodal");
64 | return false;
65 | }
66 | }
67 |
68 | return true;
69 | }
70 |
71 | public int numVertices() {
72 | return numVertices;
73 | }
74 |
75 | public S2Point vertex(int k) {
76 | // assert (k >= 0 && k < numVertices);
77 | return vertices[k];
78 | }
79 |
80 | /**
81 | * Return the angle corresponding to the total arclength of the polyline on a
82 | * unit sphere.
83 | *#/
84 | public S1Angle getArclengthAngle() {
85 | double lengthSum = 0;
86 | for (int i = 1; i < numVertices(); ++i) {
87 | lengthSum += vertex(i - 1).angle(vertex(i));
88 | }
89 | return S1Angle.radians(lengthSum);
90 | }
91 |
92 | /**
93 | * Return the point whose distance from vertex 0 along the polyline is the
94 | * given fraction of the polyline's total length. Fractions less than zero or
95 | * greater than one are clamped. The return value is unit length. This cost of
96 | * this function is currently linear in the number of vertices.
97 | *#/
98 | public S2Point interpolate(double fraction) {
99 | // We intentionally let the (fraction >= 1) case fall through, since
100 | // we need to handle it in the loop below in any case because of
101 | // possible roundoff errors.
102 | if (fraction <= 0) {
103 | return vertex(0);
104 | }
105 |
106 | double lengthSum = 0;
107 | for (int i = 1; i < numVertices(); ++i) {
108 | lengthSum += vertex(i - 1).angle(vertex(i));
109 | }
110 | double target = fraction * lengthSum;
111 | for (int i = 1; i < numVertices(); ++i) {
112 | double length = vertex(i - 1).angle(vertex(i));
113 | if (target < length) {
114 | // This code interpolates with respect to arc length rather than
115 | // straight-line distance, and produces a unit-length result.
116 | double f = Math.sin(target) / Math.sin(length);
117 | return S2Point.add(S2Point.mul(vertex(i - 1), (Math.cos(target) - f * Math.cos(length))),
118 | S2Point.mul(vertex(i), f));
119 | }
120 | target -= length;
121 | }
122 | return vertex(numVertices() - 1);
123 | }
124 |
125 | // S2Region interface (see {@code S2Region} for details):
126 |
127 | /** Return a bounding spherical cap. *#/
128 | @Override
129 | public S2Cap getCapBound() {
130 | return getRectBound().getCapBound();
131 | }
132 |
133 |
134 | /** Return a bounding latitude-longitude rectangle. *#/
135 | @Override
136 | public S2LatLngRect getRectBound() {
137 | S2EdgeUtil.RectBounder bounder = new S2EdgeUtil.RectBounder();
138 | for (int i = 0; i < numVertices(); ++i) {
139 | bounder.addPoint(vertex(i));
140 | }
141 | return bounder.getBound();
142 | }
143 |
144 | /**
145 | * If this method returns true, the region completely contains the given cell.
146 | * Otherwise, either the region does not contain the cell or the containment
147 | * relationship could not be determined.
148 | *#/
149 | @Override
150 | public boolean contains(S2Cell cell) {
151 | throw new UnsupportedOperationException(
152 | "'containment' is not numerically well-defined " + "except at the polyline vertices");
153 | }
154 |
155 | /**
156 | * If this method returns false, the region does not intersect the given cell.
157 | * Otherwise, either region intersects the cell, or the intersection
158 | * relationship could not be determined.
159 | *#/
160 | @Override
161 | public boolean mayIntersect(S2Cell cell) {
162 | if (numVertices() == 0) {
163 | return false;
164 | }
165 |
166 | // We only need to check whether the cell contains vertex 0 for correctness,
167 | // but these tests are cheap compared to edge crossings so we might as well
168 | // check all the vertices.
169 | for (int i = 0; i < numVertices(); ++i) {
170 | if (cell.contains(vertex(i))) {
171 | return true;
172 | }
173 | }
174 | S2Point[] cellVertices = new S2Point[4];
175 | for (int i = 0; i < 4; ++i) {
176 | cellVertices[i] = cell.getVertex(i);
177 | }
178 | for (int j = 0; j < 4; ++j) {
179 | S2EdgeUtil.EdgeCrosser crosser =
180 | new S2EdgeUtil.EdgeCrosser(cellVertices[j], cellVertices[(j + 1) & 3], vertex(0));
181 | for (int i = 1; i < numVertices(); ++i) {
182 | if (crosser.robustCrossing(vertex(i)) >= 0) {
183 | // There is a proper crossing, or two vertices were the same.
184 | return true;
185 | }
186 | }
187 | }
188 | return false;
189 | }
190 |
191 | /**
192 | * Given a point, returns the index of the start point of the (first) edge on
193 | * the polyline that is closest to the given point. The polyline must have at
194 | * least one vertex. Throws IllegalStateException if this is not the case.
195 | *#/
196 | public int getNearestEdgeIndex(S2Point point) {
197 | Preconditions.checkState(numVertices() > 0, "Empty polyline");
198 |
199 | if (numVertices() == 1) {
200 | // If there is only one vertex, the "edge" is trivial, and it's the only one
201 | return 0;
202 | }
203 |
204 | // Initial value larger than any possible distance on the unit sphere.
205 | S1Angle minDistance = S1Angle.radians(10);
206 | int minIndex = -1;
207 |
208 | // Find the line segment in the polyline that is closest to the point given.
209 | for (int i = 0; i < numVertices() - 1; ++i) {
210 | S1Angle distanceToSegment = S2EdgeUtil.getDistance(point, vertex(i), vertex(i + 1));
211 | if (distanceToSegment.lessThan(minDistance)) {
212 | minDistance = distanceToSegment;
213 | minIndex = i;
214 | }
215 | }
216 | return minIndex;
217 | }
218 |
219 | /**
220 | * Given a point p and the index of the start point of an edge of this polyline,
221 | * returns the point on that edge that is closest to p.
222 | *#/
223 | public S2Point projectToEdge(S2Point point, int index) {
224 | Preconditions.checkState(numVertices() > 0, "Empty polyline");
225 | Preconditions.checkState(numVertices() == 1 || index < numVertices() - 1, "Invalid edge index");
226 | if (numVertices() == 1) {
227 | // If there is only one vertex, it is always closest to any given point.
228 | return vertex(0);
229 | }
230 | return S2EdgeUtil.getClosestPoint(point, vertex(index), vertex(index + 1));
231 | }
232 |
233 | @Override
234 | public boolean equals(Object that) {
235 | if (!(that instanceof S2Polyline)) {
236 | return false;
237 | }
238 |
239 | S2Polyline thatPolygon = (S2Polyline) that;
240 | if (numVertices != thatPolygon.numVertices) {
241 | return false;
242 | }
243 |
244 | for (int i = 0; i < vertices.length; i++) {
245 | if (!vertices[i].equals(thatPolygon.vertices[i])) {
246 | return false;
247 | }
248 | }
249 | return true;
250 | }
251 |
252 | @Override
253 | public int hashCode() {
254 | return Objects.hashCode(numVertices, Arrays.deepHashCode(vertices));
255 | }
256 | */
257 | }
258 |
--------------------------------------------------------------------------------
/S2LatLng.php:
--------------------------------------------------------------------------------
1 | get(2),
47 | sqrt($p->get(0) * $p->get(0) + $p->get(1) * $p->get(1))
48 | )
49 | );
50 | }
51 |
52 | public static function longitude(S2Point $p) {
53 | // Note that atan2(0, 0) is defined to be zero.
54 | return S1Angle::sradians(atan2($p->get(1), $p->get(0)));
55 | }
56 |
57 | /**
58 | * This is internal to avoid ambiguity about which units are expected.
59 | * @param double|S1Angle $latRadians
60 | * @param double|S1Angle $lngRadians
61 | */
62 | public function __construct($latRadians = null, $lngRadians = null) {
63 | if ($latRadians instanceof S1Angle && $lngRadians instanceof S1Angle) {
64 | $this->latRadians = $latRadians->radians();
65 | $this->lngRadians = $lngRadians->radians();
66 | } else if ($lngRadians === null && $latRadians instanceof S2Point) {
67 | $this->latRadians = atan2($latRadians->z, sqrt($latRadians->x * $latRadians->x + $latRadians->y * $latRadians->y));
68 | $this->lngRadians = atan2($latRadians->y, $latRadians->x);
69 | } else if ($latRadians === null && $lngRadians === null) {
70 | $this->latRadians = 0;
71 | $this->lngRadians = 0;
72 | } else {
73 | $this->latRadians = $latRadians;
74 | $this->lngRadians = $lngRadians;
75 | }
76 | }
77 |
78 | /** Returns the latitude of this point as a new S1Angle. */
79 | public function lat() {
80 | return S1Angle::sradians($this->latRadians);
81 | }
82 |
83 | /** Returns the latitude of this point as radians. */
84 | public function latRadians() {
85 | return $this->latRadians;
86 | }
87 |
88 | /** Returns the latitude of this point as degrees. */
89 | public function latDegrees() {
90 | return 180.0 / M_PI * $this->latRadians;
91 | }
92 |
93 | /** Returns the longitude of this point as a new S1Angle. */
94 | public function lng() {
95 | return S1Angle::sradians($this->lngRadians);
96 | }
97 |
98 | /** Returns the longitude of this point as radians. */
99 | public function lngRadians() {
100 | return $this->lngRadians;
101 | }
102 |
103 | /** Returns the longitude of this point as degrees. */
104 | public function lngDegrees() {
105 | return 180.0 / M_PI * $this->lngRadians;
106 | }
107 |
108 | /**
109 | * Return true if the latitude is between -90 and 90 degrees inclusive and the
110 | * longitude is between -180 and 180 degrees inclusive.
111 | *#/
112 | * public boolean isValid() {
113 | * return Math.abs(lat().radians()) <= S2.M_PI_2 && Math.abs(lng().radians()) <= S2.M_PI;
114 | * }
115 | *
116 | * /**
117 | * Returns a new S2LatLng based on this instance for which {@link #isValid()}
118 | * will be {@code true}.
119 | *
120 | * - Latitude is clipped to the range {@code [-90, 90]}
121 | *
- Longitude is normalized to be in the range {@code [-180, 180]}
122 | *
123 | * If the current point is valid then the returned point will have the same
124 | * coordinates.
125 | *#/
126 | * public S2LatLng normalized() {
127 | * // drem(x, 2 * S2.M_PI) reduces its argument to the range
128 | * // [-S2.M_PI, S2.M_PI] inclusive, which is what we want here.
129 | * return new S2LatLng(Math.max(-S2.M_PI_2, Math.min(S2.M_PI_2, lat().radians())),
130 | * Math.IEEEremainder(lng().radians(), 2 * S2.M_PI));
131 | * }
132 | *
133 | * // Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts
134 | * // a multiple of 360 degrees to the longitude if necessary to reduce it to
135 | * // the range [-180, 180].
136 | *
137 | * /** Convert an S2LatLng to the equivalent unit-length vector (S2Point). */
138 | public function toPoint() {
139 | $phi = $this->lat()->radians();
140 | $theta = $this->lng()->radians();
141 | $cosphi = cos($phi);
142 | return new S2Point(cos($theta) * $cosphi, sin($theta) * $cosphi, sin($phi));
143 | }
144 |
145 | /**
146 | * Return the distance (measured along the surface of the sphere) to the given
147 | * point.
148 | *#/
149 | * public S1Angle getDistance(final S2LatLng o) {
150 | * // This implements the Haversine formula, which is numerically stable for
151 | * // small distances but only gets about 8 digits of precision for very large
152 | * // distances (e.g. antipodal points). Note that 8 digits is still accurate
153 | * // to within about 10cm for a sphere the size of the Earth.
154 | * //
155 | * // This could be fixed with another sin() and cos() below, but at that point
156 | * // you might as well just convert both arguments to S2Points and compute the
157 | * // distance that way (which gives about 15 digits of accuracy for all
158 | * // distances).
159 | *
160 | * double lat1 = lat().radians();
161 | * double lat2 = o.lat().radians();
162 | * double lng1 = lng().radians();
163 | * double lng2 = o.lng().radians();
164 | * double dlat = Math.sin(0.5 * (lat2 - lat1));
165 | * double dlng = Math.sin(0.5 * (lng2 - lng1));
166 | * double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
167 | * return S1Angle.radians(2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0, 1.0 - x))));
168 | * // Return the distance (measured along the surface of the sphere) to the
169 | * // given S2LatLng. This is mathematically equivalent to:
170 | * //
171 | * // S1Angle::FromRadians(ToPoint().Angle(o.ToPoint())
172 | * //
173 | * // but this implementation is slightly more efficient.
174 | * }
175 | *
176 | * /**
177 | * Returns the surface distance to the given point assuming a constant radius.
178 | *#/
179 | * public double getDistance(final S2LatLng o, double radius) {
180 | * // TODO(dbeaumont): Maybe check that radius >= 0 ?
181 | * return getDistance(o).radians() * radius;
182 | * }
183 | *
184 | * /**
185 | * Returns the surface distance to the given point assuming the default Earth
186 | * radius of {@link #EARTH_RADIUS_METERS}.
187 | *#/
188 | * public double getEarthDistance(final S2LatLng o) {
189 | * return getDistance(o, EARTH_RADIUS_METERS);
190 | * }
191 | *
192 | * /**
193 | * Adds the given point to this point.
194 | * Note that there is no guarantee that the new point will be valid.
195 | *#/
196 | * public S2LatLng add(final S2LatLng o) {
197 | * return new S2LatLng(latRadians + o.latRadians, lngRadians + o.lngRadians);
198 | * }
199 | *
200 | * /**
201 | * Subtracts the given point from this point.
202 | * Note that there is no guarantee that the new point will be valid.
203 | *#/
204 | * public S2LatLng sub(final S2LatLng o) {
205 | * return new S2LatLng(latRadians - o.latRadians, lngRadians - o.lngRadians);
206 | * }
207 | *
208 | * /**
209 | * Scales this point by the given scaling factor.
210 | * Note that there is no guarantee that the new point will be valid.
211 | */
212 | public function mul($m) {
213 | // TODO(dbeaumont): Maybe check that m >= 0 ?
214 | return new S2LatLng($this->latRadians * $m, $this->lngRadians * $m);
215 | }
216 |
217 | /*
218 | @Override
219 | public boolean equals(Object that) {
220 | if (that instanceof S2LatLng) {
221 | S2LatLng o = (S2LatLng) that;
222 | return (latRadians == o.latRadians) && (lngRadians == o.lngRadians);
223 | }
224 | return false;
225 | }
226 |
227 | @Override
228 | public int hashCode() {
229 | long value = 17;
230 | value += 37 * value + Double.doubleToLongBits(latRadians);
231 | value += 37 * value + Double.doubleToLongBits(lngRadians);
232 | return (int) (value ^ (value >>> 32));
233 | }
234 |
235 | /**
236 | * Returns true if both the latitude and longitude of the given point are
237 | * within {@code maxError} radians of this point.
238 | *#/
239 | public boolean approxEquals(S2LatLng o, double maxError) {
240 | return (Math.abs(latRadians - o.latRadians) < maxError)
241 | && (Math.abs(lngRadians - o.lngRadians) < maxError);
242 | }
243 |
244 | /#**
245 | * Returns true if the given point is within {@code 1e-9} radians of this
246 | * point. This corresponds to a distance of less than {@code 1cm} at the
247 | * surface of the Earth.
248 | *#/
249 | public boolean approxEquals(S2LatLng o) {
250 | return approxEquals(o, 1e-9);
251 | }
252 | */
253 | public function __toString() {
254 | return "(" . $this->latRadians . ", " . $this->lngRadians . ")";
255 | }
256 |
257 | public function toStringDegrees() {
258 | return "(" . $this->latDegrees() . ", " . $this->lngDegrees() . ")";
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/S2Projections.php:
--------------------------------------------------------------------------------
1 | = 0) {
175 | return (1 / 3.) * ((1 + $s) * (1 + $s) - 1);
176 | } else {
177 | return (1 / 3.) * (1 - (1 - $s) * (1 - $s));
178 | }
179 | default:
180 | throw new IllegalStateException("Invalid value for S2_PROJECTION");
181 | }
182 | }
183 |
184 | public static function uvToST($u) {
185 | switch (self::S2_PROJECTION) {
186 | case self::S2_LINEAR_PROJECTION:
187 | return $u;
188 |
189 | case self::S2_TAN_PROJECTION:
190 | return (4 * S2::M_1_PI) * atan($u);
191 |
192 | case self::S2_QUADRATIC_PROJECTION:
193 | if ($u >= 0) {
194 | return sqrt(1 + 3 * $u) - 1;
195 | } else {
196 | return 1 - sqrt(1 - 3 * $u);
197 | }
198 | default:
199 | throw new IllegalStateException("Invalid value for S2_PROJECTION");
200 | }
201 | }
202 |
203 | /**
204 | * Convert (face, u, v) coordinates to a direction vector (not necessarily
205 | * unit length).
206 | */
207 | public static function faceUvToXyz($face, $u, $v) {
208 | switch ($face) {
209 | case 0:
210 | return new S2Point(1, $u, $v);
211 |
212 | case 1:
213 | return new S2Point(-$u, 1, $v);
214 |
215 | case 2:
216 | return new S2Point(-$u, -$v, 1);
217 |
218 | case 3:
219 | return new S2Point(-1, -$v, -$u);
220 |
221 | case 4:
222 | return new S2Point($v, -1, -$u);
223 |
224 | default:
225 | return new S2Point($v, $u, -1);
226 | }
227 | }
228 |
229 | public static function validFaceXyzToUv($face, S2Point $p) {
230 | // assert (p.dotProd(faceUvToXyz(face, 0, 0)) > 0);
231 | switch ($face) {
232 | case 0:
233 | $pu = $p->y / $p->x;
234 | $pv = $p->z / $p->x;
235 | break;
236 |
237 | case 1:
238 | $pu = -$p->x / $p->y;
239 | $pv = $p->z / $p->y;
240 | break;
241 |
242 | case 2:
243 | $pu = -$p->x / $p->z;
244 | $pv = -$p->y / $p->z;
245 | break;
246 |
247 | case 3:
248 | $pu = $p->z / $p->x;
249 | $pv = $p->y / $p->x;
250 | break;
251 |
252 | case 4:
253 | $pu = $p->z / $p->y;
254 | $pv = -$p->x / $p->y;
255 | break;
256 |
257 | default:
258 | $pu = -$p->y / $p->z;
259 | $pv = -$p->x / $p->z;
260 | break;
261 | }
262 | return new R2Vector($pu, $pv);
263 | }
264 |
265 | public static function xyzToFace(S2Point $p) {
266 | $face = $p->largestAbsComponent();
267 | if ($p->get($face) < 0) {
268 | $face += 3;
269 | }
270 | return $face;
271 | }
272 |
273 | /*
274 | public static R2Vector faceXyzToUv(int face, S2Point p) {
275 | if (face < 3) {
276 | if (p.get(face) <= 0) {
277 | return null;
278 | }
279 | } else {
280 | if (p.get(face - 3) >= 0) {
281 | return null;
282 | }
283 | }
284 | return validFaceXyzToUv(face, p);
285 | }
286 |
287 | public static S2Point getUNorm(int face, double u) {
288 | switch (face) {
289 | case 0:
290 | return new S2Point(u, -1, 0);
291 | case 1:
292 | return new S2Point(1, u, 0);
293 | case 2:
294 | return new S2Point(1, 0, u);
295 | case 3:
296 | return new S2Point(-u, 0, 1);
297 | case 4:
298 | return new S2Point(0, -u, 1);
299 | default:
300 | return new S2Point(0, -1, -u);
301 | }
302 | }
303 |
304 | public static S2Point getVNorm(int face, double v) {
305 | switch (face) {
306 | case 0:
307 | return new S2Point(-v, 0, 1);
308 | case 1:
309 | return new S2Point(0, -v, 1);
310 | case 2:
311 | return new S2Point(0, -1, -v);
312 | case 3:
313 | return new S2Point(v, -1, 0);
314 | case 4:
315 | return new S2Point(1, v, 0);
316 | default:
317 | return new S2Point(1, 0, v);
318 | }
319 | }
320 |
321 | public static S2Point getNorm(int face) {
322 | return faceUvToXyz(face, 0, 0);
323 | }
324 | */
325 | public static function getUAxis($face) {
326 | switch ($face) {
327 | case 0:
328 | return new S2Point(0, 1, 0);
329 |
330 | case 1:
331 | return new S2Point(-1, 0, 0);
332 |
333 | case 2:
334 | return new S2Point(-1, 0, 0);
335 |
336 | case 3:
337 | return new S2Point(0, 0, -1);
338 |
339 | case 4:
340 | return new S2Point(0, 0, -1);
341 |
342 | default:
343 | return new S2Point(0, 1, 0);
344 | }
345 | }
346 |
347 | public static function getVAxis($face) {
348 | switch ($face) {
349 | case 0:
350 | return new S2Point(0, 0, 1);
351 |
352 | case 1:
353 | return new S2Point(0, 0, 1);
354 |
355 | case 2:
356 | return new S2Point(0, -1, 0);
357 |
358 | case 3:
359 | return new S2Point(0, -1, 0);
360 |
361 | case 4:
362 | return new S2Point(1, 0, 0);
363 |
364 | default:
365 | return new S2Point(1, 0, 0);
366 | }
367 | }
368 | }
369 |
--------------------------------------------------------------------------------
/S1Interval.php:
--------------------------------------------------------------------------------
1 | lo = $lo->lo;
14 | $this->hi = $lo->hi;
15 | } else {
16 | $newLo = $lo;
17 | $newHi = $hi;
18 | if (!$checked) {
19 | if ($lo == -S2::M_PI && $hi != S2::M_PI) {
20 | $newLo = S2::M_PI;
21 | }
22 | if ($hi == -S2::M_PI && $lo != S2::M_PI) {
23 | $newHi = S2::M_PI;
24 | }
25 | }
26 | $this->lo = $newLo;
27 | $this->hi = $newHi;
28 | }
29 | }
30 |
31 | public static function emptya() {
32 | return new S1Interval(S2::M_PI, -S2::M_PI, true);
33 | }
34 |
35 | public static function full() {
36 | return new S1Interval(-S2::M_PI, S2::M_PI, true);
37 | }
38 |
39 | /** Convenience method to construct an interval containing a single point. *#/
40 | * public static S1Interval fromPoint(double p) {
41 | * if (p == -S2.M_PI) {
42 | * p = S2.M_PI;
43 | * }
44 | * return new S1Interval(p, p, true);
45 | * }
46 | *
47 | * /**
48 | * Convenience method to construct the minimal interval containing the two
49 | * given points. This is equivalent to starting with an empty interval and
50 | * calling AddPoint() twice, but it is more efficient.
51 | */
52 | public static function fromPointPair($p1, $p2) {
53 | // assert (Math.abs(p1) <= S2.M_PI && Math.abs(p2) <= S2.M_PI);
54 | if ($p1 == -S2::M_PI) {
55 | $p1 = S2::M_PI;
56 | }
57 | if ($p2 == -S2::M_PI) {
58 | $p2 = S2::M_PI;
59 | }
60 | if (self::positiveDistance($p1, $p2) <= S2::M_PI) {
61 | return new S1Interval($p1, $p2, true);
62 | } else {
63 | return new S1Interval($p2, $p1, true);
64 | }
65 | }
66 |
67 | public function lo() {
68 | return $this->lo;
69 | }
70 |
71 | public function hi() {
72 | return $this->hi;
73 | }
74 |
75 | /**
76 | * An interval is valid if neither bound exceeds Pi in absolute value, and the
77 | * value -Pi appears only in the Empty() and Full() intervals.
78 | *#/
79 | * public boolean isValid() {
80 | * return (Math.abs(lo()) <= S2.M_PI && Math.abs(hi()) <= S2.M_PI
81 | * && !(lo() == -S2.M_PI && hi() != S2.M_PI) && !(hi() == -S2.M_PI && lo() != S2.M_PI));
82 | * }
83 | *
84 | * /** Return true if the interval contains all points on the unit circle. *#/
85 | * public boolean isFull() {
86 | * return hi() - lo() == 2 * S2.M_PI;
87 | * }
88 | *
89 | *
90 | * /** Return true if the interval is empty, i.e. it contains no points. */
91 | public function isEmpty() {
92 | return $this->lo() - $this->hi() == 2 * S2::M_PI;
93 | }
94 |
95 | /* Return true if lo() > hi(). (This is true for empty intervals.) */
96 | public function isInverted() {
97 | return $this->lo() > $this->hi();
98 | }
99 |
100 | /**
101 | * Return the midpoint of the interval. For full and empty intervals, the
102 | * result is arbitrary.
103 | */
104 | public function getCenter() {
105 | $center = 0.5 * ($this->lo() + $this->hi());
106 | if (!$this->isInverted()) {
107 | return $center;
108 | }
109 | // Return the center in the range (-Pi, Pi].
110 | return ($center <= 0) ? ($center + S2::M_PI) : ($center - S2::M_PI);
111 | }
112 |
113 | /**
114 | * Return the length of the interval. The length of an empty interval is
115 | * negative.
116 | */
117 | public function getLength() {
118 | $length = $this->hi() - $this->lo();
119 | if ($length >= 0) {
120 | return $length;
121 | }
122 | $length += 2 * S2::M_PI;
123 | // Empty intervals have a negative length.
124 | return ($length > 0) ? $length : -1;
125 | }
126 |
127 | /**
128 | * Return the complement of the interior of the interval. An interval and its
129 | * complement have the same boundary but do not share any interior values. The
130 | * complement operator is not a bijection, since the complement of a singleton
131 | * interval (containing a single value) is the same as the complement of an
132 | * empty interval.
133 | *#/
134 | * public S1Interval complement() {
135 | * if (lo() == hi()) {
136 | * return full(); // Singleton.
137 | * }
138 | * return new S1Interval(hi(), lo(), true); // Handles
139 | * // empty and
140 | * // full.
141 | * }
142 | *
143 | * /** Return true if the interval (which is closed) contains the point 'p'. *#/
144 | * public boolean contains(double p) {
145 | * // Works for empty, full, and singleton intervals.
146 | * // assert (Math.abs(p) <= S2.M_PI);
147 | * if (p == -S2.M_PI) {
148 | * p = S2.M_PI;
149 | * }
150 | * return fastContains(p);
151 | * }
152 | *
153 | * /**
154 | * Return true if the interval (which is closed) contains the point 'p'. Skips
155 | * the normalization of 'p' from -Pi to Pi.
156 | *
157 | *#/
158 | * public boolean fastContains(double p) {
159 | * if (isInverted()) {
160 | * return (p >= lo() || p <= hi()) && !isEmpty();
161 | * } else {
162 | * return p >= lo() && p <= hi();
163 | * }
164 | * }
165 | *
166 | * /** Return true if the interior of the interval contains the point 'p'. *#/
167 | * public boolean interiorContains(double p) {
168 | * // Works for empty, full, and singleton intervals.
169 | * // assert (Math.abs(p) <= S2.M_PI);
170 | * if (p == -S2.M_PI) {
171 | * p = S2.M_PI;
172 | * }
173 | *
174 | * if (isInverted()) {
175 | * return p > lo() || p < hi();
176 | * } else {
177 | * return (p > lo() && p < hi()) || isFull();
178 | * }
179 | * }
180 | *
181 | * /**
182 | * Return true if the interval contains the given interval 'y'. Works for
183 | * empty, full, and singleton intervals.
184 | *#/
185 | * public boolean contains(final S1Interval y) {
186 | * // It might be helpful to compare the structure of these tests to
187 | * // the simpler Contains(double) method above.
188 | *
189 | * if (isInverted()) {
190 | * if (y.isInverted()) {
191 | * return y.lo() >= lo() && y.hi() <= hi();
192 | * }
193 | * return (y.lo() >= lo() || y.hi() <= hi()) && !isEmpty();
194 | * } else {
195 | * if (y.isInverted()) {
196 | * return isFull() || y.isEmpty();
197 | * }
198 | * return y.lo() >= lo() && y.hi() <= hi();
199 | * }
200 | * }
201 | *
202 | * /**
203 | * Returns true if the interior of this interval contains the entire interval
204 | * 'y'. Note that x.InteriorContains(x) is true only when x is the empty or
205 | * full interval, and x.InteriorContains(S1Interval(p,p)) is equivalent to
206 | * x.InteriorContains(p).
207 | *#/
208 | * public boolean interiorContains(final S1Interval y) {
209 | * if (isInverted()) {
210 | * if (!y.isInverted()) {
211 | * return y.lo() > lo() || y.hi() < hi();
212 | * }
213 | * return (y.lo() > lo() && y.hi() < hi()) || y.isEmpty();
214 | * } else {
215 | * if (y.isInverted()) {
216 | * return isFull() || y.isEmpty();
217 | * }
218 | * return (y.lo() > lo() && y.hi() < hi()) || isFull();
219 | * }
220 | * }
221 | *
222 | * /**
223 | * Return true if the two intervals contain any points in common. Note that
224 | * the point +/-Pi has two representations, so the intervals [-Pi,-3] and
225 | * [2,Pi] intersect, for example.
226 | */
227 | public function intersects(S1Interval $y) {
228 | if ($this->isEmpty() || $y->isEmpty()) {
229 | return false;
230 | }
231 | if ($this->isInverted()) {
232 | // Every non-empty inverted interval contains Pi.
233 | return $y->isInverted() || $y->lo() <= $this->hi() || $y->hi() >= $this->lo();
234 | } else {
235 | if ($y->isInverted()) {
236 | return $y->lo() <= $this->hi() || $y->hi() >= $this->lo();
237 | }
238 | return $y->lo() <= $this->hi() && $y->hi() >= $this->lo();
239 | }
240 | }
241 |
242 | /**
243 | * Return true if the interior of this interval contains any point of the
244 | * interval 'y' (including its boundary). Works for empty, full, and singleton
245 | * intervals.
246 | *#/
247 | * public boolean interiorIntersects(final S1Interval y) {
248 | * if (isEmpty() || y.isEmpty() || lo() == hi()) {
249 | * return false;
250 | * }
251 | * if (isInverted()) {
252 | * return y.isInverted() || y.lo() < hi() || y.hi() > lo();
253 | * } else {
254 | * if (y.isInverted()) {
255 | * return y.lo() < hi() || y.hi() > lo();
256 | * }
257 | * return (y.lo() < hi() && y.hi() > lo()) || isFull();
258 | * }
259 | * }
260 | *
261 | * /**
262 | * Expand the interval by the minimum amount necessary so that it contains the
263 | * given point "p" (an angle in the range [-Pi, Pi]).
264 | *#/
265 | * public S1Interval addPoint(double p) {
266 | * // assert (Math.abs(p) <= S2.M_PI);
267 | * if (p == -S2.M_PI) {
268 | * p = S2.M_PI;
269 | * }
270 | *
271 | * if (fastContains(p)) {
272 | * return new S1Interval(this);
273 | * }
274 | *
275 | * if (isEmpty()) {
276 | * return S1Interval.fromPoint(p);
277 | * } else {
278 | * // Compute distance from p to each endpoint.
279 | * double dlo = positiveDistance(p, lo());
280 | * double dhi = positiveDistance(hi(), p);
281 | * if (dlo < dhi) {
282 | * return new S1Interval(p, hi());
283 | * } else {
284 | * return new S1Interval(lo(), p);
285 | * }
286 | * // Adding a point can never turn a non-full interval into a full one.
287 | * }
288 | * }
289 | *
290 | * /**
291 | * Return an interval that contains all points within a distance "radius" of
292 | * a point in this interval. Note that the expansion of an empty interval is
293 | * always empty. The radius must be non-negative.
294 | */
295 | public function expanded($radius) {
296 | // assert (radius >= 0);
297 | if ($this->isEmpty()) {
298 | return this;
299 | }
300 |
301 | // Check whether this interval will be full after expansion, allowing
302 | // for a 1-bit rounding error when computing each endpoint.
303 | if ($this->getLength() + 2 * $radius >= 2 * S2::M_PI - 1e-15) {
304 | return self::full();
305 | }
306 |
307 | // NOTE(dbeaumont): Should this remainder be 2 * M_PI or just M_PI ??
308 | // double lo = Math.IEEEremainder(lo() - radius, 2 * S2.M_PI);
309 | $lo = S2::IEEEremainder($this->lo() - $radius, 2 * S2::M_PI);
310 | $hi = S2::IEEEremainder($this->hi() + $radius, 2 * S2::M_PI);
311 | if ($lo == -S2::M_PI) {
312 | $lo = S2::M_PI;
313 | }
314 | return new S1Interval($lo, $hi);
315 | }
316 |
317 | /**
318 | * Return the smallest interval that contains this interval and the given
319 | * interval "y".
320 | *#/
321 | * public S1Interval union(final S1Interval y) {
322 | * // The y.is_full() case is handled correctly in all cases by the code
323 | * // below, but can follow three separate code paths depending on whether
324 | * // this interval is inverted, is non-inverted but contains Pi, or neither.
325 | *
326 | * if (y.isEmpty()) {
327 | * return this;
328 | * }
329 | * if (fastContains(y.lo())) {
330 | * if (fastContains(y.hi())) {
331 | * // Either this interval contains y, or the union of the two
332 | * // intervals is the Full() interval.
333 | * if (contains(y)) {
334 | * return this; // is_full() code path
335 | * }
336 | * return full();
337 | * }
338 | * return new S1Interval(lo(), y.hi(), true);
339 | * }
340 | * if (fastContains(y.hi())) {
341 | * return new S1Interval(y.lo(), hi(), true);
342 | * }
343 | *
344 | * // This interval contains neither endpoint of y. This means that either y
345 | * // contains all of this interval, or the two intervals are disjoint.
346 | * if (isEmpty() || y.fastContains(lo())) {
347 | * return y;
348 | * }
349 | *
350 | * // Check which pair of endpoints are closer together.
351 | * double dlo = positiveDistance(y.hi(), lo());
352 | * double dhi = positiveDistance(hi(), y.lo());
353 | * if (dlo < dhi) {
354 | * return new S1Interval(y.lo(), hi(), true);
355 | * } else {
356 | * return new S1Interval(lo(), y.hi(), true);
357 | * }
358 | * }
359 | *
360 | * /**
361 | * Return the smallest interval that contains the intersection of this
362 | * interval with "y". Note that the region of intersection may consist of two
363 | * disjoint intervals.
364 | *#/
365 | * public S1Interval intersection(final S1Interval y) {
366 | * // The y.is_full() case is handled correctly in all cases by the code
367 | * // below, but can follow three separate code paths depending on whether
368 | * // this interval is inverted, is non-inverted but contains Pi, or neither.
369 | *
370 | * if (y.isEmpty()) {
371 | * return empty();
372 | * }
373 | * if (fastContains(y.lo())) {
374 | * if (fastContains(y.hi())) {
375 | * // Either this interval contains y, or the region of intersection
376 | * // consists of two disjoint subintervals. In either case, we want
377 | * // to return the shorter of the two original intervals.
378 | * if (y.getLength() < getLength()) {
379 | * return y; // is_full() code path
380 | * }
381 | * return this;
382 | * }
383 | * return new S1Interval(y.lo(), hi(), true);
384 | * }
385 | * if (fastContains(y.hi())) {
386 | * return new S1Interval(lo(), y.hi(), true);
387 | * }
388 | *
389 | * // This interval contains neither endpoint of y. This means that either y
390 | * // contains all of this interval, or the two intervals are disjoint.
391 | *
392 | * if (y.fastContains(lo())) {
393 | * return this; // is_empty() okay here
394 | * }
395 | * // assert (!intersects(y));
396 | * return empty();
397 | * }
398 | *
399 | * /**
400 | * Return true if the length of the symmetric difference between the two
401 | * intervals is at most the given tolerance.
402 | *#/
403 | * public boolean approxEquals(final S1Interval y, double maxError) {
404 | * if (isEmpty()) {
405 | * return y.getLength() <= maxError;
406 | * }
407 | * if (y.isEmpty()) {
408 | * return getLength() <= maxError;
409 | * }
410 | * return (Math.abs(Math.IEEEremainder(y.lo() - lo(), 2 * S2.M_PI))
411 | * + Math.abs(Math.IEEEremainder(y.hi() - hi(), 2 * S2.M_PI))) <= maxError;
412 | * }
413 | *
414 | * public boolean approxEquals(final S1Interval y) {
415 | * return approxEquals(y, 1e-9);
416 | * }
417 | *
418 | * /**
419 | * Return true if two intervals contains the same set of points.
420 | *#/
421 | * @Override
422 | * public boolean equals(Object that) {
423 | * if (that instanceof S1Interval) {
424 | * S1Interval thatInterval = (S1Interval) that;
425 | * return lo() == thatInterval.lo() && hi() == thatInterval.hi();
426 | * }
427 | * return false;
428 | * }
429 | *
430 | * @Override
431 | * public int hashCode() {
432 | * long value = 17;
433 | * value = 37 * value + Double.doubleToLongBits(lo());
434 | * value = 37 * value + Double.doubleToLongBits(hi());
435 | * return (int) ((value >>> 32) ^ value);
436 | * }
437 | *
438 | * @Override
439 | * public String toString() {
440 | * return "[" + this.lo() + ", " + this.hi() + "]";
441 | * }
442 | *
443 | * /**
444 | * Compute the distance from "a" to "b" in the range [0, 2*Pi). This is
445 | * equivalent to (drem(b - a - S2.M_PI, 2 * S2.M_PI) + S2.M_PI), except that
446 | * it is more numerically stable (it does not lose precision for very small
447 | * positive distances).
448 | */
449 | public static function positiveDistance($a, $b) {
450 | $d = $b - $a;
451 | if ($d >= 0) {
452 | return $d;
453 | }
454 | // We want to ensure that if b == Pi and a == (-Pi + eps),
455 | // the return result is approximately 2*Pi and not zero.
456 | return ($b + S2::M_PI) - ($a - S2::M_PI);
457 | }
458 | }
459 |
--------------------------------------------------------------------------------
/S2Cap.php:
--------------------------------------------------------------------------------
1 | axis = new S2Point();
21 | $this->height = 0;
22 | } else {
23 | $this->axis = $axis;
24 | $this->height = $height;
25 | }
26 | }
27 |
28 | /**
29 | * Create a cap given its axis and the cap height, i.e. the maximum projected
30 | * distance along the cap axis from the cap center. 'axis' should be a
31 | * unit-length vector.
32 | */
33 | // public static S2Cap fromAxisHeight(S2Point axis, double height) {
34 | // assert (S2.isUnitLength(axis));
35 | // return new S2Cap(axis, height);
36 | // }
37 |
38 | /**
39 | * Create a cap given its axis and the cap opening angle, i.e. maximum angle
40 | * between the axis and a point on the cap. 'axis' should be a unit-length
41 | * vector, and 'angle' should be between 0 and 180 degrees.
42 | */
43 | public static function fromAxisAngle(S2Point $axis, S1Angle $angle) {
44 | // The height of the cap can be computed as 1-cos(angle), but this isn't
45 | // very accurate for angles close to zero (where cos(angle) is almost 1).
46 | // Computing it as 2*(sin(angle/2)**2) gives much better precision.
47 |
48 | // assert (S2.isUnitLength(axis));
49 | $d = sin(0.5 * $angle->radians());
50 | return new S2Cap($axis, 2 * $d * $d);
51 | }
52 |
53 | /**
54 | * Create a cap given its axis and its area in steradians. 'axis' should be a
55 | * unit-length vector, and 'area' should be between 0 and 4 * M_PI.
56 | */
57 | // public static S2Cap fromAxisArea(S2Point axis, double area) {
58 | // assert (S2.isUnitLength(axis));
59 | // return new S2Cap(axis, area / (2 * S2.M_PI));
60 | // }
61 |
62 | /** Return an empty cap, i.e. a cap that contains no points. */
63 | public static function sempty() {
64 | return new S2Cap(new S2Point(1, 0, 0), -1);
65 | }
66 |
67 | /** Return a full cap, i.e. a cap that contains all points. *#/
68 | * public static S2Cap full() {
69 | * return new S2Cap(new S2Point(1, 0, 0), 2);
70 | * }
71 |
72 | */
73 | // Accessor methods.
74 | public function axis() {
75 | return $this->axis;
76 | }
77 |
78 | public function height() {
79 | return $this->height;
80 | }
81 |
82 | public function area() {
83 | return 2 * S2::M_PI * max(0.0, $this->height);
84 | }
85 |
86 | /**
87 | * Return the cap opening angle in radians, or a negative number for empty
88 | * caps.
89 | */
90 | public function angle() {
91 | // This could also be computed as acos(1 - height_), but the following
92 | // formula is much more accurate when the cap height is small. It
93 | // follows from the relationship h = 1 - cos(theta) = 2 sin^2(theta/2).
94 | if ($this->isEmpty()) {
95 | return S1Angle::sradians(-1);
96 | }
97 | return S1Angle::sradians(2 * asin(sqrt(0.5 * $this->height)));
98 | }
99 |
100 | /**
101 | * We allow negative heights (to represent empty caps) but not heights greater
102 | * than 2.
103 | *#/
104 | * public boolean isValid() {
105 | * return S2.isUnitLength(axis) && height <= 2;
106 | * }
107 | *
108 | * /** Return true if the cap is empty, i.e. it contains no points. */
109 | public function isEmpty() {
110 | return $this->height < 0;
111 | }
112 |
113 | /** Return true if the cap is full, i.e. it contains all points. *#/
114 | * public boolean isFull() {
115 | * return height >= 2;
116 | * }
117 | *
118 | * /**
119 | * Return the complement of the interior of the cap. A cap and its complement
120 | * have the same boundary but do not share any interior points. The complement
121 | * operator is not a bijection, since the complement of a singleton cap
122 | * (containing a single point) is the same as the complement of an empty cap.
123 | *#/
124 | * public S2Cap complement() {
125 | * // The complement of a full cap is an empty cap, not a singleton.
126 | * // Also make sure that the complement of an empty cap has height 2.
127 | * double cHeight = isFull() ? -1 : 2 - Math.max(height, 0.0);
128 | * return S2Cap.fromAxisHeight(S2Point.neg(axis), cHeight);
129 | * }
130 | *
131 | * /**
132 | * Return true if and only if this cap contains the given other cap (in a set
133 | * containment sense, e.g. every cap contains the empty cap).
134 | *#/
135 | * public boolean contains(S2Cap other) {
136 | * if (isFull() || other.isEmpty()) {
137 | * return true;
138 | * }
139 | * return angle().radians() >= axis.angle(other.axis)
140 | * + other.angle().radians();
141 | * }
142 | *
143 | * /**
144 | * Return true if and only if the interior of this cap intersects the given
145 | * other cap. (This relationship is not symmetric, since only the interior of
146 | * this cap is used.)
147 | *#/
148 | * public boolean interiorIntersects(S2Cap other) {
149 | * // Interior(X) intersects Y if and only if Complement(Interior(X))
150 | * // does not contain Y.
151 | * return !complement().contains(other);
152 | * }
153 | *
154 | * /**
155 | * Return true if and only if the given point is contained in the interior of
156 | * the region (i.e. the region excluding its boundary). 'p' should be a
157 | * unit-length vector.
158 | *#/
159 | * public boolean interiorContains(S2Point p) {
160 | * // assert (S2.isUnitLength(p));
161 | * return isFull() || S2Point.sub(axis, p).norm2() < 2 * height;
162 | * }
163 | *
164 | * /**
165 | * Increase the cap height if necessary to include the given point. If the cap
166 | * is empty the axis is set to the given point, but otherwise it is left
167 | * unchanged. 'p' should be a unit-length vector.
168 | */
169 | public function addPoint(S2Point $p) {
170 | // Compute the squared chord length, then convert it into a height.
171 | // assert (S2.isUnitLength(p));
172 | if ($this->isEmpty()) {
173 | return new S2Cap($p, 0);
174 | } else {
175 | // To make sure that the resulting cap actually includes this point,
176 | // we need to round up the distance calculation. That is, after
177 | // calling cap.AddPoint(p), cap.Contains(p) should be true.
178 | $dist2 = S2Point::sub($this->axis, $p)->norm2();
179 | $newHeight = max($this->height, self::ROUND_UP * 0.5 * $dist2);
180 | return new S2Cap($this->axis, $newHeight);
181 | }
182 | }
183 |
184 | /*
185 | // Increase the cap height if necessary to include "other". If the current
186 | // cap is empty it is set to the given other cap.
187 | public S2Cap addCap(S2Cap other) {
188 | if (isEmpty()) {
189 | return new S2Cap(other.axis, other.height);
190 | } else {
191 | // See comments for FromAxisAngle() and AddPoint(). This could be
192 | // optimized by doing the calculation in terms of cap heights rather
193 | // than cap opening angles.
194 | double angle = axis.angle(other.axis) + other.angle().radians();
195 | if (angle >= S2.M_PI) {
196 | return new S2Cap(axis, 2); //Full cap
197 | } else {
198 | double d = Math.sin(0.5 * angle);
199 | double newHeight = Math.max(height, ROUND_UP * 2 * d * d);
200 | return new S2Cap(axis, newHeight);
201 | }
202 | }
203 | }
204 |
205 | // //////////////////////////////////////////////////////////////////////
206 | // S2Region interface (see {@code S2Region} for details):
207 | * */
208 | public function getCapBound() {
209 | return $this;
210 | }
211 |
212 | public function getRectBound() {
213 | if ($this->isEmpty()) {
214 | return S2LatLngRect::emptya();
215 | }
216 |
217 | // Convert the axis to a (lat,lng) pair, and compute the cap angle.
218 | $axisLatLng = new S2LatLng($this->axis);
219 | $capAngle = $this->angle()->radians();
220 |
221 | $allLongitudes = false;
222 | $lat = array();
223 | $lng = array();
224 | $lng[0] = -S2::M_PI;
225 | $lng[1] = S2::M_PI;
226 |
227 | // Check whether cap includes the south pole.
228 | $lat[0] = $axisLatLng->lat()->radians() - $capAngle;
229 | if ($lat[0] <= -S2::M_PI_2) {
230 | $lat[0] = -S2::M_PI_2;
231 | $allLongitudes = true;
232 | }
233 | // Check whether cap includes the north pole.
234 | $lat[1] = $axisLatLng->lat()->radians() + $capAngle;
235 | if ($lat[1] >= S2::M_PI_2) {
236 | $lat[1] = S2::M_PI_2;
237 | $allLongitudes = true;
238 | }
239 | if (!$allLongitudes) {
240 | // Compute the range of longitudes covered by the cap. We use the law
241 | // of sines for spherical triangles. Consider the triangle ABC where
242 | // A is the north pole, B is the center of the cap, and C is the point
243 | // of tangency between the cap boundary and a line of longitude. Then
244 | // C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
245 | // we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
246 | // Here "a" is the cap angle, and "c" is the colatitude (90 degrees
247 | // minus the latitude). This formula also works for negative latitudes.
248 | //
249 | // The formula for sin(a) follows from the relationship h = 1 - cos(a).
250 |
251 | $sinA = sqrt($this->height * (2 - $this->height));
252 | $sinC = cos($axisLatLng->lat()->radians());
253 | if ($sinA <= $sinC) {
254 | $angleA = asin($sinA / $sinC);
255 | $lng[0] = S2::IEEEremainder($axisLatLng->lng()->radians() - $angleA, 2 * S2::M_PI);
256 | $lng[1] = S2::IEEEremainder($axisLatLng->lng()->radians() + $angleA, 2 * S2::M_PI);
257 | }
258 | }
259 | return new S2LatLngRect(
260 | new R1Interval($lat[0], $lat[1]),
261 | new S1Interval(
262 | $lng[0],
263 | $lng[1]
264 | )
265 | );
266 | }
267 |
268 | public function contains($cell) {
269 | // If the cap does not contain all cell vertices, return false.
270 | // We check the vertices before taking the Complement() because we can't
271 | // accurately represent the complement of a very small cap (a height
272 | // of 2-epsilon is rounded off to 2).
273 | $vertices = array();
274 | for ($k = 0; $k < 4; ++$k) {
275 | $vertices[$k] = $cell->getVertex($k);
276 | if (!$this->contains($vertices[$k])) {
277 | return false;
278 | }
279 | }
280 | // Otherwise, return true if the complement of the cap does not intersect
281 | // the cell. (This test is slightly conservative, because technically we
282 | // want Complement().InteriorIntersects() here.)
283 | return !$this->complement()->intersects($cell, $vertices);
284 | }
285 |
286 | public function mayIntersect(S2Cell $cell) {
287 | // If the cap contains any cell vertex, return true.
288 | $vertices = array();
289 | for ($k = 0; $k < 4; ++$k) {
290 | $vertices[$k] = $cell->getVertex($k);
291 | if ($this->contains($vertices[$k])) {
292 | return true;
293 | }
294 | }
295 | return $this->intersects($cell, $vertices);
296 | }
297 |
298 | /**
299 | * Return true if the cap intersects 'cell', given that the cap vertices have
300 | * alrady been checked.
301 | *#/
302 | * public boolean intersects(S2Cell cell, S2Point[] vertices) {
303 | * // Return true if this cap intersects any point of 'cell' excluding its
304 | * // vertices (which are assumed to already have been checked).
305 | *
306 | * // If the cap is a hemisphere or larger, the cell and the complement of the
307 | * // cap are both convex. Therefore since no vertex of the cell is contained,
308 | * // no other interior point of the cell is contained either.
309 | * if (height >= 1) {
310 | * return false;
311 | * }
312 | *
313 | * // We need to check for empty caps due to the axis check just below.
314 | * if (isEmpty()) {
315 | * return false;
316 | * }
317 | *
318 | * // Optimization: return true if the cell contains the cap axis. (This
319 | * // allows half of the edge checks below to be skipped.)
320 | * if (cell.contains(axis)) {
321 | * return true;
322 | * }
323 | *
324 | * // At this point we know that the cell does not contain the cap axis,
325 | * // and the cap does not contain any cell vertex. The only way that they
326 | * // can intersect is if the cap intersects the interior of some edge.
327 | *
328 | * double sin2Angle = height * (2 - height); // sin^2(capAngle)
329 | * for (int k = 0; k < 4; ++k) {
330 | * S2Point edge = cell.getEdgeRaw(k);
331 | * double dot = axis.dotProd(edge);
332 | * if (dot > 0) {
333 | * // The axis is in the interior half-space defined by the edge. We don't
334 | * // need to consider these edges, since if the cap intersects this edge
335 | * // then it also intersects the edge on the opposite side of the cell
336 | * // (because we know the axis is not contained with the cell).
337 | * continue;
338 | * }
339 | * // The Norm2() factor is necessary because "edge" is not normalized.
340 | * if (dot * dot > sin2Angle * edge.norm2()) {
341 | * return false; // Entire cap is on the exterior side of this edge.
342 | * }
343 | * // Otherwise, the great circle containing this edge intersects
344 | * // the interior of the cap. We just need to check whether the point
345 | * // of closest approach occurs between the two edge endpoints.
346 | * S2Point dir = S2Point.crossProd(edge, axis);
347 | * if (dir.dotProd(vertices[k]) < 0
348 | * && dir.dotProd(vertices[(k + 1) & 3]) > 0) {
349 | * return true;
350 | * }
351 | * }
352 | * return false;
353 | * }
354 | *
355 | * public boolean contains(S2Point p) {
356 | * // The point 'p' should be a unit-length vector.
357 | * // assert (S2.isUnitLength(p));
358 | * return S2Point.sub(axis, p).norm2() <= 2 * height;
359 | *
360 | * }
361 | *
362 | *
363 | * /** Return true if two caps are identical. *#/
364 | * @Override
365 | * public boolean equals(Object that) {
366 | *
367 | * if (!(that instanceof S2Cap)) {
368 | * return false;
369 | * }
370 | *
371 | * S2Cap other = (S2Cap) that;
372 | * return (axis.equals(other.axis) && height == other.height)
373 | * || (isEmpty() && other.isEmpty()) || (isFull() && other.isFull());
374 | *
375 | * }
376 | *
377 | * @Override
378 | * public int hashCode() {
379 | * if (isFull()) {
380 | * return 17;
381 | * } else if (isEmpty()) {
382 | * return 37;
383 | * }
384 | * int result = 17;
385 | * result = 37 * result + axis.hashCode();
386 | * long heightBits = Double.doubleToLongBits(height);
387 | * result = 37 * result + (int) ((heightBits >>> 32) ^ heightBits);
388 | * return result;
389 | * }
390 | *
391 | * // /////////////////////////////////////////////////////////////////////
392 | * // The following static methods are convenience functions for assertions
393 | * // and testing purposes only.
394 | *
395 | * /**
396 | * Return true if the cap axis and height differ by at most "max_error" from
397 | * the given cap "other".
398 | *#/
399 | * boolean approxEquals(S2Cap other, double maxError) {
400 | * return (axis.aequal(other.axis, maxError) && Math.abs(height - other.height) <= maxError)
401 | * || (isEmpty() && other.height <= maxError)
402 | * || (other.isEmpty() && height <= maxError)
403 | * || (isFull() && other.height >= 2 - maxError)
404 | * || (other.isFull() && height >= 2 - maxError);
405 | * }
406 | *
407 | * boolean approxEquals(S2Cap other) {
408 | * return approxEquals(other, 1e-14);
409 | * }
410 | *
411 | * @Override
412 | * public String toString() {
413 | * return "[Point = " + axis.toString() + " Height = " + height + "]";
414 | * }
415 | */
416 | }
417 |
--------------------------------------------------------------------------------
/S2Cell.php:
--------------------------------------------------------------------------------
1 | init(S2CellId::fromPoint($p));
22 | } else if ($p instanceof S2LatLng) {
23 | $this->init(S2CellId::fromLatLng($p));
24 | } else if ($p instanceof S2CellId) {
25 | $this->init($p);
26 | }
27 | }
28 |
29 | // This is a static method in order to provide named parameters.*/
30 | public static function fromFacePosLevel($face, $pos, $level) {
31 | return new S2Cell(S2CellId::fromFacePosLevel($face, $pos, $level));
32 | }
33 |
34 | public function id() {
35 | return $this->cellId;
36 | }
37 |
38 | public function face() {
39 | return $this->face;
40 | }
41 |
42 | public function level() {
43 | return $this->level;
44 | }
45 | /*
46 | public byte orientation() {
47 | return orientation;
48 | }
49 |
50 | public boolean isLeaf() {
51 | return level == S2CellId.MAX_LEVEL;
52 | }
53 |
54 | public S2Point getVertex(int k) {
55 | return S2Point.normalize(getVertexRaw(k));
56 | }
57 |
58 | /**
59 | * Return the k-th vertex of the cell (k = 0,1,2,3). Vertices are returned in
60 | * CCW order. The points returned by GetVertexRaw are not necessarily unit
61 | * length.
62 | *#/
63 | public S2Point getVertexRaw(int k) {
64 | // Vertices are returned in the order SW, SE, NE, NW.
65 | return S2Projections.faceUvToXyz(face, uv[0][(k >> 1) ^ (k & 1)], uv[1][k >> 1]);
66 | }
67 |
68 | public S2Point getEdge(int k) {
69 | return S2Point.normalize(getEdgeRaw(k));
70 | }
71 |
72 | public S2Point getEdgeRaw(int k) {
73 | switch (k) {
74 | case 0:
75 | return S2Projections.getVNorm(face, uv[1][0]); // South
76 | case 1:
77 | return S2Projections.getUNorm(face, uv[0][1]); // East
78 | case 2:
79 | return S2Point.neg(S2Projections.getVNorm(face, uv[1][1])); // North
80 | default:
81 | return S2Point.neg(S2Projections.getUNorm(face, uv[0][0])); // West
82 | }
83 | }
84 | */
85 | /**
86 | * Return the inward-facing normal of the great circle passing through the
87 | * edge from vertex k to vertex k+1 (mod 4). The normals returned by
88 | * GetEdgeRaw are not necessarily unit length.
89 | *
90 | * If this is not a leaf cell, set children[0..3] to the four children of
91 | * this cell (in traversal order) and return true. Otherwise returns false.
92 | * This method is equivalent to the following:
93 | *
94 | * for (pos=0, id=child_begin(); id != child_end(); id = id.next(), ++pos)
95 | * children[i] = S2Cell(id);
96 | *
97 | * except that it is more than two times faster.
98 | * @param S2Cell[] $children
99 | */
100 | public function subdivide(&$children) {
101 | // This function is equivalent to just iterating over the child cell ids
102 | // and calling the S2Cell constructor, but it is about 2.5 times faster.
103 |
104 | if ($this->cellId->isLeaf()) {
105 | return false;
106 | }
107 |
108 | // Compute the cell midpoint in uv-space.
109 | $uvMid = $this->getCenterUV();
110 |
111 | // Create four children with the appropriate bounds.
112 | /** @var S2CellId $id */
113 | $id = $this->cellId->childBegin();
114 | for ($pos = 0; $pos < 4; ++$pos, $id = $id->next()) {
115 | $child = &$children[$pos];
116 | $child->face = $this->face;
117 | $child->level = $this->level + 1;
118 | $new_o = S2::posToOrientation($pos);
119 | $child->orientation = $this->orientation ^ $new_o;
120 | // echo "this-ori:" . $this->orientation . " new_o:" . $new_o . " res:" . $child->orientation . "\n";
121 | $child->cellId = $id;
122 | $ij = S2::posToIJ($this->orientation, $pos);
123 | for ($d = 0; $d < 2; ++$d) {
124 | // The dimension 0 index (i/u) is in bit 1 of ij.
125 | $m = 1 - (($ij >> (1 - $d)) & 1);
126 | $child->uv[$d][$m] = $uvMid->get($d);
127 | $child->uv[$d][1 - $m] = $this->uv[$d][1 - $m];
128 | }
129 | }
130 | return true;
131 | }
132 |
133 | /**
134 | * Return the direction vector corresponding to the center in (s,t)-space of
135 | * the given cell. This is the point at which the cell is divided into four
136 | * subcells; it is not necessarily the centroid of the cell in (u,v)-space or
137 | * (x,y,z)-space. The point returned by GetCenterRaw is not necessarily unit
138 | * length.
139 | *#/
140 | * public S2Point getCenter() {
141 | * return S2Point.normalize(getCenterRaw());
142 | * }
143 | *
144 | * public S2Point getCenterRaw() {
145 | * return cellId.toPointRaw();
146 | * }
147 | *
148 | * /**
149 | * Return the center of the cell in (u,v) coordinates (see {@code
150 | * S2Projections}). Note that the center of the cell is defined as the point
151 | * at which it is recursively subdivided into four children; in general, it is
152 | * not at the midpoint of the (u,v) rectangle covered by the cell
153 | */
154 | public function getCenterUV() {
155 | $i = 0;
156 | $j = 0;
157 | $null = null;
158 | $this->cellId->toFaceIJOrientation($i, $j, $null);
159 | $cellSize = 1 << (S2CellId::MAX_LEVEL - $this->level);
160 |
161 | // TODO(dbeaumont): Figure out a better naming of the variables here (and elsewhere).
162 | $si = ($i & -$cellSize) * 2 + $cellSize - self::MAX_CELL_SIZE;
163 | $x = S2Projections::stToUV((1.0 / self::MAX_CELL_SIZE) * $si);
164 |
165 | $sj = ($j & -$cellSize) * 2 + $cellSize - self::MAX_CELL_SIZE;
166 | $y = S2Projections::stToUV((1.0 / self::MAX_CELL_SIZE) * $sj);
167 |
168 | return new R2Vector($x, $y);
169 | }
170 |
171 | /**
172 | * Return the average area for cells at the given level.
173 | *#/
174 | * public static double averageArea(int level) {
175 | * return S2Projections.AVG_AREA.getValue(level);
176 | * }
177 | *
178 | * /**
179 | * Return the average area of cells at this level. This is accurate to within
180 | * a factor of 1.7 (for S2_QUADRATIC_PROJECTION) and is extremely cheap to
181 | * compute.
182 | *#/
183 | * public double averageArea() {
184 | * return averageArea(level);
185 | * }
186 | *
187 | * /**
188 | * Return the approximate area of this cell. This method is accurate to within
189 | * 3% percent for all cell sizes and accurate to within 0.1% for cells at
190 | * level 5 or higher (i.e. 300km square or smaller). It is moderately cheap to
191 | * compute.
192 | *#/
193 | * public double approxArea() {
194 | *
195 | * // All cells at the first two levels have the same area.
196 | * if (level < 2) {
197 | * return averageArea(level);
198 | * }
199 | *
200 | * // First, compute the approximate area of the cell when projected
201 | * // perpendicular to its normal. The cross product of its diagonals gives
202 | * // the normal, and the length of the normal is twice the projected area.
203 | * double flatArea = 0.5 * S2Point.crossProd(
204 | * S2Point.sub(getVertex(2), getVertex(0)), S2Point.sub(getVertex(3), getVertex(1))).norm();
205 | *
206 | * // Now, compensate for the curvature of the cell surface by pretending
207 | * // that the cell is shaped like a spherical cap. The ratio of the
208 | * // area of a spherical cap to the area of its projected disc turns out
209 | * // to be 2 / (1 + sqrt(1 - r*r)) where "r" is the radius of the disc.
210 | * // For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
211 | * // Here we set Pi*r*r == flat_area to find the equivalent disc.
212 | * return flatArea * 2 / (1 + Math.sqrt(1 - Math.min(S2.M_1_PI * flatArea, 1.0)));
213 | * }
214 | *
215 | * /**
216 | * Return the area of this cell as accurately as possible. This method is more
217 | * expensive but it is accurate to 6 digits of precision even for leaf cells
218 | * (whose area is approximately 1e-18).
219 | *#/
220 | * public double exactArea() {
221 | * S2Point v0 = getVertex(0);
222 | * S2Point v1 = getVertex(1);
223 | * S2Point v2 = getVertex(2);
224 | * S2Point v3 = getVertex(3);
225 | * return S2.area(v0, v1, v2) + S2.area(v0, v2, v3);
226 | * }
227 | *
228 | * // //////////////////////////////////////////////////////////////////////
229 | * // S2Region interface (see {@code S2Region} for details):
230 | *
231 | * @Override
232 | * public S2Region clone() {
233 | * S2Cell clone = new S2Cell();
234 | * clone.face = this.face;
235 | * clone.level = this.level;
236 | * clone.orientation = this.orientation;
237 | * clone.uv = this.uv.clone();
238 | *
239 | * return clone;
240 | * }
241 |
242 | */
243 | public function getCapBound() {
244 | // Use the cell center in (u,v)-space as the cap axis. This vector is
245 | // very close to GetCenter() and faster to compute. Neither one of these
246 | // vectors yields the bounding cap with minimal surface area, but they
247 | // are both pretty close.
248 | //
249 | // It's possible to show that the two vertices that are furthest from
250 | // the (u,v)-origin never determine the maximum cap size (this is a
251 | // possible future optimization).
252 |
253 | $u = 0.5 * ($this->uv[0][0] + $this->uv[0][1]);
254 | $v = 0.5 * ($this->uv[1][0] + $this->uv[1][1]);
255 | $cap = S2Cap::fromAxisHeight(S2Point::normalize(S2Projections::faceUvToXyz($this->face, $u, $v)), 0);
256 | for ($k = 0; $k < 4; ++$k) {
257 | $cap = $cap->addPoint($this->getVertex($k));
258 | }
259 | return $cap;
260 | }
261 | // We grow the bounds slightly to make sure that the bounding rectangle
262 | // also contains the normalized versions of the vertices. Note that the
263 | // maximum result magnitude is Pi, with a floating-point exponent of 1.
264 | // Therefore adding or subtracting 2**-51 will always change the result.
265 | const MAX_ERROR = MAX_ERROR;
266 |
267 | // The 4 cells around the equator extend to +/-45 degrees latitude at the
268 | // midpoints of their top and bottom edges. The two cells covering the
269 | // poles extend down to +/-35.26 degrees at their vertices.
270 | // adding kMaxError (as opposed to the C version) because of asin and atan2
271 | // roundoff errors
272 | const POLE_MIN_LAT = POLE_MIN_LAT;
273 |
274 | // 35.26 degrees
275 |
276 | public function getRectBound() {
277 | if ($this->level > 0) {
278 | // Except for cells at level 0, the latitude and longitude extremes are
279 | // attained at the vertices. Furthermore, the latitude range is
280 | // determined by one pair of diagonally opposite vertices and the
281 | // longitude range is determined by the other pair.
282 | //
283 | // We first determine which corner (i,j) of the cell has the largest
284 | // absolute latitude. To maximize latitude, we want to find the point in
285 | // the cell that has the largest absolute z-coordinate and the smallest
286 | // absolute x- and y-coordinates. To do this we look at each coordinate
287 | // (u and v), and determine whether we want to minimize or maximize that
288 | // coordinate based on the axis direction and the cell's (u,v) quadrant.
289 | $u = $this->uv[0][0] + $this->uv[0][1];
290 | $v = $this->uv[1][0] + $this->uv[1][1];
291 | $i = S2Projections::getUAxis($this->face)->z == 0 ? ($u < 0 ? 1 : 0) : ($u > 0 ? 1 : 0);
292 | $j = S2Projections::getVAxis($this->face)->z == 0 ? ($v < 0 ? 1 : 0) : ($v > 0 ? 1 : 0);
293 |
294 | $lat = R1Interval::fromPointPair($this->getLatitude($i, $j), $this->getLatitude(1 - $i, 1 - $j));
295 | $lat = $lat->expanded(self::MAX_ERROR)->intersection(S2LatLngRect::fullLat());
296 | if ($lat->lo() == -S2::M_PI_2 || $lat->hi() == S2::M_PI_2) {
297 | return new S2LatLngRect($lat, S1Interval::full());
298 | }
299 | $lng = S1Interval::fromPointPair($this->getLongitude($i, 1 - $j), $this->getLongitude(1 - $i, $j));
300 | return new S2LatLngRect($lat, $lng->expanded(self::MAX_ERROR));
301 | }
302 |
303 |
304 | // The face centers are the +X, +Y, +Z, -X, -Y, -Z axes in that order.
305 | // assert (S2Projections.getNorm(face).get(face % 3) == ((face < 3) ? 1 : -1));
306 | switch ($this->face) {
307 | case 0:
308 | return new S2LatLngRect(
309 | new R1Interval(-S2::M_PI_4, S2::M_PI_4),
310 | new S1Interval(-S2::M_PI_4, S2::M_PI_4)
311 | );
312 |
313 | case 1:
314 | return new S2LatLngRect(
315 | new R1Interval(-S2::M_PI_4, S2::M_PI_4),
316 | new S1Interval(S2::M_PI_4, 3 * S2::M_PI_4)
317 | );
318 |
319 | case 2:
320 | return new S2LatLngRect(
321 | new R1Interval(POLE_MIN_LAT, S2::M_PI_2),
322 | new S1Interval(-S2::M_PI, S2::M_PI)
323 | );
324 |
325 | case 3:
326 | return new S2LatLngRect(
327 | new R1Interval(-S2::M_PI_4, S2::M_PI_4),
328 | new S1Interval(3 * S2::M_PI_4, -3 * S2::M_PI_4)
329 | );
330 |
331 | case 4:
332 | return new S2LatLngRect(
333 | new R1Interval(-S2::M_PI_4, S2::M_PI_4),
334 | new S1Interval(-3 * S2::M_PI_4, -S2::M_PI_4)
335 | );
336 |
337 | default:
338 | return new S2LatLngRect(
339 | new R1Interval(-S2::M_PI_2, -POLE_MIN_LAT),
340 | new S1Interval(-S2::M_PI, S2::M_PI)
341 | );
342 | }
343 | }
344 |
345 | public function mayIntersect(S2Cell $cell) {
346 | return $this->cellId->intersects($cell->cellId);
347 | }
348 |
349 | public function contains($p) {
350 | // We can't just call XYZtoFaceUV, because for points that lie on the
351 | // boundary between two faces (i.e. u or v is +1/-1) we need to return
352 | // true for both adjacent cells.
353 | if ($p instanceof S2Point) {
354 | $uvPoint = S2Projections::faceXyzToUv($this->face, $p);
355 | if ($uvPoint == null) {
356 | return false;
357 | }
358 | return ($uvPoint->x() >= $uv[0][0] && $uvPoint->x() <= $uv[0][1]
359 | && $uvPoint->y() >= $uv[1][0] && $uvPoint->y() <= $uv[1][1]);
360 | } else if ($p instanceof S2Cell) {
361 | return $this->cellId . contains($p->cellId);
362 | }
363 | }
364 |
365 | private function init(S2CellId $id) {
366 | $this->cellId = $id;
367 | $ij = array(0, 0);
368 | $mOrientation = 0;
369 |
370 | // echo " $mOrientation\n";
371 | $this->face = $id->toFaceIJOrientation($ij[0], $ij[1], $mOrientation);
372 | // echo ">> $mOrientation\n";
373 | $this->orientation = $mOrientation;
374 | $this->level = $id->level();
375 | $cellSize = 1 << (S2CellId::MAX_LEVEL - $this->level);
376 | for ($d = 0; $d < 2; ++$d) {
377 | // Compute the cell bounds in scaled (i,j) coordinates.
378 | $sijLo = ($ij[$d] & -$cellSize) * 2 - self::MAX_CELL_SIZE;
379 | $sijHi = $sijLo + $cellSize * 2;
380 | $this->uv[$d][0] = S2Projections::stToUV((1.0 / self::MAX_CELL_SIZE) * $sijLo);
381 | $this->uv[$d][1] = S2Projections::stToUV((1.0 / self::MAX_CELL_SIZE) * $sijHi);
382 | }
383 | }
384 |
385 | // Internal method that does the actual work in the constructors.
386 |
387 | private function getLatitude($i, $j) {
388 | $p = S2Projections::faceUvToXyz($this->face, $this->uv[0][$i], $this->uv[1][$j]);
389 | return atan2($p->z, sqrt($p->x * $p->x + $p->y * $p->y));
390 | }
391 |
392 | private function getLongitude($i, $j) {
393 | $p = S2Projections::faceUvToXyz($this->face, $this->uv[0][$i], $this->uv[1][$j]);
394 | return atan2($p->y, $p->x);
395 | }
396 |
397 | /*
398 | // Return the latitude or longitude of the cell vertex given by (i,j),
399 | // where "i" and "j" are either 0 or 1.
400 |
401 | */
402 | public function __toString() {
403 | return sprintf("[%d, %d, %d, %s]", $this->face, $this->level, $this->orientation, $this->cellId);
404 | }
405 | /*
406 | @Override
407 | public int hashCode() {
408 | int value = 17;
409 | value = 37 * (37 * (37 * value + face) + orientation) + level;
410 | return 37 * value + id().hashCode();
411 | }
412 |
413 | @Override
414 | public boolean equals(Object that) {
415 | if (that instanceof S2Cell) {
416 | S2Cell thatCell = (S2Cell) that;
417 | return this.face == thatCell.face && this.level == thatCell.level
418 | && this.orientation == thatCell.orientation && this.cellId.equals(thatCell.cellId);
419 | }
420 | return false;
421 | }
422 | */
423 | }
424 |
--------------------------------------------------------------------------------
/S2EdgeIndex.php:
--------------------------------------------------------------------------------
1 | edges.
19 | */
20 | private $cells;
21 |
22 | /**
23 | * The edge contained by each cell, as given in the parallel array
24 | * cells.
25 | */
26 | private $edges;
27 |
28 | /**
29 | * No cell strictly below this level appears in mapping. Initially leaf level,
30 | * that's the minimum level at which we will ever look for test edges.
31 | */
32 | private $minimumS2LevelUsed;
33 |
34 | /**
35 | * Has the index been computed already?
36 | */
37 | private $indexComputed;
38 |
39 | /**
40 | * Number of queries so far
41 | */
42 | private $queryCount;
43 |
44 | /**
45 | * Empties the index in case it already contained something.
46 | */
47 | public function reset() {
48 | $this->minimumS2LevelUsed = S2CellId::MAX_LEVEL;
49 | $this->indexComputed = false;
50 | $this->queryCount = 0;
51 | $this->cells = null;
52 | $this->edges = null;
53 | }
54 |
55 | /**
56 | * Compares [cell1, edge1] to [cell2, edge2], by cell first and edge second.
57 | *
58 | * @param $cell1
59 | * @param $edge1
60 | * @param $cell2
61 | * @param $edge2
62 | * @return int -1 if [cell1, edge1] is less than [cell2, edge2], 1 if [cell1,
63 | */
64 | private static function compare($cell1, $edge1, $cell2, $edge2) {
65 | if ($cell1 < $cell2) {
66 | return -1;
67 | } else if ($cell1 > $cell2) {
68 | return 1;
69 | } else if ($edge1 < $edge2) {
70 | return -1;
71 | } else if ($edge1 > $edge2) {
72 | return 1;
73 | } else {
74 | return 0;
75 | }
76 | }
77 |
78 | /** Computes the index (if it has not been previously done). *#/
79 | public final function computeIndex() {
80 | if ($this->indexComputed) {
81 | return;
82 | }
83 | List
84 | cellList = Lists.newArrayList();
85 | List
86 | edgeList = Lists.newArrayList();
87 | for (int i = 0; i < getNumEdges(); ++i) {
88 | S2Point from = edgeFrom(i);
89 | S2Point to = edgeTo(i);
90 | ArrayList
91 | cover = Lists.newArrayList();
92 | int level = getCovering(from, to, true, cover);
93 | minimumS2LevelUsed = Math.min(minimumS2LevelUsed, level);
94 | for (S2CellId cellId : cover) {
95 | cellList.add(cellId.id());
96 | edgeList.add(i);
97 | }
98 | }
99 | cells = new long[cellList.size()];
100 | edges = new int[edgeList.size()];
101 | for (int i = 0; i < cells.length; i++) {
102 | cells[i] = cellList.get(i);
103 | edges[i] = edgeList.get(i);
104 | }
105 | sortIndex();
106 | indexComputed = true;
107 | }
108 |
109 | /** Sorts the parallel cells and edges arrays. *#/
110 | private function sortIndex() {
111 | // create an array of indices and sort based on the values in the parallel
112 | // arrays at each index
113 | Integer[] indices = new Integer[cells.length];
114 | for (int i = 0; i < indices.length; i++) {
115 | indices[i] = i;
116 | }
117 | Arrays.sort(indices, new Comparator
118 | () {
119 | @Override
120 | public int compare(Integer index1, Integer index2) {
121 | return S2EdgeIndex.compare(cells[index1], edges[index1], cells[index2], edges[index2]);
122 | }
123 | });
124 | // copy the cells and edges in the order given by the sorted list of indices
125 | long[] newCells = new long[cells.length];
126 | int[] newEdges = new int[edges.length];
127 | for (int i = 0; i < indices.length; i++) {
128 | newCells[i] = cells[indices[i]];
129 | newEdges[i] = edges[indices[i]];
130 | }
131 | // replace the cells and edges with the sorted arrays
132 | cells = newCells;
133 | edges = newEdges;
134 | }
135 |
136 | public final function isIndexComputed() {
137 | return indexComputed;
138 | }
139 |
140 | /**
141 | * Tell the index that we just received a new request for candidates. Useful
142 | * to compute when to switch to quad tree.
143 | *#/
144 | protected final function incrementQueryCount() {
145 | ++queryCount;
146 | }
147 |
148 | /**
149 | * If the index hasn't been computed yet, looks at how much work has gone into
150 | * iterating using the brute force method, and how much more work is planned
151 | * as defined by 'cost'. If it were to have been cheaper to use a quad tree
152 | * from the beginning, then compute it now. This guarantees that we will never
153 | * use more than twice the time we would have used had we known in advance
154 | * exactly how many edges we would have wanted to test. It is the theoretical
155 | * best.
156 | *
157 | * The value 'n' is the number of iterators we expect to request from this
158 | * edge index.
159 | *
160 | * If we have m data edges and n query edges, then the brute force cost is m
161 | * * n * testCost where testCost is taken to be the cost of
162 | * EdgeCrosser.robustCrossing, measured to be about 30ns at the time of this
163 | * writing.
164 | *
165 | * If we compute the index, the cost becomes: m * costInsert + n *
166 | * costFind(m)
167 | *
168 | * - costInsert can be expected to be reasonably stable, and was measured at
169 | * 1200ns with the BM_QuadEdgeInsertionCost benchmark.
170 | *
171 | * - costFind depends on the length of the edge . For m=1000 edges, we got
172 | * timings ranging from 1ms (edge the length of the polygon) to 40ms. The
173 | * latter is for very long query edges, and needs to be optimized. We will
174 | * assume for the rest of the discussion that costFind is roughly 3ms.
175 | *
176 | * When doing one additional query, the differential cost is m * testCost -
177 | * costFind(m) With the numbers above, it is better to use the quad tree (if
178 | * we have it) if m >= 100.
179 | *
180 | * If m = 100, 30 queries will give m*n*testCost = m * costInsert = 100ms,
181 | * while the marginal cost to find is 3ms. Thus, this is a reasonable thing to
182 | * do.
183 | *#/
184 | public final function predictAdditionalCalls(int n) {
185 | if (indexComputed) {
186 | return;
187 | }
188 | if (getNumEdges() > 100 && (queryCount + n) > 30) {
189 | computeIndex();
190 | }
191 | }
192 |
193 | /**
194 | * Overwrite these functions to give access to the underlying data. The
195 | * function getNumEdges() returns the number of edges in the index, while
196 | * edgeFrom(index) and edgeTo(index) return the "from" and "to" endpoints of
197 | * the edge at the given index.
198 | *#/
199 | protected abstract function getNumEdges();
200 |
201 | protected abstract function edgeFrom(int index);
202 |
203 | protected abstract function edgeTo(int index);
204 |
205 | /**
206 | * Appends to "candidateCrossings" all edge references which may cross the
207 | * given edge. This is done by covering the edge and then finding all
208 | * references of edges whose coverings overlap this covering. Parent cells are
209 | * checked level by level. Child cells are checked all at once by taking
210 | * advantage of the natural ordering of S2CellIds.
211 | *#/
212 | protected function findCandidateCrossings(S2Point a, S2Point b, List
213 | candidateCrossings) {
214 | Preconditions.checkState(indexComputed);
215 | ArrayList
216 | cover = Lists.newArrayList();
217 | getCovering(a, b, false, cover);
218 |
219 | // Edge references are inserted into the map once for each covering cell, so
220 | // absorb duplicates here
221 | Set
222 | uniqueSet = new HashSet
223 | ();
224 | getEdgesInParentCells(cover, uniqueSet);
225 |
226 | // TODO(user): An important optimization for long query
227 | // edges (Contains queries): keep a bounding cap and clip the query
228 | // edge to the cap before starting the descent.
229 | getEdgesInChildrenCells(a, b, cover, uniqueSet);
230 |
231 | candidateCrossings.clear();
232 | candidateCrossings.addAll(uniqueSet);
233 | }
234 |
235 | /**
236 | * Returns the smallest cell containing all four points, or
237 | * {@link S2CellId#sentinel()} if they are not all on the same face. The
238 | * points don't need to be normalized.
239 | *#/
240 | private static function containingCell(S2Point pa, S2Point pb, S2Point pc, S2Point pd) {
241 | S2CellId a = S2CellId.fromPoint(pa);
242 | S2CellId b = S2CellId.fromPoint(pb);
243 | S2CellId c = S2CellId.fromPoint(pc);
244 | S2CellId d = S2CellId.fromPoint(pd);
245 |
246 | if (a.face() != b.face() || a.face() != c.face() || a.face() != d.face()) {
247 | return S2CellId.sentinel();
248 | }
249 |
250 | while (!a.equals(b) || !a.equals(c) || !a.equals(d)) {
251 | a = a.parent();
252 | b = b.parent();
253 | c = c.parent();
254 | d = d.parent();
255 | }
256 | return a;
257 | }
258 |
259 | /**
260 | * Returns the smallest cell containing both points, or Sentinel if they are
261 | * not all on the same face. The points don't need to be normalized.
262 | *#/
263 | private static function containingCell(S2Point pa, S2Point pb) {
264 | S2CellId a = S2CellId.fromPoint(pa);
265 | S2CellId b = S2CellId.fromPoint(pb);
266 |
267 | if (a.face() != b.face()) {
268 | return S2CellId.sentinel();
269 | }
270 |
271 | while (!a.equals(b)) {
272 | a = a.parent();
273 | b = b.parent();
274 | }
275 | return a;
276 | }
277 |
278 | /**
279 | * Computes a cell covering of an edge. Clears edgeCovering and returns the
280 | * level of the s2 cells used in the covering (only one level is ever used for
281 | * each call).
282 | *
283 | * If thickenEdge is true, the edge is thickened and extended by 1% of its
284 | * length.
285 | *
286 | * It is guaranteed that no child of a covering cell will fully contain the
287 | * covered edge.
288 | *#/
289 | private function getCovering(
290 | S2Point a, S2Point b, boolean thickenEdge, ArrayList
291 | edgeCovering) {
292 | edgeCovering.clear();
293 |
294 | // Selects the ideal s2 level at which to cover the edge, this will be the
295 | // level whose S2 cells have a width roughly commensurate to the length of
296 | // the edge. We multiply the edge length by 2*THICKENING to guarantee the
297 | // thickening is honored (it's not a big deal if we honor it when we don't
298 | // request it) when doing the covering-by-cap trick.
299 | double edgeLength = a.angle(b);
300 | int idealLevel = S2Projections.MIN_WIDTH.getMaxLevel(edgeLength * (1 + 2 * THICKENING));
301 |
302 | S2CellId containingCellId;
303 | if (!thickenEdge) {
304 | containingCellId = containingCell(a, b);
305 | } else {
306 | if (idealLevel == S2CellId.MAX_LEVEL) {
307 | // If the edge is tiny, instabilities are more likely, so we
308 | // want to limit the number of operations.
309 | // We pretend we are in a cell much larger so as to trigger the
310 | // 'needs covering' case, so we won't try to thicken the edge.
311 | containingCellId = (new S2CellId(0xFFF0)).parent(3);
312 | } else {
313 | S2Point pq = S2Point.mul(S2Point.minus(b, a), THICKENING);
314 | S2Point ortho =
315 | S2Point.mul(S2Point.normalize(S2Point.crossProd(pq, a)), edgeLength * THICKENING);
316 | S2Point p = S2Point.minus(a, pq);
317 | S2Point q = S2Point.add(b, pq);
318 | // If p and q were antipodal, the edge wouldn't be lengthened,
319 | // and it could even flip! This is not a problem because
320 | // idealLevel != 0 here. The farther p and q can be is roughly
321 | // a quarter Earth away from each other, so we remain
322 | // Theta(THICKENING).
323 | containingCellId =
324 | containingCell(S2Point.minus(p, ortho), S2Point.add(p, ortho), S2Point.minus(q, ortho),
325 | S2Point.add(q, ortho));
326 | }
327 | }
328 |
329 | // Best case: edge is fully contained in a cell that's not too big.
330 | if (!containingCellId.equals(S2CellId.sentinel())
331 | && containingCellId.level() >= idealLevel - 2) {
332 | edgeCovering.add(containingCellId);
333 | return containingCellId.level();
334 | }
335 |
336 | if (idealLevel == 0) {
337 | // Edge is very long, maybe even longer than a face width, so the
338 | // trick below doesn't work. For now, we will add the whole S2 sphere.
339 | // TODO(user): Do something a tad smarter (and beware of the
340 | // antipodal case).
341 | for (S2CellId cellid = S2CellId.begin(0); !cellid.equals(S2CellId.end(0));
342 | cellid = cellid.next()) {
343 | edgeCovering.add(cellid);
344 | }
345 | return 0;
346 | }
347 | // TODO(user): Check trick below works even when vertex is at
348 | // interface
349 | // between three faces.
350 |
351 | // Use trick as in S2PolygonBuilder.PointIndex.findNearbyPoint:
352 | // Cover the edge by a cap centered at the edge midpoint, then cover
353 | // the cap by four big-enough cells around the cell vertex closest to the
354 | // cap center.
355 | S2Point middle = S2Point.normalize(S2Point.div(S2Point.add(a, b), 2));
356 | int actualLevel = Math.min(idealLevel, S2CellId.MAX_LEVEL - 1);
357 | S2CellId.fromPoint(middle).getVertexNeighbors(actualLevel, edgeCovering);
358 | return actualLevel;
359 | }
360 |
361 | /**
362 | * Filters a list of entries down to the inclusive range defined by the given
363 | * cells, in O(log N) time.
364 | *
365 | * @param cell1 One side of the inclusive query range.
366 | * @param cell2 The other side of the inclusive query range.
367 | * @return An array of length 2, containing the start/end indices.
368 | *#/
369 | private function getEdges(long cell1, long cell2) {
370 | // ensure cell1 <= cell2
371 | if (cell1 > cell2) {
372 | long temp = cell1;
373 | cell1 = cell2;
374 | cell2 = temp;
375 | }
376 | // The binary search returns -N-1 to indicate an insertion point at index N,
377 | // if an exact match cannot be found. Since the edge indices queried for are
378 | // not valid edge indices, we will always get -N-1, so we immediately
379 | // convert to N.
380 | return new int[]{
381 | -1 - binarySearch(cell1, Integer.MIN_VALUE),
382 | -1 - binarySearch(cell2, Integer.MAX_VALUE)};
383 | }
384 |
385 | private function binarySearch(long cell, int edge) {
386 | int low = 0;
387 | int high = cells.length - 1;
388 | while (low <= high) {
389 | int mid = (low + high) >>> 1;
390 | int cmp = compare(cells[mid], edges[mid], cell, edge);
391 | if (cmp < 0) {
392 | low = mid + 1;
393 | } else if (cmp > 0) {
394 | high = mid - 1;
395 | } else {
396 | return mid;
397 | }
398 | }
399 | return -(low + 1);
400 | }
401 |
402 | /**
403 | * Adds to candidateCrossings all the edges present in any ancestor of any
404 | * cell of cover, down to minimumS2LevelUsed. The cell->edge map is in the
405 | * variable mapping.
406 | *#/
407 | private function getEdgesInParentCells(List
408 | cover, Set
409 | candidateCrossings) {
410 | // Find all parent cells of covering cells.
411 | Set
412 | parentCells = Sets.newHashSet();
413 | for (S2CellId coverCell : cover) {
414 | for (int parentLevel = coverCell.level() - 1; parentLevel >= minimumS2LevelUsed;
415 | --parentLevel) {
416 | if (!parentCells.add(coverCell.parent(parentLevel))) {
417 | break; // cell is already in => parents are too.
418 | }
419 | }
420 | }
421 |
422 | // Put parent cell edge references into result.
423 | for (S2CellId parentCell : parentCells) {
424 | int[] bounds = getEdges(parentCell.id(), parentCell.id());
425 | for (int i = bounds[0]; i < bounds[1]; i++) {
426 | candidateCrossings.add(edges[i]);
427 | }
428 | }
429 | }
430 |
431 | /**
432 | * Returns true if ab possibly crosses cd, by clipping tiny angles to zero.
433 | *#/
434 | private static function lenientCrossing(S2Point a, S2Point b, S2Point c, S2Point d) {
435 | // assert (S2.isUnitLength(a));
436 | // assert (S2.isUnitLength(b));
437 | // assert (S2.isUnitLength(c));
438 |
439 | double acb = S2Point.crossProd(a, c).dotProd(b);
440 | double bda = S2Point.crossProd(b, d).dotProd(a);
441 | if (Math.abs(acb) < MAX_DET_ERROR || Math.abs(bda) < MAX_DET_ERROR) {
442 | return true;
443 | }
444 | if (acb * bda < 0) {
445 | return false;
446 | }
447 | double cbd = S2Point.crossProd(c, b).dotProd(d);
448 | double dac = S2Point.crossProd(c, a).dotProd(c);
449 | if (Math.abs(cbd) < MAX_DET_ERROR || Math.abs(dac) < MAX_DET_ERROR) {
450 | return true;
451 | }
452 | return (acb * cbd >= 0) && (acb * dac >= 0);
453 | }
454 |
455 | /**
456 | * Returns true if the edge and the cell (including boundary) intersect.
457 | *#/
458 | private static function edgeIntersectsCellBoundary(S2Point a, S2Point b, S2Cell cell) {
459 | S2Point[] vertices = new S2Point[4];
460 | for (int i = 0; i < 4; ++i) {
461 | vertices[i] = cell.getVertex(i);
462 | }
463 | for (int i = 0; i < 4; ++i) {
464 | S2Point fromPoint = vertices[i];
465 | S2Point toPoint = vertices[(i + 1) % 4];
466 | if (lenientCrossing(a, b, fromPoint, toPoint)) {
467 | return true;
468 | }
469 | }
470 | return false;
471 | }
472 |
473 | /**
474 | * Appends to candidateCrossings the edges that are fully contained in an S2
475 | * covering of edge. The covering of edge used is initially cover, but is
476 | * refined to eliminate quickly subcells that contain many edges but do not
477 | * intersect with edge.
478 | *#/
479 | private function getEdgesInChildrenCells(S2Point a, S2Point b, List
480 | cover,
481 | Set
482 | candidateCrossings) {
483 | // Put all edge references of (covering cells + descendant cells) into
484 | // result.
485 | // This relies on the natural ordering of S2CellIds.
486 | S2Cell[] children = null;
487 | while (!cover.isEmpty()) {
488 | S2CellId cell = cover.remove(cover.size() - 1);
489 | int[] bounds = getEdges(cell.rangeMin().id(), cell.rangeMax().id());
490 | if (bounds[1] - bounds[0] <= 16) {
491 | for (int i = bounds[0]; i < bounds[1]; i++) {
492 | candidateCrossings.add(edges[i]);
493 | }
494 | } else {
495 | // Add cells at this level
496 | bounds = getEdges(cell.id(), cell.id());
497 | for (int i = bounds[0]; i < bounds[1]; i++) {
498 | candidateCrossings.add(edges[i]);
499 | }
500 | // Recurse on the children -- hopefully some will be empty.
501 | if (children == null) {
502 | children = new S2Cell[4];
503 | for (int i = 0; i < 4; ++i) {
504 | children[i] = new S2Cell();
505 | }
506 | }
507 | new S2Cell(cell).subdivide(children);
508 | for (S2Cell child : children) {
509 | // TODO(user): Do the check for the four cells at once,
510 | // as it is enough to check the four edges between the cells. At
511 | // this time, we are checking 16 edges, 4 times too many.
512 | //
513 | // Note that given the guarantee of AppendCovering, it is enough
514 | // to check that the edge intersect with the cell boundary as it
515 | // cannot be fully contained in a cell.
516 | if (edgeIntersectsCellBoundary(a, b, child)) {
517 | cover.add(child.id());
518 | }
519 | }
520 | }
521 | }
522 | }
523 | */
524 | }
525 |
526 | /*
527 | * An iterator on data edges that may cross a query edge (a,b). Create the
528 | * iterator, call getCandidates(), then hasNext()/next() repeatedly.
529 | *
530 | * The current edge in the iteration has index index(), goes between from()
531 | * and to().
532 | */
533 |
534 | class DataEdgeIterator {
535 | /**
536 | * The structure containing the data edges.
537 | */
538 | private $edgeIndex;
539 |
540 | /**
541 | * Tells whether getCandidates() obtained the candidates through brute force
542 | * iteration or using the quad tree structure.
543 | */
544 | private $isBruteForce;
545 |
546 | /**
547 | * Index of the current edge and of the edge before the last next() call.
548 | */
549 | private $currentIndex;
550 |
551 | /**
552 | * Cache of edgeIndex.getNumEdges() so that hasNext() doesn't make an extra
553 | * call
554 | */
555 | private $numEdges;
556 |
557 | /**
558 | * All the candidates obtained by getCandidates() when we are using a
559 | * quad-tree (i.e. isBruteForce = false).
560 | *#/
561 | ArrayList
562 | candidates;
563 |
564 | /**
565 | * Index within array above. We have: currentIndex =
566 | * candidates.get(currentIndexInCandidates).
567 | *#/
568 | private int currentIndexInCandidates;
569 |
570 | public DataEdgeIterator(S2EdgeIndex edgeIndex) {
571 | this.edgeIndex = edgeIndex;
572 | candidates = Lists.newArrayList();
573 | }
574 |
575 | /**
576 | * Initializes the iterator to iterate over a set of candidates that may
577 | * cross the edge (a,b).
578 | *#/
579 | public void getCandidates(S2Point a, S2Point b) {
580 | edgeIndex.predictAdditionalCalls(1);
581 | isBruteForce = !edgeIndex.isIndexComputed();
582 | if (isBruteForce) {
583 | edgeIndex.incrementQueryCount();
584 | currentIndex = 0;
585 | numEdges = edgeIndex.getNumEdges();
586 | } else {
587 | candidates.clear();
588 | edgeIndex.findCandidateCrossings(a, b, candidates);
589 | currentIndexInCandidates = 0;
590 | if (!candidates.isEmpty()) {
591 | currentIndex = candidates.get(0);
592 | }
593 | }
594 | }
595 |
596 | /**
597 | * Index of the current edge in the iteration.
598 | *#/
599 | public int index() {
600 | Preconditions.checkState(hasNext());
601 | return currentIndex;
602 | }
603 |
604 | /**
605 | * False if there are no more candidates; true otherwise.
606 | *#/
607 | public boolean hasNext() {
608 | if (isBruteForce) {
609 | return (currentIndex < numEdges);
610 | } else {
611 | return currentIndexInCandidates < candidates.size();
612 | }
613 | }
614 |
615 | /**
616 | * Iterate to the next available candidate.
617 | *#/
618 | public void next() {
619 | Preconditions.checkState(hasNext());
620 | if (isBruteForce) {
621 | ++currentIndex;
622 | } else {
623 | ++currentIndexInCandidates;
624 | if (currentIndexInCandidates < candidates.size()) {
625 | currentIndex = candidates.get(currentIndexInCandidates);
626 | }
627 | }
628 | }
629 | */
630 | }
631 |
--------------------------------------------------------------------------------
/S2RegionCoverer.php:
--------------------------------------------------------------------------------
1 | , entries of equal
57 | * priority would be sorted according to the memory address of the candidate.
58 | */
59 | /* static class QueueEntriesComparator implements Comparator {
60 | @Override
61 | public int compare(S2RegionCoverer.QueueEntry x, S2RegionCoverer.QueueEntry y) {
62 | return x.id < y.id ? 1 : (x.id > y.id ? -1 : 0);
63 | }
64 | }*/
65 |
66 |
67 | /**
68 | * We keep the candidates in a priority queue. We specify a vector to hold the
69 | * queue entries since for some reason priority_queue<> uses a deque by
70 | * default.
71 | */
72 | private $candidateQueue;
73 |
74 | /**
75 | * Default constructor, sets all fields to default values.
76 | */
77 | public function __construct() {
78 | $this->minLevel = 0;
79 | $this->maxLevel = S2CellId::MAX_LEVEL;
80 | $this->levelMod = 1;
81 | $this->maxCells = self::DEFAULT_MAX_CELLS;
82 | $this->region = null;
83 | $this->result = array();
84 | // TODO(kirilll?): 10 is a completely random number, work out a better
85 | // estimate
86 | // $this->candidateQueue = array();//new PriorityQueue(10, new QueueEntriesComparator());
87 | $this->candidateQueue = new SplPriorityQueue(); //new PriorityQueue(10, new QueueEntriesComparator());
88 | }
89 |
90 | // Set the minimum and maximum cell level to be used. The default is to use
91 | // all cell levels. Requires: max_level() >= min_level().
92 | //
93 | // To find the cell level corresponding to a given physical distance, use
94 | // the S2Cell metrics defined in s2.h. For example, to find the cell
95 | // level that corresponds to an average edge length of 10km, use:
96 | //
97 | // int level = S2::kAvgEdge.GetClosestLevel(
98 | // geostore::S2Earth::KmToRadians(length_km));
99 | //
100 | // Note: min_level() takes priority over max_cells(), i.e. cells below the
101 | // given level will never be used even if this causes a large number of
102 | // cells to be returned.
103 |
104 | /**
105 | * Sets the minimum level to be used.
106 | */
107 | public function setMinLevel($minLevel) {
108 | // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL);
109 | $this->minLevel = max(0, min(S2CellId::MAX_LEVEL, $minLevel));
110 | }
111 |
112 | /**
113 | * Sets the maximum level to be used.
114 | */
115 | public function setMaxLevel($maxLevel) {
116 | // assert (maxLevel >= 0 && maxLevel <= S2CellId.MAX_LEVEL);
117 | $this->maxLevel = max(0, min(S2CellId::MAX_LEVEL, $maxLevel));
118 | }
119 |
120 | public function minLevel() {
121 | return $this->minLevel;
122 | }
123 |
124 | public function maxLevel() {
125 | return $this->maxLevel;
126 | }
127 |
128 | // public int maxCells() {
129 | // return maxCells;
130 | // }
131 |
132 | /**
133 | * If specified, then only cells where (level - min_level) is a multiple of
134 | * "level_mod" will be used (default 1). This effectively allows the branching
135 | * factor of the S2CellId hierarchy to be increased. Currently the only
136 | * parameter values allowed are 1, 2, or 3, corresponding to branching factors
137 | * of 4, 16, and 64 respectively.
138 | */
139 | // public void setLevelMod(int levelMod) {
140 | // assert (levelMod >= 1 && levelMod <= 3);
141 | // this.levelMod = Math.max(1, Math.min(3, levelMod));
142 | // }
143 |
144 | public function levelMod() {
145 | return $this->levelMod;
146 | }
147 |
148 | /**
149 | * Sets the maximum desired number of cells in the approximation (defaults to
150 | * kDefaultMaxCells). Note the following:
151 | *
152 | *
153 | * - For any setting of max_cells(), up to 6 cells may be returned if that
154 | * is the minimum number of cells required (e.g. if the region intersects all
155 | * six face cells). Up to 3 cells may be returned even for very tiny convex
156 | * regions if they happen to be located at the intersection of three cube
157 | * faces.
158 | *
159 | *
- For any setting of max_cells(), an arbitrary number of cells may be
160 | * returned if min_level() is too high for the region being approximated.
161 | *
162 | *
- If max_cells() is less than 4, the area of the covering may be
163 | * arbitrarily large compared to the area of the original region even if the
164 | * region is convex (e.g. an S2Cap or S2LatLngRect).
165 | *
166 | *
167 | * Accuracy is measured by dividing the area of the covering by the area of
168 | * the original region. The following table shows the median and worst case
169 | * values for this area ratio on a test case consisting of 100,000 spherical
170 | * caps of random size (generated using s2regioncoverer_unittest):
171 | *
172 | *
173 | * max_cells: 3 4 5 6 8 12 20 100 1000
174 | * median ratio: 5.33 3.32 2.73 2.34 1.98 1.66 1.42 1.11 1.01
175 | * worst case: 215518 14.41 9.72 5.26 3.91 2.75 1.92 1.20 1.02
176 | *
177 | */
178 | // public void setMaxCells(int maxCells) {
179 | // this.maxCells = maxCells;
180 | // }
181 |
182 | /**
183 | * Computes a list of cell ids that covers the given region and satisfies the
184 | * various restrictions specified above.
185 | *
186 | * @param region The region to cover
187 | * @param S2CellId[] covering The list filled in by this method
188 | */
189 | public function getCovering(S2Region $region, &$covering) {
190 | // Rather than just returning the raw list of cell ids generated by
191 | // GetCoveringInternal(), we construct a cell union and then denormalize it.
192 | // This has the effect of replacing four child cells with their parent
193 | // whenever this does not violate the covering parameters specified
194 | // (min_level, level_mod, etc). This strategy significantly reduces the
195 | // number of cells returned in many cases, and it is cheap compared to
196 | // computing the covering in the first place.
197 |
198 | $tmp = new S2CellUnion();
199 |
200 | $this->interiorCovering = false;
201 | $this->getCoveringInternal($region);
202 | $tmp->initSwap($this->result);
203 |
204 | $tmp->denormalize($this->minLevel(), $this->levelMod(), $covering);
205 | }
206 |
207 | /**
208 | * Computes a list of cell ids that is contained within the given region and
209 | * satisfies the various restrictions specified above.
210 | *
211 | * @param region The region to fill
212 | * @param interior The list filled in by this method
213 | */
214 | // public void getInteriorCovering(S2Region region, ArrayList interior) {
215 | // S2CellUnion tmp = getInteriorCovering(region);
216 | // tmp.denormalize(minLevel(), levelMod(), interior);
217 | // }
218 |
219 | /**
220 | * Return a normalized cell union that is contained within the given region
221 | * and satisfies the restrictions *EXCEPT* for min_level() and level_mod().
222 | */
223 | // public S2CellUnion getInteriorCovering(S2Region region) {
224 | // S2CellUnion covering = new S2CellUnion();
225 | // getInteriorCovering(region, covering);
226 | // return covering;
227 | // }
228 |
229 | // public void getInteriorCovering(S2Region region, S2CellUnion covering) {
230 | // interiorCovering = true;
231 | // getCoveringInternal(region);
232 | // covering.initSwap(result);
233 | // }
234 |
235 | /**
236 | * Given a connected region and a starting point, return a set of cells at the
237 | * given level that cover the region.
238 | */
239 | // public static void getSimpleCovering(
240 | // S2Region region, S2Point start, int level, ArrayList output) {
241 | // floodFill(region, S2CellId.fromPoint(start).parent(level), output);
242 | // }
243 |
244 | /**
245 | * If the cell intersects the given region, return a new candidate with no
246 | * children, otherwise return null. Also marks the candidate as "terminal" if
247 | * it should not be expanded further.
248 | */
249 | private function newCandidate(S2Cell $cell) {
250 | if (!$this->region->mayIntersect($cell)) {
251 | // echo "null\n";
252 | return null;
253 | }
254 |
255 | $isTerminal = false;
256 | if ($cell->level() >= $this->minLevel) {
257 | if ($this->interiorCovering) {
258 | if ($this->region->contains($cell)) {
259 | $isTerminal = true;
260 | } else if ($cell->level() + $this->levelMod > $this->maxLevel) {
261 | return null;
262 | }
263 | } else {
264 | if ($cell->level() + $this->levelMod > $this->maxLevel || $this->region->contains($cell)) {
265 | $isTerminal = true;
266 | }
267 | }
268 | }
269 | $candidate = new Candidate();
270 | $candidate->cell = $cell;
271 | $candidate->isTerminal = $isTerminal;
272 | if (!$isTerminal) {
273 | $candidate->children = array_pad(array(), 1 << $this->maxChildrenShift(), new Candidate);
274 | }
275 | $this->candidatesCreatedCounter++;
276 | return $candidate;
277 | }
278 |
279 | /** Return the log base 2 of the maximum number of children of a candidate. */
280 | private function maxChildrenShift() {
281 | return 2 * $this->levelMod;
282 | }
283 |
284 | /**
285 | * Process a candidate by either adding it to the result list or expanding its
286 | * children and inserting it into the priority queue. Passing an argument of
287 | * NULL does nothing.
288 | */
289 | private function addCandidate(Candidate $candidate = null) {
290 | if ($candidate == null) {
291 | // echo "\t addCandidate null\n";
292 | return;
293 | }
294 |
295 | if ($candidate->isTerminal) {
296 | // echo "\taddCandidato terminal: " . $candidate->cell->id() . "\n";
297 | $this->result[] = $candidate->cell->id();
298 | return;
299 | }
300 | // assert (candidate.numChildren == 0);
301 |
302 | // Expand one level at a time until we hit min_level_ to ensure that
303 | // we don't skip over it.
304 | $numLevels = ($candidate->cell->level() < $this->minLevel) ? 1 : $this->levelMod;
305 | $numTerminals = $this->expandChildren($candidate, $candidate->cell, $numLevels);
306 |
307 | // var_dump($candidate->numChildren);
308 |
309 | if ($candidate->numChildren == 0) {
310 | // echo "\tcandidate numChildred is zero\n";
311 | // Do nothing
312 | } else if (!$this->interiorCovering && $numTerminals == 1 << $this->maxChildrenShift()
313 | && $candidate->cell->level() >= $this->minLevel) {
314 | // Optimization: add the parent cell rather than all of its children.
315 | // We can't do this for interior coverings, since the children just
316 | // intersect the region, but may not be contained by it - we need to
317 | // subdivide them further.
318 | $candidate->isTerminal = true;
319 | echo "addCandidato recurse: " . $candidate->cell->id() . "\n";
320 | $this->addCandidate($candidate);
321 | } else {
322 | // We negate the priority so that smaller absolute priorities are returned
323 | // first. The heuristic is designed to refine the largest cells first,
324 | // since those are where we have the largest potential gain. Among cells
325 | // at the same level, we prefer the cells with the smallest number of
326 | // intersecting children. Finally, we prefer cells that have the smallest
327 | // number of children that cannot be refined any further.
328 | $priority = -(((($candidate->cell->level() << $this->maxChildrenShift()) + $candidate->numChildren) << $this->maxChildrenShift()) + $numTerminals);
329 | // echo "Push: " . $candidate . " ($priority)\n";
330 | $this->candidateQueue->insert($candidate, $priority);
331 | // logger.info("Push: " + candidate.cell.id() + " (" + priority + ") ");
332 | }
333 | }
334 |
335 | /**
336 | * Populate the children of "candidate" by expanding the given number of
337 | * levels from the given cell. Returns the number of children that were marked
338 | * "terminal".
339 | */
340 | private function expandChildren(Candidate $candidate, S2Cell $cell, $numLevels) {
341 | $numLevels--;
342 | $childCells = array();
343 | for ($i = 0; $i < 4; ++$i) {
344 | $childCells[$i] = new S2Cell();
345 | }
346 | $cell->subdivide($childCells);
347 | $numTerminals = 0;
348 | for ($i = 0; $i < 4; ++$i) {
349 | if ($numLevels > 0) {
350 | if ($this->region->mayIntersect($childCells[$i])) {
351 | $numTerminals += $this->expandChildren($candidate, $childCells[$i], $numLevels);
352 | }
353 | continue;
354 | }
355 | $child = $this->newCandidate($childCells[$i]);
356 | // echo "child for " . $childCells[$i] . " is " . $child . "\n";
357 |
358 | if ($child != null) {
359 | $candidate->children[$candidate->numChildren++] = $child;
360 | if ($child->isTerminal) {
361 | ++$numTerminals;
362 | }
363 | }
364 | }
365 | return $numTerminals;
366 | }
367 |
368 | /** Computes a set of initial candidates that cover the given region. */
369 | private function getInitialCandidates() {
370 | // Optimization: if at least 4 cells are desired (the normal case),
371 | // start with a 4-cell covering of the region's bounding cap. This
372 | // lets us skip quite a few levels of refinement when the region to
373 | // be covered is relatively small.
374 | if ($this->maxCells >= 4) {
375 | // Find the maximum level such that the bounding cap contains at most one
376 | // cell vertex at that level.
377 | $cap = $this->region->getCapBound();
378 | $level = min(
379 | S2Projections::MIN_WIDTH()->getMaxLevel(2 * $cap->angle()->radians()),
380 | min($this->maxLevel(), S2CellId::MAX_LEVEL - 1)
381 | );
382 | if ($this->levelMod() > 1 && $level > $this->minLevel()) {
383 | $level -= ($level - $this->minLevel()) % $this->levelMod();
384 | }
385 | // We don't bother trying to optimize the level == 0 case, since more than
386 | // four face cells may be required.
387 | if ($level > 0) {
388 | // Find the leaf cell containing the cap axis, and determine which
389 | // subcell of the parent cell contains it.
390 | /** @var S2CellId[] $base */
391 | $base = array();
392 |
393 | $s2point_tmp = $cap->axis();
394 |
395 | $id = S2CellId::fromPoint($s2point_tmp);
396 | $id->getVertexNeighbors($level, $base);
397 | for ($i = 0; $i < count($base); ++$i) {
398 |
399 | // printf("(face=%s pos=%s level=%s)\n", $base[$i]->face(), dechex($base[$i]->pos()), $base[$i]->level());
400 | // echo "new S2Cell(base[i])\n";
401 | $cell = new S2Cell($base[$i]);
402 | // echo "neighbour cell: " . $cell . "\n";
403 | $c = $this->newCandidate($cell);
404 | // if ($c !== null)
405 | // echo "addCandidato getInitialCandidates: " . $c->cell->id() . "\n";
406 | $this->addCandidate($c);
407 | }
408 |
409 | // echo "\n\n\n";
410 |
411 | return;
412 | }
413 | }
414 | // Default: start with all six cube faces.
415 | $face_cells = self::FACE_CELLS();
416 | for ($face = 0; $face < 6; ++$face) {
417 | $c = $this->newCandidate($face_cells[$face]);
418 | echo "addCandidato getInitialCandidates_default: " . $c->cell->id() . "\n";
419 | $this->addCandidate($c);
420 | }
421 | }
422 |
423 | /** Generates a covering and stores it in result. */
424 | private function getCoveringInternal(S2Region $region) {
425 | // Strategy: Start with the 6 faces of the cube. Discard any
426 | // that do not intersect the shape. Then repeatedly choose the
427 | // largest cell that intersects the shape and subdivide it.
428 | //
429 | // result contains the cells that will be part of the output, while the
430 | // priority queue contains cells that we may still subdivide further. Cells
431 | // that are entirely contained within the region are immediately added to
432 | // the output, while cells that do not intersect the region are immediately
433 | // discarded.
434 | // Therefore pq_ only contains cells that partially intersect the region.
435 | // Candidates are prioritized first according to cell size (larger cells
436 | // first), then by the number of intersecting children they have (fewest
437 | // children first), and then by the number of fully contained children
438 | // (fewest children first).
439 |
440 | $tmp1 = $this->candidateQueue->isEmpty();
441 | if (!($tmp1 && count($this->result) == 0)) throw new Exception();
442 |
443 | $this->region = $region;
444 | $this->candidatesCreatedCounter = 0;
445 |
446 | $this->getInitialCandidates();
447 | while (!$this->candidateQueue->isEmpty() && (!$this->interiorCovering || $this->result->size() < $this->maxCells)) {
448 | $candidate = $this->candidateQueue->extract();
449 |
450 | // logger.info("Pop: " + candidate.cell.id());
451 | // echo "Pop: " . $candidate . "\n";
452 | if ($candidate->cell->level() < $this->minLevel || $candidate->numChildren == 1
453 | || $this->result->size() + ($this->interiorCovering ? 0 : $this->candidateQueue->size()) + $candidate->numChildren <= $this->maxCells) {
454 | // Expand this candidate into its children.
455 | for ($i = 0; $i < $candidate->numChildren; ++$i) {
456 | $c = $candidate->children[$i];
457 | // echo "call addCandidate on $c\n";
458 | $this->addCandidate($c);
459 | }
460 | } else if ($this->interiorCovering) {
461 | // Do nothing
462 | } else {
463 | $candidate->isTerminal = true;
464 | $this->addCandidate($candidate);
465 | }
466 | }
467 |
468 | unset($this->candidateQueue);
469 | $this->candidateQueue = new SplPriorityQueue();
470 | $this->region = null;
471 | }
472 |
473 | /**
474 | * Given a region and a starting cell, return the set of all the
475 | * edge-connected cells at the same level that intersect "region". The output
476 | * cells are returned in arbitrary order.
477 | *#/
478 | * private static void floodFill(S2Region region, S2CellId start, ArrayList output) {
479 | * HashSet all = new HashSet();
480 | * ArrayList frontier = new ArrayList();
481 | * output.clear();
482 | * all.add(start);
483 | * frontier.add(start);
484 | * while (!frontier.isEmpty()) {
485 | * S2CellId id = frontier.get(frontier.size() - 1);
486 | * frontier.remove(frontier.size() - 1);
487 | * if (!region.mayIntersect(new S2Cell(id))) {
488 | * continue;
489 | * }
490 | * output.add(id);
491 | *
492 | * S2CellId[] neighbors = new S2CellId[4];
493 | * id.getEdgeNeighbors(neighbors);
494 | * for (int edge = 0; edge < 4; ++edge) {
495 | * S2CellId nbr = neighbors[edge];
496 | * boolean hasNbr = all.contains(nbr);
497 | * if (!all.contains(nbr)) {
498 | * frontier.add(nbr);
499 | * all.add(nbr);
500 | * }
501 | * }
502 | * }
503 | * }
504 | *
505 | */
506 | }
507 |
508 | class Candidate {
509 | public $cell;
510 | public $isTerminal; // Cell should not be expanded further.
511 | public $numChildren = 0; // Number of children that intersect the region.
512 | public $children; // Actual size may be 0, 4, 16, or 64 elements.
513 | public function __toString() {
514 | return sprintf("[%s t:%s n:%d]", $this->cell, $this->isTerminal ? 'true' : 'false', $this->numChildren);
515 | }
516 | }
517 |
--------------------------------------------------------------------------------
/S2CellUnion.php:
--------------------------------------------------------------------------------
1 | cellIds) {
11 | initRawCellIds(cellIds);
12 | normalize();
13 | }
14 |
15 | /**
16 | * Populates a cell union with the given S2CellIds or 64-bit cells ids, and
17 | * then calls Normalize(). The InitSwap() version takes ownership of the
18 | * vector data without copying and clears the given vector. These methods may
19 | * be called multiple times.
20 | *#/
21 | public void initFromIds(ArrayList cellIds) {
22 | initRawIds(cellIds);
23 | normalize();
24 | }
25 | */
26 | public function initSwap($cellIds) {
27 | $this->initRawSwap($cellIds);
28 | $this->normalize();
29 | }
30 |
31 | /*
32 | public void initRawCellIds(ArrayList cellIds) {
33 | this.cellIds = cellIds;
34 | }
35 |
36 | public void initRawIds(ArrayList cellIds) {
37 | int size = cellIds.size();
38 | this.cellIds = new ArrayList(size);
39 | for (Long id : cellIds) {
40 | this.cellIds.add(new S2CellId(id));
41 | }
42 | }
43 |
44 | /**
45 | * Like Init(), but does not call Normalize(). The cell union *must* be
46 | * normalized before doing any calculations with it, so it is the caller's
47 | * responsibility to make sure that the input is normalized. This method is
48 | * useful when converting cell unions to another representation and back.
49 | * These methods may be called multiple times.
50 | */
51 | public function initRawSwap($cellIds) {
52 | $this->cellIds = $cellIds;
53 | // cellIds.clear();
54 | }
55 |
56 | public function size() {
57 | return count($this->cellIds);
58 | }
59 |
60 | /** Convenience methods for accessing the individual cell ids. *#/
61 | * public S2CellId cellId(int i) {
62 | * return cellIds.get(i);
63 | * }
64 | *
65 | * /** Enable iteration over the union's cells. *#/
66 | * @Override
67 | * public Iterator iterator() {
68 | * return cellIds.iterator();
69 | * }
70 | *
71 | * /** Direct access to the underlying vector for iteration . *#/
72 | * public ArrayList cellIds() {
73 | * return cellIds;
74 | * }
75 | *
76 | * /**
77 | * Replaces "output" with an expanded version of the cell union where any
78 | * cells whose level is less than "min_level" or where (level - min_level) is
79 | * not a multiple of "level_mod" are replaced by their children, until either
80 | * both of these conditions are satisfied or the maximum level is reached.
81 | *
82 | * This method allows a covering generated by S2RegionCoverer using
83 | * min_level() or level_mod() constraints to be stored as a normalized cell
84 | * union (which allows various geometric computations to be done) and then
85 | * converted back to the original list of cell ids that satisfies the desired
86 | * constraints.
87 | */
88 | public function denormalize($minLevel, $levelMod, &$output) {
89 | // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL);
90 | // assert (levelMod >= 1 && levelMod <= 3);
91 |
92 | $output = array();
93 | /** @var $id S2CellId */
94 | foreach ($this->cellIds as $id) {
95 | $level = $id->level();
96 | $newLevel = max($minLevel, $level);
97 | if ($levelMod > 1) {
98 | // Round up so that (new_level - min_level) is a multiple of level_mod.
99 | // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.)
100 | $newLevel += (S2CellId::MAX_LEVEL - ($newLevel - $minLevel)) % $levelMod;
101 | $newLevel = min(S2CellId::MAX_LEVEL, $newLevel);
102 | }
103 | if ($newLevel == $level) {
104 | $output[] = $id;
105 | } else {
106 | $end = $id->childEnd($newLevel);
107 | for ($id = $id->childBegin($newLevel); !$id->equals($end); $id = $id->next()) {
108 | $output[] = $id;
109 | }
110 | }
111 | }
112 | }
113 |
114 | /**
115 | * If there are more than "excess" elements of the cell_ids() vector that are
116 | * allocated but unused, reallocate the array to eliminate the excess space.
117 | * This reduces memory usage when many cell unions need to be held in memory
118 | * at once.
119 | *#/
120 | * public void pack() {
121 | * cellIds.trimToSize();
122 | * }
123 | *
124 | *
125 | * /**
126 | * Return true if the cell union contains the given cell id. Containment is
127 | * defined with respect to regions, e.g. a cell contains its 4 children. This
128 | * is a fast operation (logarithmic in the size of the cell union).
129 | *#/
130 | * public boolean contains(S2CellId id) {
131 | * // This function requires that Normalize has been called first.
132 | * //
133 | * // This is an exact test. Each cell occupies a linear span of the S2
134 | * // space-filling curve, and the cell id is simply the position at the center
135 | * // of this span. The cell union ids are sorted in increasing order along
136 | * // the space-filling curve. So we simply find the pair of cell ids that
137 | * // surround the given cell id (using binary search). There is containment
138 | * // if and only if one of these two cell ids contains this cell.
139 | *
140 | * int pos = Collections.binarySearch(cellIds, id);
141 | * if (pos < 0) {
142 | * pos = -pos - 1;
143 | * }
144 | * if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id)) {
145 | * return true;
146 | * }
147 | * return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id);
148 | * }
149 | *
150 | * /**
151 | * Return true if the cell union intersects the given cell id. This is a fast
152 | * operation (logarithmic in the size of the cell union).
153 | *#/
154 | * public boolean intersects(S2CellId id) {
155 | * // This function requires that Normalize has been called first.
156 | * // This is an exact test; see the comments for Contains() above.
157 | * int pos = Collections.binarySearch(cellIds, id);
158 | *
159 | * if (pos < 0) {
160 | * pos = -pos - 1;
161 | * }
162 | *
163 | *
164 | * if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id.rangeMax())) {
165 | * return true;
166 | * }
167 | * return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id.rangeMin());
168 | * }
169 | *
170 | * public boolean contains(S2CellUnion that) {
171 | * // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach
172 | * // may be significantly faster in both the average and worst case.
173 | * for (S2CellId id : that) {
174 | * if (!this.contains(id)) {
175 | * return false;
176 | * }
177 | * }
178 | * return true;
179 | * }
180 | *
181 | * /** This is a fast operation (logarithmic in the size of the cell union). *#/
182 | * @Override
183 | * public boolean contains(S2Cell cell) {
184 | * return contains(cell.id());
185 | * }
186 | *
187 | * /**
188 | * Return true if this cell union contain/intersects the given other cell
189 | * union.
190 | *#/
191 | * public boolean intersects(S2CellUnion union) {
192 | * // TODO(kirilll?): A divide-and-conquer or alternating-skip-search approach
193 | * // may be significantly faster in both the average and worst case.
194 | * for (S2CellId id : union) {
195 | * if (intersects(id)) {
196 | * return true;
197 | * }
198 | * }
199 | * return false;
200 | * }
201 | *
202 | * public void getUnion(S2CellUnion x, S2CellUnion y) {
203 | * // assert (x != this && y != this);
204 | * cellIds.clear();
205 | * cellIds.ensureCapacity(x.size() + y.size());
206 | * cellIds.addAll(x.cellIds);
207 | * cellIds.addAll(y.cellIds);
208 | * normalize();
209 | * }
210 | *
211 | * /**
212 | * Specialized version of GetIntersection() that gets the intersection of a
213 | * cell union with the given cell id. This can be useful for "splitting" a
214 | * cell union into chunks.
215 | *#/
216 | * public void getIntersection(S2CellUnion x, S2CellId id) {
217 | * // assert (x != this);
218 | * cellIds.clear();
219 | * if (x.contains(id)) {
220 | * cellIds.add(id);
221 | * } else {
222 | * int pos = Collections.binarySearch(x.cellIds, id.rangeMin());
223 | *
224 | * if (pos < 0) {
225 | * pos = -pos - 1;
226 | * }
227 | *
228 | * S2CellId idmax = id.rangeMax();
229 | * int size = x.cellIds.size();
230 | * while (pos < size && x.cellIds.get(pos).lessOrEquals(idmax)) {
231 | * cellIds.add(x.cellIds.get(pos++));
232 | * }
233 | * }
234 | * }
235 | *
236 | * /**
237 | * Initialize this cell union to the union or intersection of the two given
238 | * cell unions. Requires: x != this and y != this.
239 | *#/
240 | * public void getIntersection(S2CellUnion x, S2CellUnion y) {
241 | * // assert (x != this && y != this);
242 | *
243 | * // This is a fairly efficient calculation that uses binary search to skip
244 | * // over sections of both input vectors. It takes constant time if all the
245 | * // cells of "x" come before or after all the cells of "y" in S2CellId order.
246 | *
247 | * cellIds.clear();
248 | *
249 | * int i = 0;
250 | * int j = 0;
251 | *
252 | * while (i < x.cellIds.size() && j < y.cellIds.size()) {
253 | * S2CellId imin = x.cellId(i).rangeMin();
254 | * S2CellId jmin = y.cellId(j).rangeMin();
255 | * if (imin.greaterThan(jmin)) {
256 | * // Either j->contains(*i) or the two cells are disjoint.
257 | * if (x.cellId(i).lessOrEquals(y.cellId(j).rangeMax())) {
258 | * cellIds.add(x.cellId(i++));
259 | * } else {
260 | * // Advance "j" to the first cell possibly contained by *i.
261 | * j = indexedBinarySearch(y.cellIds, imin, j + 1);
262 | * // The previous cell *(j-1) may now contain *i.
263 | * if (x.cellId(i).lessOrEquals(y.cellId(j - 1).rangeMax())) {
264 | * --j;
265 | * }
266 | * }
267 | * } else if (jmin.greaterThan(imin)) {
268 | * // Identical to the code above with "i" and "j" reversed.
269 | * if (y.cellId(j).lessOrEquals(x.cellId(i).rangeMax())) {
270 | * cellIds.add(y.cellId(j++));
271 | * } else {
272 | * i = indexedBinarySearch(x.cellIds, jmin, i + 1);
273 | * if (y.cellId(j).lessOrEquals(x.cellId(i - 1).rangeMax())) {
274 | * --i;
275 | * }
276 | * }
277 | * } else {
278 | * // "i" and "j" have the same range_min(), so one contains the other.
279 | * if (x.cellId(i).lessThan(y.cellId(j))) {
280 | * cellIds.add(x.cellId(i++));
281 | * } else {
282 | * cellIds.add(y.cellId(j++));
283 | * }
284 | * }
285 | * }
286 | * // The output is generated in sorted order, and there should not be any
287 | * // cells that can be merged (provided that both inputs were normalized).
288 | * // assert (!normalize());
289 | * }
290 | *
291 | * /**
292 | * Just as normal binary search, except that it allows specifying the starting
293 | * value for the lower bound.
294 | *
295 | * @return The position of the searched element in the list (if found), or the
296 | * position where the element could be inserted without violating the
297 | * order.
298 | *#/
299 | * private int indexedBinarySearch(List l, S2CellId key, int low) {
300 | * int high = l.size() - 1;
301 | *
302 | * while (low <= high) {
303 | * int mid = (low + high) >> 1;
304 | * S2CellId midVal = l.get(mid);
305 | * int cmp = midVal.compareTo(key);
306 | *
307 | * if (cmp < 0) {
308 | * low = mid + 1;
309 | * } else if (cmp > 0) {
310 | * high = mid - 1;
311 | * } else {
312 | * return mid; // key found
313 | * }
314 | * }
315 | * return low; // key not found
316 | * }
317 | *
318 | * /**
319 | * Expands the cell union such that it contains all cells of the given level
320 | * that are adjacent to any cell of the original union. Two cells are defined
321 | * as adjacent if their boundaries have any points in common, i.e. most cells
322 | * have 8 adjacent cells (not counting the cell itself).
323 | *
324 | * Note that the size of the output is exponential in "level". For example,
325 | * if level == 20 and the input has a cell at level 10, there will be on the
326 | * order of 4000 adjacent cells in the output. For most applications the
327 | * Expand(min_fraction, min_distance) method below is easier to use.
328 | *#/
329 | * public void expand(int level) {
330 | * ArrayList output = new ArrayList();
331 | * long levelLsb = S2CellId.lowestOnBitForLevel(level);
332 | * int i = size() - 1;
333 | * do {
334 | * S2CellId id = cellId(i);
335 | * if (id.lowestOnBit() < levelLsb) {
336 | * id = id.parent(level);
337 | * // Optimization: skip over any cells contained by this one. This is
338 | * // especially important when very small regions are being expanded.
339 | * while (i > 0 && id.contains(cellId(i - 1))) {
340 | * --i;
341 | * }
342 | * }
343 | * output.add(id);
344 | * id.getAllNeighbors(level, output);
345 | * } while (--i >= 0);
346 | * initSwap(output);
347 | * }
348 | *
349 | * /**
350 | * Expand the cell union such that it contains all points whose distance to
351 | * the cell union is at most minRadius, but do not use cells that are more
352 | * than maxLevelDiff levels higher than the largest cell in the input. The
353 | * second parameter controls the tradeoff between accuracy and output size
354 | * when a large region is being expanded by a small amount (e.g. expanding
355 | * Canada by 1km).
356 | *
357 | * For example, if maxLevelDiff == 4, the region will always be expanded by
358 | * approximately 1/16 the width of its largest cell. Note that in the worst
359 | * case, the number of cells in the output can be up to 4 * (1 + 2 **
360 | * maxLevelDiff) times larger than the number of cells in the input.
361 | *#/
362 | * public void expand(S1Angle minRadius, int maxLevelDiff) {
363 | * int minLevel = S2CellId.MAX_LEVEL;
364 | * for (S2CellId id : this) {
365 | * minLevel = Math.min(minLevel, id.level());
366 | * }
367 | * // Find the maximum level such that all cells are at least "min_radius"
368 | * // wide.
369 | * int radiusLevel = S2Projections.MIN_WIDTH.getMaxLevel(minRadius.radians());
370 | * if (radiusLevel == 0 && minRadius.radians() > S2Projections.MIN_WIDTH.getValue(0)) {
371 | * // The requested expansion is greater than the width of a face cell.
372 | * // The easiest way to handle this is to expand twice.
373 | * expand(0);
374 | * }
375 | * expand(Math.min(minLevel + maxLevelDiff, radiusLevel));
376 | * }
377 | *
378 | * @Override
379 | * public S2Region clone() {
380 | * S2CellUnion copy = new S2CellUnion();
381 | * copy.initRawCellIds(Lists.newArrayList(cellIds));
382 | * return copy;
383 | * }
384 | *
385 | * @Override
386 | * public S2Cap getCapBound() {
387 | * // Compute the approximate centroid of the region. This won't produce the
388 | * // bounding cap of minimal area, but it should be close enough.
389 | * if (cellIds.isEmpty()) {
390 | * return S2Cap.empty();
391 | * }
392 | * S2Point centroid = new S2Point(0, 0, 0);
393 | * for (S2CellId id : this) {
394 | * double area = S2Cell.averageArea(id.level());
395 | * centroid = S2Point.add(centroid, S2Point.mul(id.toPoint(), area));
396 | * }
397 | * if (centroid.equals(new S2Point(0, 0, 0))) {
398 | * centroid = new S2Point(1, 0, 0);
399 | * } else {
400 | * centroid = S2Point.normalize(centroid);
401 | * }
402 | *
403 | * // Use the centroid as the cap axis, and expand the cap angle so that it
404 | * // contains the bounding caps of all the individual cells. Note that it is
405 | * // *not* sufficient to just bound all the cell vertices because the bounding
406 | * // cap may be concave (i.e. cover more than one hemisphere).
407 | * S2Cap cap = S2Cap.fromAxisHeight(centroid, 0);
408 | * for (S2CellId id : this) {
409 | * cap = cap.addCap(new S2Cell(id).getCapBound());
410 | * }
411 | * return cap;
412 | * }
413 | *
414 | * @Override
415 | * public S2LatLngRect getRectBound() {
416 | * S2LatLngRect bound = S2LatLngRect.empty();
417 | * for (S2CellId id : this) {
418 | * bound = bound.union(new S2Cell(id).getRectBound());
419 | * }
420 | * return bound;
421 | * }
422 | *
423 | *
424 | * /** This is a fast operation (logarithmic in the size of the cell union). *#/
425 | * @Override
426 | * public boolean mayIntersect(S2Cell cell) {
427 | * return intersects(cell.id());
428 | * }
429 | *
430 | * /**
431 | * The point 'p' does not need to be normalized. This is a fast operation
432 | * (logarithmic in the size of the cell union).
433 | *#/
434 | * public boolean contains(S2Point p) {
435 | * return contains(S2CellId.fromPoint(p));
436 | *
437 | * }
438 | *
439 | * /**
440 | * The number of leaf cells covered by the union.
441 | * This will be no more than 6*2^60 for the whole sphere.
442 | *
443 | * @return the number of leaf cells covered by the union
444 | *#/
445 | * public long leafCellsCovered() {
446 | * long numLeaves = 0;
447 | * for (S2CellId cellId : cellIds) {
448 | * int invertedLevel = S2CellId.MAX_LEVEL - cellId.level();
449 | * numLeaves += (1L << (invertedLevel << 1));
450 | * }
451 | * return numLeaves;
452 | * }
453 | *
454 | *
455 | * /**
456 | * Approximate this cell union's area by summing the average area of
457 | * each contained cell's average area, using {@link S2Cell#averageArea()}.
458 | * This is equivalent to the number of leaves covered, multiplied by
459 | * the average area of a leaf.
460 | * Note that {@link S2Cell#averageArea()} does not take into account
461 | * distortion of cell, and thus may be off by up to a factor of 1.7.
462 | * NOTE: Since this is proportional to LeafCellsCovered(), it is
463 | * always better to use the other function if all you care about is
464 | * the relative average area between objects.
465 | *
466 | * @return the sum of the average area of each contained cell's average area
467 | *#/
468 | * public double averageBasedArea() {
469 | * return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered();
470 | * }
471 | *
472 | * /**
473 | * Calculates this cell union's area by summing the approximate area for each
474 | * contained cell, using {@link S2Cell#approxArea()}.
475 | *
476 | * @return approximate area of the cell union
477 | *#/
478 | * public double approxArea() {
479 | * double area = 0;
480 | * for (S2CellId cellId : cellIds) {
481 | * area += new S2Cell(cellId).approxArea();
482 | * }
483 | * return area;
484 | * }
485 | *
486 | * /**
487 | * Calculates this cell union's area by summing the exact area for each
488 | * contained cell, using the {@link S2Cell#exactArea()}.
489 | *
490 | * @return the exact area of the cell union
491 | *#/
492 | * public double exactArea() {
493 | * double area = 0;
494 | * for (S2CellId cellId : cellIds) {
495 | * area += new S2Cell(cellId).exactArea();
496 | * }
497 | * return area;
498 | * }
499 | *
500 | * /** Return true if two cell unions are identical. *#/
501 | * @Override
502 | * public boolean equals(Object that) {
503 | * if (!(that instanceof S2CellUnion)) {
504 | * return false;
505 | * }
506 | * S2CellUnion union = (S2CellUnion) that;
507 | * return this.cellIds.equals(union.cellIds);
508 | * }
509 | *
510 | * @Override
511 | * public int hashCode() {
512 | * int value = 17;
513 | * for (S2CellId id : this) {
514 | * value = 37 * value + id.hashCode();
515 | * }
516 | * return value;
517 | * }
518 | *
519 | * /**
520 | * Normalizes the cell union by discarding cells that are contained by other
521 | * cells, replacing groups of 4 child cells by their parent cell whenever
522 | * possible, and sorting all the cell ids in increasing order. Returns true if
523 | * the number of cells was reduced.
524 | *
525 | * This method *must* be called before doing any calculations on the cell
526 | * union, such as Intersects() or Contains().
527 | *
528 | * @return true if the normalize operation had any effect on the cell union,
529 | * false if the union was already normalized
530 | */
531 | public function normalize() {
532 | // Optimize the representation by looking for cases where all subcells
533 | // of a parent cell are present.
534 |
535 | /** @var S2CellId[] $output */
536 | $output = array();
537 | sort($this->cellIds);
538 |
539 | // echo "\n\n\n";
540 |
541 | // foreach ($this->cellIds as $id) {
542 | // echo $id . "\n";
543 | // }
544 |
545 | foreach ($this->cellIds as $id) {
546 | $size = count($output);
547 | // Check whether this cell is contained by the previous cell.
548 | if ($size && $output[$size - 1]->contains($id)) {
549 | continue;
550 | }
551 |
552 | // Discard any previous cells contained by this cell.
553 | while (!empty($output) && $id->contains($output[count($output) - 1])) {
554 | unset($output[count($output) - 1]);
555 | }
556 |
557 | // Check whether the last 3 elements of "output" plus "id" can be
558 | // collapsed into a single parent cell.
559 | while (count($output) >= 3) {
560 | $size = count($output);
561 | // A necessary (but not sufficient) condition is that the XOR of the
562 | // four cells must be zero. This is also very fast to test.
563 | if (($output[$size - 3]->id() ^ $output[$size - 2]->id() ^ $output[$size - 1]->id()) != $id->id()) {
564 | break;
565 | }
566 |
567 | // Now we do a slightly more expensive but exact test. First, compute a
568 | // mask that blocks out the two bits that encode the child position of
569 | // "id" with respect to its parent, then check that the other three
570 | // children all agree with "mask.
571 | $mask = $id->lowestOnBit() << 1;
572 | $mask = ~($mask + ($mask << 1));
573 | $idMasked = ($id->id() & $mask);
574 | if (($output[$size - 3]->id() & $mask) != $idMasked
575 | || ($output[$size - 2]->id() & $mask) != $idMasked
576 | || ($output[$size - 1]->id() & $mask) != $idMasked || $id->isFace()) {
577 | break;
578 | }
579 |
580 | // Replace four children by their parent cell.
581 | unset($output[$size - 1]);
582 | unset($output[$size - 2]);
583 | unset($output[$size - 3]);
584 | $id = $id->parent();
585 | }
586 | $size = count($output);
587 | $output[$size] = $id;
588 | }
589 |
590 | // echo "===\n";
591 | // foreach ($output as $id) {
592 | // echo $id . "\n";
593 | // }
594 | // echo "\n";
595 |
596 | if (count($output) < $this->size()) {
597 | $this->initRawSwap($output);
598 | return true;
599 | }
600 | return false;
601 | }
602 | }
603 |
--------------------------------------------------------------------------------
/S2PolygonBuilder.php:
--------------------------------------------------------------------------------
1 | > edges;
42 |
43 | /**
44 | * Default constructor for well-behaved polygons. Uses the DIRECTED_XOR
45 | * options.
46 | *#/
47 | public S2PolygonBuilder() {
48 | this(Options.DIRECTED_XOR);
49 |
50 | }
51 |
52 | public S2PolygonBuilder(Options options) {
53 | this.options = options;
54 | this.edges = Maps.newHashMap();
55 | }
56 |
57 | public enum Options {
58 |
59 | /**
60 | * These are the options that should be used for assembling well-behaved
61 | * input data into polygons. All edges should be directed such that "shells"
62 | * and "holes" have opposite orientations (typically CCW shells and
63 | * clockwise holes), unless it is known that shells and holes do not share
64 | * any edges.
65 | *#/
66 | DIRECTED_XOR(false, true),
67 |
68 | /**
69 | * These are the options that should be used for assembling polygons that do
70 | * not follow the conventions above, e.g. where edge directions may vary
71 | * within a single loop, or shells and holes are not oppositely oriented.
72 | *#/
73 | UNDIRECTED_XOR(true, true),
74 |
75 | /**
76 | * These are the options that should be used for assembling edges where the
77 | * desired output is a collection of loops rather than a polygon, and edges
78 | * may occur more than once. Edges are treated as undirected and are not
79 | * XORed together, in particular, adding edge A->B also adds B->A.
80 | *#/
81 | UNDIRECTED_UNION(true, false),
82 |
83 | /**
84 | * Finally, select this option when the desired output is a collection of
85 | * loops rather than a polygon, but your input edges are directed and you do
86 | * not want reverse edges to be added implicitly as above.
87 | *#/
88 | DIRECTED_UNION(false, false);
89 |
90 | private boolean undirectedEdges;
91 | private boolean xorEdges;
92 | private boolean validate;
93 | private S1Angle mergeDistance;
94 |
95 | private Options(boolean undirectedEdges, boolean xorEdges) {
96 | this.undirectedEdges = undirectedEdges;
97 | this.xorEdges = xorEdges;
98 | this.validate = false;
99 | this.mergeDistance = S1Angle.radians(0);
100 | }
101 |
102 | /**
103 | * If "undirected_edges" is false, then the input is assumed to consist of
104 | * edges that can be assembled into oriented loops without reversing any of
105 | * the edges. Otherwise, "undirected_edges" should be set to true.
106 | *#/
107 | public boolean getUndirectedEdges() {
108 | return undirectedEdges;
109 | }
110 |
111 | /**
112 | * If "xor_edges" is true, then any duplicate edge pairs are removed. This
113 | * is useful for computing the union of a collection of polygons whose
114 | * interiors are disjoint but whose boundaries may share some common edges
115 | * (e.g. computing the union of South Africa, Lesotho, and Swaziland).
116 | *
117 | * Note that for directed edges, a "duplicate edge pair" consists of an
118 | * edge and its corresponding reverse edge. This means that either (a)
119 | * "shells" and "holes" must have opposite orientations, or (b) shells and
120 | * holes do not share edges. Otherwise undirected_edges() should be
121 | * specified.
122 | *
123 | * There are only two reasons to turn off xor_edges():
124 | *
125 | * (1) assemblePolygon() will be called, and you want to assert that there
126 | * are no duplicate edge pairs in the input.
127 | *
128 | * (2) assembleLoops() will be called, and you want to keep abutting loops
129 | * separate in the output rather than merging their regions together (e.g.
130 | * assembling loops for Kansas City, KS and Kansas City, MO simultaneously).
131 | *#/
132 | public boolean getXorEdges() {
133 | return xorEdges;
134 | }
135 |
136 | /**
137 | * Default value: false
138 | *#/
139 | public boolean getValidate() {
140 | return validate;
141 | }
142 |
143 | /**
144 | * Default value: 0
145 | *#/
146 | public S1Angle getMergeDistance() {
147 | return mergeDistance;
148 | }
149 |
150 | /**
151 | * If true, isValid() is called on all loops and polygons before
152 | * constructing them. If any loop is invalid (e.g. self-intersecting), it is
153 | * rejected and returned as a set of "unused edges". Any remaining valid
154 | * loops are kept. If the entire polygon is invalid (e.g. two loops
155 | * intersect), then all loops are rejected and returned as unused edges.
156 | *#/
157 | public void setValidate(boolean validate) {
158 | this.validate = validate;
159 | }
160 |
161 | /**
162 | * If set to a positive value, all vertices that are separated by at most
163 | * this distance will be merged together. In addition, vertices that are
164 | * closer than this distance to a non-incident edge will be spliced into it
165 | * (TODO).
166 | *
167 | * The merging is done in such a way that all vertex-vertex and vertex-edge
168 | * distances in the output are greater than 'merge_distance'.
169 | *
170 | * This method is useful for assembling polygons out of input data where
171 | * vertices and/or edges may not be perfectly aligned.
172 | *#/
173 | public void setMergeDistance(S1Angle mergeDistance) {
174 | this.mergeDistance = mergeDistance;
175 | }
176 |
177 | // Used for testing only
178 | void setUndirectedEdges(boolean undirectedEdges) {
179 | this.undirectedEdges = undirectedEdges;
180 | }
181 |
182 | // Used for testing only
183 | void setXorEdges(boolean xorEdges) {
184 | this.xorEdges = xorEdges;
185 | }
186 | }
187 |
188 | public Options options() {
189 | return options;
190 | }
191 |
192 | /**
193 | * Add the given edge to the polygon builder. This method should be used for
194 | * input data that may not follow S2 polygon conventions. Note that edges are
195 | * not allowed to cross each other. Also note that as a convenience, edges
196 | * where v0 == v1 are ignored.
197 | *#/
198 | public void addEdge(S2Point v0, S2Point v1) {
199 | // If xor_edges is true, we look for an existing edge in the opposite
200 | // direction. We either delete that edge or insert a new one.
201 |
202 | if (v0.equals(v1)) {
203 | return;
204 | }
205 |
206 | if (options.getXorEdges()) {
207 | Multiset
208 | candidates = edges.get(v1);
209 | if (candidates != null && candidates.count(v0) > 0) {
210 | eraseEdge(v1, v0);
211 | return;
212 | }
213 | }
214 |
215 | if (edges.get(v0) == null) {
216 | edges.put(v0, HashMultiset.
217 | create());
218 | }
219 |
220 | edges.get(v0).add(v1);
221 | if (options.getUndirectedEdges()) {
222 | if (edges.get(v1) == null) {
223 | edges.put(v1, HashMultiset.
224 | create());
225 | }
226 | edges.get(v1).add(v0);
227 | }
228 | }
229 |
230 | /**
231 | * Add all edges in the given loop. If the sign() of the loop is negative
232 | * (i.e. this loop represents a hole), the reverse edges are added instead.
233 | * This implies that "shells" are CCW and "holes" are CW, as required for the
234 | * directed edges convention described above.
235 | *
236 | * This method does not take ownership of the loop.
237 | *#/
238 | public void addLoop(S2Loop loop) {
239 | int sign = loop.sign();
240 | for (int i = loop.numVertices(); i > 0; --i) {
241 | // Vertex indices need to be in the range [0, 2*num_vertices()-1].
242 | addEdge(loop.vertex(i), loop.vertex(i + sign));
243 | }
244 | }
245 |
246 | /**
247 | * Add all loops in the given polygon. Shells and holes are added with
248 | * opposite orientations as described for AddLoop(). This method does not take
249 | * ownership of the polygon.
250 | *#/
251 | public void addPolygon(S2Polygon polygon) {
252 | for (int i = 0; i < polygon.numLoops(); ++i) {
253 | addLoop(polygon.loop(i));
254 | }
255 | }
256 |
257 | /**
258 | * Assembles the given edges into as many non-crossing loops as possible. When
259 | * there is a choice about how to assemble the loops, then CCW loops are
260 | * preferred. Returns true if all edges were assembled. If "unused_edges" is
261 | * not NULL, it is initialized to the set of edges that could not be assembled
262 | * into loops.
263 | *
264 | * Note that if xor_edges() is false and duplicate edge pairs may be present,
265 | * then undirected_edges() should be specified unless all loops can be
266 | * assembled in a counter-clockwise direction. Otherwise this method may not
267 | * be able to assemble all loops due to its preference for CCW loops.
268 | *
269 | * This method resets the S2PolygonBuilder state so that it can be reused.
270 | *#/
271 | public boolean assembleLoops(List
272 | loops, List
273 | unusedEdges) {
274 | if (options.getMergeDistance().radians() > 0) {
275 | mergeVertices();
276 | }
277 |
278 | List
279 | dummyUnusedEdges = Lists.newArrayList();
280 | if (unusedEdges == null) {
281 | unusedEdges = dummyUnusedEdges;
282 | }
283 |
284 | // We repeatedly choose an arbitrary edge and attempt to assemble a loop
285 | // starting from that edge. (This is always possible unless the input
286 | // includes extra edges that are not part of any loop.)
287 |
288 | unusedEdges.clear();
289 | while (!edges.isEmpty()) {
290 | Map.Entry
291 | > edge = edges.entrySet().iterator().next();
294 |
295 | S2Point v0 = edge.getKey();
296 | S2Point v1 = edge.getValue().iterator().next();
297 |
298 | S2Loop loop = assembleLoop(v0, v1, unusedEdges);
299 | if (loop == null) {
300 | continue;
301 | }
302 |
303 | // In the case of undirected edges, we may have assembled a clockwise
304 | // loop while trying to assemble a CCW loop. To fix this, we assemble
305 | // a new loop starting with an arbitrary edge in the reverse direction.
306 | // This is guaranteed to assemble a loop that is interior to the previous
307 | // one and will therefore eventually terminate.
308 |
309 | while (options.getUndirectedEdges() && !loop.isNormalized()) {
310 | loop = assembleLoop(loop.vertex(1), loop.vertex(0), unusedEdges);
311 | }
312 | loops.add(loop);
313 | eraseLoop(loop, loop.numVertices());
314 | }
315 | return unusedEdges.isEmpty();
316 | }
317 |
318 | /**
319 | * Like AssembleLoops, but normalizes all the loops so that they enclose less
320 | * than half the sphere, and then assembles the loops into a polygon.
321 | *
322 | * For this method to succeed, there should be no duplicate edges in the
323 | * input. If this is not known to be true, then the "xor_edges" option should
324 | * be set (which is true by default).
325 | *
326 | * Note that S2Polygons cannot represent arbitrary regions on the sphere,
327 | * because of the limitation that no loop encloses more than half of the
328 | * sphere. For example, an S2Polygon cannot represent a 100km wide band around
329 | * the equator. In such cases, this method will return the *complement* of the
330 | * expected region. So for example if all the world's coastlines were
331 | * assembled, the output S2Polygon would represent the land area (irrespective
332 | * of the input edge or loop orientations).
333 | *#/
334 | public boolean assemblePolygon(S2Polygon polygon, List
335 | unusedEdges) {
336 | List
337 | loops = Lists.newArrayList();
338 | boolean success = assembleLoops(loops, unusedEdges);
339 |
340 | // If edges are undirected, then all loops are already CCW. Otherwise we
341 | // need to make sure the loops are normalized.
342 | if (!options.getUndirectedEdges()) {
343 | for (int i = 0; i < loops.size(); ++i) {
344 | loops.get(i).normalize();
345 | }
346 | }
347 | if (options.getValidate() && !S2Polygon.isValid(loops)) {
348 | if (unusedEdges != null) {
349 | for (S2Loop loop : loops) {
350 | rejectLoop(loop, loop.numVertices(), unusedEdges);
351 | }
352 | }
353 | return false;
354 | }
355 | polygon.init(loops);
356 | return success;
357 | }
358 |
359 | /**
360 | * Convenience method for when you don't care about unused edges.
361 | *#/
362 | public S2Polygon assemblePolygon() {
363 | S2Polygon polygon = new S2Polygon();
364 | List
365 | unusedEdges = Lists.newArrayList();
366 |
367 | assemblePolygon(polygon, unusedEdges);
368 |
369 | return polygon;
370 | }
371 |
372 | // Debugging functions:
373 |
374 | protected void dumpEdges(S2Point v0) {
375 | log.info(v0.toString());
376 | Multiset
377 | vset = edges.get(v0);
378 | if (vset != null) {
379 | for (S2Point v : vset) {
380 | log.info(" " + v.toString());
381 | }
382 | }
383 | }
384 |
385 | protected void dump() {
386 | for (S2Point v : edges.keySet()) {
387 | dumpEdges(v);
388 | }
389 | }
390 |
391 | private void eraseEdge(S2Point v0, S2Point v1) {
392 | // Note that there may be more than one copy of an edge if we are not XORing
393 | // them, so a VertexSet is a multiset.
394 |
395 | Multiset
396 | vset = edges.get(v0);
397 | // assert (vset.count(v1) > 0);
398 | vset.remove(v1);
399 | if (vset.isEmpty()) {
400 | edges.remove(v0);
401 | }
402 |
403 | if (options.getUndirectedEdges()) {
404 | vset = edges.get(v1);
405 | // assert (vset.count(v0) > 0);
406 | vset.remove(v0);
407 | if (vset.isEmpty()) {
408 | edges.remove(v1);
409 | }
410 | }
411 | }
412 |
413 | private void eraseLoop(List
414 | v, int n) {
415 | for (int i = n - 1, j = 0; j < n; i = j++) {
416 | eraseEdge(v.get(i), v.get(j));
417 | }
418 | }
419 |
420 | private void eraseLoop(S2Loop v, int n) {
421 | for (int i = n - 1, j = 0; j < n; i = j++) {
422 | eraseEdge(v.vertex(i), v.vertex(j));
423 | }
424 | }
425 |
426 | /**
427 | * We start at the given edge and assemble a loop taking left turns whenever
428 | * possible. We stop the loop as soon as we encounter any vertex that we have
429 | * seen before *except* for the first vertex (v0). This ensures that only CCW
430 | * loops are constructed when possible.
431 | *#/
432 | private S2Loop assembleLoop(S2Point v0, S2Point v1, List
433 | unusedEdges) {
434 |
435 | // The path so far.
436 | List
437 | path = Lists.newArrayList();
438 |
439 | // Maps a vertex to its index in "path".
440 | Map
441 | index = Maps.newHashMap();
443 | path.add(v0);
444 | path.add(v1);
445 |
446 | index.put(v1, 1);
447 |
448 | while (path.size() >= 2) {
449 | // Note that "v0" and "v1" become invalid if "path" is modified.
450 | v0 = path.get(path.size() - 2);
451 | v1 = path.get(path.size() - 1);
452 |
453 | S2Point v2 = null;
454 | boolean v2Found = false;
455 | Multiset
456 | vset = edges.get(v1);
457 | if (vset != null) {
458 | for (S2Point v : vset) {
459 | // We prefer the leftmost outgoing edge, ignoring any reverse edges.
460 | if (v.equals(v0)) {
461 | continue;
462 | }
463 | if (!v2Found || S2.orderedCCW(v0, v2, v, v1)) {
464 | v2 = v;
465 | }
466 | v2Found = true;
467 | }
468 | }
469 | if (!v2Found) {
470 | // We've hit a dead end. Remove this edge and backtrack.
471 | unusedEdges.add(new S2Edge(v0, v1));
472 | eraseEdge(v0, v1);
473 | index.remove(v1);
474 | path.remove(path.size() - 1);
475 | } else if (index.get(v2) == null) {
476 | // This is the first time we've visited this vertex.
477 | index.put(v2, path.size());
478 | path.add(v2);
479 | } else {
480 | // We've completed a loop. Throw away any initial vertices that
481 | // are not part of the loop.
482 | path = path.subList(index.get(v2), path.size());
483 |
484 | if (options.getValidate() && !S2Loop.isValid(path)) {
485 | // We've constructed a loop that crosses itself, which can only happen
486 | // if there is bad input data. Throw away the whole loop.
487 | rejectLoop(path, path.size(), unusedEdges);
488 | eraseLoop(path, path.size());
489 | return null;
490 | }
491 | return new S2Loop(path);
492 | }
493 | }
494 | return null;
495 | }
496 |
497 | /** Erases all edges of the given loop and marks them as unused. *#/
498 | private void rejectLoop(S2Loop v, int n, List
499 | unusedEdges) {
500 | for (int i = n - 1, j = 0; j < n; i = j++) {
501 | unusedEdges.add(new S2Edge(v.vertex(i), v.vertex(j)));
502 | }
503 | }
504 |
505 | /** Erases all edges of the given loop and marks them as unused. *#/
506 | private void rejectLoop(List
507 | v, int n, List
508 | unusedEdges) {
509 | for (int i = n - 1, j = 0; j < n; i = j++) {
510 | unusedEdges.add(new S2Edge(v.get(i), v.get(j)));
511 | }
512 | }
513 |
514 | /** Moves a set of vertices from old to new positions. *#/
515 | private void moveVertices(Map
516 | mergeMap) {
518 | if (mergeMap.isEmpty()) {
519 | return;
520 | }
521 |
522 | // We need to copy the set of edges affected by the move, since
523 | // this.edges_could be reallocated when we start modifying it.
524 | List
525 | edgesCopy = Lists.newArrayList();
526 | for (Map.Entry
527 | > edge : this.edges.entrySet()) {
530 | S2Point v0 = edge.getKey();
531 | Multiset
532 | vset = edge.getValue();
533 | for (S2Point v1 : vset) {
534 | if (mergeMap.get(v0) != null || mergeMap.get(v1) != null) {
535 |
536 | // We only need to modify one copy of each undirected edge.
537 | if (!options.getUndirectedEdges() || v0.lessThan(v1)) {
538 | edgesCopy.add(new S2Edge(v0, v1));
539 | }
540 | }
541 | }
542 | }
543 |
544 | // Now erase all the old edges, and add all the new edges. This will
545 | // automatically take care of any XORing that needs to be done, because
546 | // EraseEdge also erases the sibiling of undirected edges.
547 | for (int i = 0; i < edgesCopy.size(); ++i) {
548 | S2Point v0 = edgesCopy.get(i).getStart();
549 | S2Point v1 = edgesCopy.get(i).getEnd();
550 | eraseEdge(v0, v1);
551 | if (mergeMap.get(v0) != null) {
552 | v0 = mergeMap.get(v0);
553 | }
554 | if (mergeMap.get(v1) != null) {
555 | v1 = mergeMap.get(v1);
556 | }
557 | addEdge(v0, v1);
558 | }
559 | }
560 |
561 | /**
562 | * Look for groups of vertices that are separated by at most merge_distance()
563 | * and merge them into a single vertex.
564 | *#/
565 | private void mergeVertices() {
566 | // The overall strategy is to start from each vertex and grow a maximal
567 | // cluster of mergable vertices. In graph theoretic terms, we find the
568 | // connected components of the undirected graph whose edges connect pairs of
569 | // vertices that are separated by at most merge_distance.
570 | //
571 | // We then choose a single representative vertex for each cluster, and
572 | // update all the edges appropriately. We choose an arbitrary existing
573 | // vertex rather than computing the centroid of all the vertices to avoid
574 | // creating new vertex pairs that need to be merged. (We guarantee that all
575 | // vertex pairs are separated by at least merge_distance in the output.)
576 |
577 | PointIndex index = new PointIndex(options.getMergeDistance().radians());
578 |
579 | for (Map.Entry
580 | > edge : edges.entrySet()) {
583 | index.add(edge.getKey());
584 | Multiset
585 | vset = edge.getValue();
586 | for (S2Point v : vset) {
587 | index.add(v);
588 | }
589 | }
590 |
591 | // Next, we loop through all the vertices and attempt to grow a maximial
592 | // mergeable group starting from each vertex.
593 |
594 | Map
595 | mergeMap = Maps.newHashMap();
597 | Stack
598 | frontier = new Stack
599 | ();
600 | List
601 | mergeable = Lists.newArrayList();
602 |
603 | for (Map.Entry
604 | entry : index.entries()) {
606 | MarkedS2Point point = entry.getValue();
607 | if (point.isMarked()) {
608 | continue; // Already processed.
609 | }
610 |
611 | point.mark();
612 |
613 | // Grow a maximal mergeable component starting from "vstart", the
614 | // canonical representative of the mergeable group.
615 | S2Point vstart = point.getPoint();
616 | frontier.push(vstart);
617 | while (!frontier.isEmpty()) {
618 | S2Point v0 = frontier.pop();
619 |
620 | index.query(v0, mergeable);
621 | for (S2Point v1 : mergeable) {
622 | frontier.push(v1);
623 | mergeMap.put(v1, vstart);
624 | }
625 | }
626 | }
627 |
628 | // Finally, we need to replace vertices according to the merge_map.
629 | moveVertices(mergeMap);
630 | }
631 |
632 | /**
633 | * A PointIndex is a cheap spatial index to help us find mergeable vertices.
634 | * Given a set of points, it can efficiently find all of the points within a
635 | * given search radius of an arbitrary query location. It is essentially just
636 | * a hash map from cell ids at a given fixed level to the set of points
637 | * contained by that cell id.
638 | *
639 | * This class is not suitable for general use because it only supports
640 | * fixed-radius queries and has various special-purpose operations to avoid
641 | * the need for additional data structures.
642 | *#/
643 | private class PointIndex extends ForwardingMultimap
644 | {
646 | private double searchRadius;
647 | private int level;
648 | private final Multimap
649 | delegate = HashMultimap.create();
651 |
652 | public PointIndex(double searchRadius) {
653 |
654 | this.searchRadius = searchRadius;
655 |
656 | // We choose a cell level such that if dist(A,B) <= search_radius, the
657 | // S2CellId at that level containing A is a vertex neighbor of B (see
658 | // S2CellId.getVertexNeighbors). This turns out to be the highest
659 | // level such that a spherical cap (i.e. "disc") of the given radius
660 | // fits completely inside all cells at that level.
661 | this.level =
662 | Math.min(S2Projections.MIN_WIDTH.getMaxLevel(2 * searchRadius), S2CellId.MAX_LEVEL - 1);
663 | }
664 |
665 | @Override
666 | protected Multimap
667 | delegate() {
669 | return delegate;
670 | }
671 |
672 | /** Add a point to the index if it does not already exist. *#/
673 | public void add(S2Point p) {
674 | S2CellId id = S2CellId.fromPoint(p).parent(level);
675 | Collection
676 | pointSet = get(id);
677 | for (MarkedS2Point point : pointSet) {
678 | if (point.getPoint().equals(p)) {
679 | return;
680 | }
681 | }
682 | put(id, new MarkedS2Point(p));
683 | }
684 |
685 | /**
686 | * Return the set the unmarked points whose distance to "center" is less
687 | * than search_radius_, and mark these points. By construction, these points
688 | * will be contained by one of the vertex neighbors of "center".
689 | *#/
690 | public void query(S2Point center, List
691 | output) {
692 | output.clear();
693 |
694 | List
695 | neighbors = Lists.newArrayList();
696 | S2CellId.fromPoint(center).getVertexNeighbors(level, neighbors);
697 | for (S2CellId id : neighbors) {
698 | // Iterate over the points contained by each vertex neighbor.
699 | for (MarkedS2Point mp : get(id)) {
700 | if (mp.isMarked()) {
701 | continue;
702 | }
703 | S2Point p = mp.getPoint();
704 |
705 | if (center.angle(p) <= searchRadius) {
706 | output.add(p);
707 | mp.mark();
708 | }
709 | }
710 | }
711 | }
712 | }
713 |
714 | /**
715 | * An S2Point that can be marked. Used in PointIndex.
716 | *#/
717 | private class MarkedS2Point {
718 | private S2Point point;
719 | private boolean mark;
720 |
721 | public MarkedS2Point(S2Point point) {
722 | this.point = point;
723 | this.mark = false;
724 | }
725 |
726 | public boolean isMarked() {
727 | return mark;
728 | }
729 |
730 | public S2Point getPoint() {
731 | return point;
732 | }
733 |
734 | public void mark() {
735 | // assert (!isMarked());
736 | this.mark = true;
737 | }
738 | }
739 | */}
740 |
--------------------------------------------------------------------------------